a problem of unix domain socket

kawasa_r at itg.hitachi.co.jp kawasa_r at itg.hitachi.co.jp
Fri May 7 09:38:51 GMT 2004


After an abnormal termination of the winbind daemon, processses which tried to 
communicate winbind daemon hang.

Processes use UNIX domain sockets when they communicate winbind daemon.
This socket does not detects timeout when establishing the connection, and 
sending/receiveing data in default. Because of this specification, if 
winbind daemon in LISTEN status is abnormally terminated, processes that 
communicating winbind daemon fell into eternal waiting status. 

Set nonblock attribute to the UNIX domain socket before communicate winbind daemon.
Then, retry if failed to establish connection and send/receive data.
When retry the managements, define a threshold and detect timeout errors.

Index: samba-302/source/nsswitch/wb_common.c
===================================================================
RCS file: /cvs/samba-302/source/nsswitch/wb_common.c,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- samba-302/source/nsswitch/wb_common.c	16 Feb 2004 01:13:36 -0000	1.1
+++ samba-302/source/nsswitch/wb_common.c	17 Mar 2004 05:16:26 -0000	1.2
@@ -70,6 +70,10 @@
 	}
 }
 
+#define CONNECT_TIMEOUT 30
+#define WRITE_TIMEOUT CONNECT_TIMEOUT
+#define READ_TIMEOUT CONNECT_TIMEOUT
+
 /* Make sure socket handle isn't stdin, stdout or stderr */
 #define RECURSION_LIMIT 3
 
@@ -137,6 +141,9 @@
 	struct stat st;
 	pstring path;
 	int fd;
+	int flag;
+	int wait_time;
+	int slept;
 	
 	/* Check permissions on unix socket directory */
 	
@@ -188,14 +195,67 @@
 	if ((fd = make_safe_fd( fd)) == -1) {
 		return fd;
 	}
-	
-	if (connect(fd, (struct sockaddr *)&sunaddr, 
-		    sizeof(sunaddr)) == -1) {
+
+	if ((flag = fcntl(fd, F_GETFL)) == -1) {
+		close(fd);
+		return -1;
+	}
+
+	flag |= O_NONBLOCK;
+	if (fcntl(fd, F_SETFL, flag) == -1) {
 		close(fd);
 		return -1;
 	}
+	
+	for (wait_time = 0;
+	    connect(fd, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) == -1;
+	    wait_time += slept) {
+		struct timeval tv;
+		fd_set w_fds;
+		int ret;
+		int connect_errno = 0, errnosize;
+
+		if (wait_time >= CONNECT_TIMEOUT) goto error_out;
+
+		switch (errno) {
+		    case EINPROGRESS:
+			FD_ZERO(&w_fds);
+			FD_SET(fd, &w_fds);
+			tv.tv_sec = CONNECT_TIMEOUT - wait_time;
+			tv.tv_usec = 0;
+
+			ret = select(fd + 1, NULL, &w_fds, NULL, &tv);
+
+			if (ret > 0) {
+				errnosize = sizeof(connect_errno);
+
+				ret = getsockopt(fd, SOL_SOCKET,
+				    SO_ERROR, &connect_errno, &errnosize);
+
+				if (ret >= 0 && connect_errno == 0) {
+					/* Connect succeed */
+					goto out;
+				}
+			}
+				
+			slept = CONNECT_TIMEOUT;
+			break;
+		    case EAGAIN:
+			slept = rand() % 3 + 1;
+			sleep(slept);
+			break;
+		    default:
+			goto error_out;
+		}	
+
+	}
         
+ out:
 	return fd;
+
+ error_out:
+	close(fd);
+	return -1;
 }
 
 /* Connect to winbindd socket */
@@ -268,6 +328,7 @@
 	while(nwritten < count) {
 		struct timeval tv;
 		fd_set r_fds;
+		fd_set w_fds;
 		
 		/* Catch pipe close on other end by checking if a read()
 		   call would not block by calling select(). */
@@ -284,19 +345,41 @@
 		/* Write should be OK if fd not available for reading */
 		
 		if (!FD_ISSET(winbindd_fd, &r_fds)) {
-			
+			int ret;
+
 			/* Do the write */
 			
 			result = write(winbindd_fd,
 				       (char *)buffer + nwritten, 
 				       count - nwritten);
 			
-			if ((result == -1) || (result == 0)) {
+			switch (result) {
+			  case -1:
+				if (errno == EAGAIN) {
+					FD_ZERO(&w_fds);
+					FD_SET(winbindd_fd, &w_fds);
+					tv.tv_sec = WRITE_TIMEOUT;
+					tv.tv_usec = 0;
 				
+					ret = select(winbindd_fd + 1,
+						     NULL, &w_fds, NULL, &tv);
+
+					if (ret > 0 && FD_ISSET(winbindd_fd, &w_fds)) {
+						/* retry */
+						result = write(winbindd_fd,
+							    (char *)buffer + nwritten,
+							    count - nwritten);
+						if (result > 0) break;
+					}
+				}
+
+				/* Fall through */
+			  case 0:
 				/* Write failed */
-				
 				close_sock();
 				return -1;
+			  default:
+				/* Write succeed */
 			}
 			
 			nwritten += result;
@@ -322,18 +405,43 @@
 	/* Read data from socket */
 	
 	while(nread < count) {
+		struct timeval tv;
+		fd_set r_fds;
+		int ret;
 		
 		result = read(winbindd_fd, (char *)buffer + nread, 
 			      count - nread);
 		
-		if ((result == -1) || (result == 0)) {
-			
+		switch (result) {
+		  case -1:
+			if (errno == EAGAIN) {
+				FD_ZERO(&r_fds);
+				FD_SET(winbindd_fd, &r_fds);
+				tv.tv_sec = READ_TIMEOUT;
+				tv.tv_usec = 0;
+				
+				ret = select(winbindd_fd + 1,
+					     &r_fds, NULL, NULL, &tv);
+
+				if (ret > 0 && FD_ISSET(winbindd_fd, &r_fds)) {
+					/* retry */
+					result = read(winbindd_fd,
+							    (char *)buffer + nread, 
+							    count - nread);
+					if (result > 0) break;
+				}
+			}
+
+			/* Fall through */
+		  case 0:
 			/* Read failed.  I think the only useful thing we
 			   can do here is just return -1 and fail since the
 			   transaction has failed half way through. */
 			
 			close_sock();
 			return -1;
+		  default:
+			/* Read succeed */
 		}
 		
 		nread += result;


More information about the samba-technical mailing list