[email protected] (Wietse Venema) wrote:
>
> No, Postfix read_wait() uses poll() unconditionally. If Solaris
> has an edge-triggered implementation (i.e. no notification when
> poll() is called after the connection is already closed), that
> sucks.
>
> Presumably there are other ways a way to determine that write will
> not block on Solaris, or to find out that the peer has closed the
> connection without doing I/O on it. Your call. Solaris is no longer
> a primary test platform for me.
I thought I would poke at it with a stick and see what would happen.
First attempt I changed peekfd() to detect ECONNRESET, and if so issue
close(fd). That way the later poll() would definitely fail. But
unfortunately, it fails rather hard;
Dec 17 13:22:53 smtp01.unix postfix/smtpd[5474]: [ID 197553 mail.info]
connect from l4.unix[172.20.12.2]
Dec 17 13:22:53 smtp01.unix postfix/smtpd[5474]: [ID 947731 mail.warning]
warning: Early connection lost detected: 12
Dec 17 13:22:53 smtp01.unix postfix/smtpd[5474]: [ID 197553 mail.info]
disconnect from l4.unix[172.20.12.2]
Dec 17 13:22:53 smtp01.unix postfix/smtpd[5474]: [ID 947731 mail.crit]
fatal: poll: Connection reset by peer
Dec 17 13:22:54 smtp01.unix postfix/master[5459]: [ID 947731 mail.warning]
warning: process /usr/libexec/postfix/smtpd pid 5471 exit status 1
I would guess because poll checks for POLLNVAL, and if so calls
msg_fatal(). I can't think of any other, softer, ways to mark the fd to
future poll failure. (Set it readonly? Or mark it in some Postfix internal
structure to signal failure, but I do not know Postfix enough).
So then I attempt to fix up write_wait.c to detect the problem before we
call poll().
if ((peekfd(fd) < 0) && (errno == ECONNRESET)) {
msg_warn("write_wait() connection reset %d", fd);
return 0;
}
pollfd.fd = fd;
pollfd.events = POLLOUT;
for (;;) {
switch (poll(&pollfd, 1, timeout < 0 ?
WAIT_FOR_EVENT : timeout * 1000)) {
To my surprise, I see this behaviour in Solaris:
13724: 0.0000 ioctl(11, FIONREAD, 0x08047614) Err#131
ECONNRESET
13724: 0.0000 time() =
13724: 0.0000 ioctl(11, FIONREAD, 0x08047804) = 0
Only the first call to FIONREAD gets the error?! from then on, it just
signals 0 bytes. (Presumably what you meant by edge-triggered).
So, lets detect it with a zero-length read()
char buffer[1];
if ((read(fd, buffer, 0) < 0) && (errno == ECONNRESET)) {
msg_warn("write_wait() connection reset %d", fd);
return 0;
}
Alas, we get the same situation! Only the first read() gets the error,
after that, read() returns with 0.
However, I found that this works:
char buffer[1];
if ((write(fd, buffer, 0) < 0) && (errno == EPIPE)) {
msg_warn("write_wait() connection reset %d", fd);
return 0;
}
pollfd.fd = fd;
pollfd.events = POLLOUT;
for (;;) {
switch (poll(&pollfd, 1, timeout < 0 ?
WAIT_FOR_EVENT : timeout * 1000)) {
Which truss shows as:
22179: 0.0000 read(11, " Q U I T\r\n", 4096) = 6
22179: 0.0001 ioctl(11, FIONREAD, 0x08047614) Err#131
ECONNRESET
22179: 0.0000 write(11, 0x0804782F, 0) Err#32 EPIPE
22179: 0.0001 Received signal #13, SIGPIPE [ignored]
22179: 0.0000 write(11, " 2 2 1 2 . 0 . 0 B y".., 15) Err#32 EPIPE
22179: 0.0000 Received signal #13, SIGPIPE [ignored]
22179: 0.0001 close(11) = 0
It is interesting to note the errno changed to EPIPE when calling write()
instead of read().
Is a write( ,0) always non-blocking? Probably needs answering.
Running with L4 health checks for 20mins and:
# ps -edaf| grep smtpd | wc -l
2
Everything "appears to work" but I have no put it on production. I do wish
there was a nicer way to detect the problem than to use a zero-write.
Possibly signal the problem in peekfd(), and act on it in write_wait().
--
Jorgen Lundman | <[email protected]>
Unix Administrator | +81 (0)3 -5456-2687 ext 1017 (work)
Shibuya-ku, Tokyo | +81 (0)90-5578-8500 (cell)
Japan | +81 (0)3 -3375-1767 (home)