summaryrefslogtreecommitdiffstats
path: root/unix/tclUnixChan.c
diff options
context:
space:
mode:
Diffstat (limited to 'unix/tclUnixChan.c')
-rw-r--r--unix/tclUnixChan.c61
1 files changed, 58 insertions, 3 deletions
diff --git a/unix/tclUnixChan.c b/unix/tclUnixChan.c
index 93bb1fe..1d60340 100644
--- a/unix/tclUnixChan.c
+++ b/unix/tclUnixChan.c
@@ -150,6 +150,7 @@ typedef struct TcpState {
int fd; /* The socket itself. */
int flags; /* ORed combination of the bitfields defined
* below. */
+ int interest; /* Events types of interest. */
Tcl_TcpAcceptProc *acceptProc;
/* Proc to call on accept. */
ClientData acceptProcData; /* The data for the accept proc. */
@@ -248,6 +249,7 @@ static int TtySetOptionProc(ClientData instanceData,
static int WaitForConnect(TcpState *statePtr, int *errorCodePtr);
static Tcl_Channel MakeTcpClientChannelMode(ClientData tcpSocket,
int mode);
+static void WrapNotify(ClientData clientData, int mask);
/*
* This structure describes the channel type structure for file based IO:
@@ -2198,6 +2200,35 @@ TcpGetOptionProc(
*/
static void
+WrapNotify(
+ ClientData clientData,
+ int mask)
+{
+ TcpState *statePtr = (TcpState *) clientData;
+ int newmask = mask & statePtr->interest;
+
+ if (newmask == 0) {
+ /*
+ * There was no overlap between the states the channel is
+ * interested in notifications for, and the states that are
+ * reported present on the file descriptor by select(). The
+ * only way that can happen is when the channel is interested
+ * in a writable condition, and only a readable state is reported
+ * present (see TcpWatchProc() below). In that case, signal back
+ * to the caller the writable state, which is really an error
+ * condition. As an extra check on that assumption, check for
+ * a non-zero value of errno before reporting an artificial
+ * writable state.
+ */
+ if (errno == 0) {
+ return;
+ }
+ newmask = TCL_WRITABLE;
+ }
+ Tcl_NotifyChannel(statePtr->channel, newmask);
+}
+
+static void
TcpWatchProc(
ClientData instanceData, /* The socket state. */
int mask) /* Events of interest; an OR-ed combination of
@@ -2214,9 +2245,30 @@ TcpWatchProc(
if (!statePtr->acceptProc) {
if (mask) {
- Tcl_CreateFileHandler(statePtr->fd, mask,
- (Tcl_FileProc *) Tcl_NotifyChannel,
- (ClientData) statePtr->channel);
+
+ /*
+ * Whether it is a bug or feature or otherwise, it is a fact
+ * of life that on at least some Linux kernels select() fails
+ * to report that a socket file descriptor is writable when
+ * the other end of the socket is closed. This is in contrast
+ * to the guarantees Tcl makes that its channels become
+ * writable and fire writable events on an error conditon.
+ * This has caused a leak of file descriptors in a state of
+ * background flushing. See Tcl ticket 1758a0b603.
+ *
+ * As a workaround, when our caller indicates an interest in
+ * writable notifications, we must tell the notifier built
+ * around select() that we are interested in the readable state
+ * of the file descriptor as well, as that is the only reliable
+ * means to get notified of error conditions. Then it is the
+ * task of WrapNotify() above to untangle the meaning of these
+ * channel states and report the chan events as best it can.
+ * We save a copy of the mask passed in to assist with that.
+ */
+
+ statePtr->interest = mask;
+ Tcl_CreateFileHandler(statePtr->fd, mask|TCL_READABLE,
+ (Tcl_FileProc *) WrapNotify, (ClientData) statePtr);
} else {
Tcl_DeleteFileHandler(statePtr->fd);
}
@@ -2404,6 +2456,7 @@ CreateSocket(
if (asyncConnect) {
statePtr->flags = TCP_ASYNC_CONNECT;
}
+ statePtr->interest = 0;
statePtr->fd = sock;
return statePtr;
@@ -2675,6 +2728,7 @@ MakeTcpClientChannelMode(
statePtr = (TcpState *) ckalloc((unsigned) sizeof(TcpState));
statePtr->fd = PTR2INT(sock);
statePtr->flags = 0;
+ statePtr->interest = 0;
statePtr->acceptProc = NULL;
statePtr->acceptProcData = NULL;
@@ -2792,6 +2846,7 @@ TcpAccept(
newSockState = (TcpState *) ckalloc((unsigned) sizeof(TcpState));
newSockState->flags = 0;
+ newSockState->interest = 0;
newSockState->fd = newsock;
newSockState->acceptProc = NULL;
newSockState->acceptProcData = NULL;