diff src/channel.c @ 7935:3f2e0b62003d v7.4.1263

commit https://github.com/vim/vim/commit/4d919d748e4e435edb135aa5ccf6ee7de9212023 Author: Bram Moolenaar <Bram@vim.org> Date: Fri Feb 5 22:36:41 2016 +0100 patch 7.4.1263 Problem: ch_open() hangs when the server isn't running. Solution: Add a timeout. Use a dict to pass arguments. (Yasuhiro Matsumoto)
author Christian Brabandt <cb@256bit.org>
date Fri, 05 Feb 2016 22:45:06 +0100
parents 1f0743f4f88f
children 2e905dfc6999
line wrap: on
line diff
--- a/src/channel.c
+++ b/src/channel.c
@@ -42,6 +42,8 @@ static void chlog(int send, char_u *buf)
 # define SOCK_ERRNO errno = WSAGetLastError()
 # undef ECONNREFUSED
 # define ECONNREFUSED WSAECONNREFUSED
+# undef EWOULDBLOCK
+# define EWOULDBLOCK WSAEWOULDBLOCK
 # ifdef EINTR
 #  undef EINTR
 # endif
@@ -119,6 +121,8 @@ typedef struct {
 
     int	      ch_json_mode;	/* TRUE for a json channel */
     jsonq_T   ch_json_head;	/* dummy node, header for circular queue */
+
+    int       ch_timeout;	/* request timeout in msec */
 } channel_T;
 
 /*
@@ -133,6 +137,48 @@ static int channel_count = 0;
  */
 FILE *debugfd = NULL;
 
+#ifdef _WIN32
+# undef PERROR
+# define PERROR(msg) (void)emsg3((char_u *)"%s: %s", \
+	(char_u *)msg, (char_u *)strerror_win32(errno))
+
+    static char *
+strerror_win32(int eno)
+{
+    static LPVOID msgbuf = NULL;
+    char_u *ptr;
+
+    if (msgbuf)
+	LocalFree(msgbuf);
+    FormatMessage(
+	FORMAT_MESSAGE_ALLOCATE_BUFFER |
+	FORMAT_MESSAGE_FROM_SYSTEM |
+	FORMAT_MESSAGE_IGNORE_INSERTS,
+	NULL,
+	eno,
+	MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
+	(LPTSTR) &msgbuf,
+	0,
+	NULL);
+    /* chomp \r or \n */
+    for (ptr = (char_u *)msgbuf; *ptr; ptr++)
+	switch (*ptr)
+	{
+	    case '\r':
+		STRMOVE(ptr, ptr + 1);
+		ptr--;
+		break;
+	    case '\n':
+		if (*(ptr + 1) == '\0')
+		    *ptr = '\0';
+		else
+		    *ptr = ' ';
+		break;
+	}
+    return msgbuf;
+}
+#endif
+
 /*
  * Add a new channel slot, return the index.
  * The channel isn't actually used into ch_fd is set >= 0;
@@ -182,6 +228,8 @@ add_channel(void)
     ch->ch_json_head.next = &ch->ch_json_head;
     ch->ch_json_head.prev = &ch->ch_json_head;
 
+    ch->ch_timeout = 2000;
+
     return channel_count++;
 }
 
@@ -303,17 +351,19 @@ channel_gui_unregister(int idx)
  * Returns a negative number for failure.
  */
     int
-channel_open(char *hostname, int port_in, void (*close_cb)(void))
+channel_open(char *hostname, int port_in, int waittime, void (*close_cb)(void))
 {
     int			sd;
     struct sockaddr_in	server;
     struct hostent *	host;
 #ifdef WIN32
     u_short		port = port_in;
+    u_long		val = 1;
 #else
     int			port = port_in;
 #endif
     int			idx;
+    int			ret;
 
 #ifdef WIN32
     channel_init_winsock();
@@ -348,63 +398,121 @@ channel_open(char *hostname, int port_in
     }
     memcpy((char *)&server.sin_addr, host->h_addr, host->h_length);
 
-    /* Connect to server */
-    if (connect(sd, (struct sockaddr *)&server, sizeof(server)))
+    if (waittime >= 0)
     {
-	SOCK_ERRNO;
-	CHERROR("channel_open: Connect failed with errno %d\n", errno);
-	if (errno == ECONNREFUSED)
+	/* Make connect non-blocking. */
+	if (
+#ifdef _WIN32
+	    ioctlsocket(sd, FIONBIO, &val) < 0
+#else
+	    fcntl(sd, F_SETFL, O_NONBLOCK) < 0
+#endif
+	   )
 	{
+	    SOCK_ERRNO;
+	    CHERROR("channel_open: Connect failed with errno %d\n", errno);
 	    sock_close(sd);
-	    if ((sd = (sock_T)socket(AF_INET, SOCK_STREAM, 0)) == (sock_T)-1)
-	    {
-		SOCK_ERRNO;
-		CHERROR("socket() retry in channel_open()\n", "");
-		PERROR("E900: socket() retry in channel_open()");
-		return -1;
-	    }
-	    if (connect(sd, (struct sockaddr *)&server, sizeof(server)))
-	    {
-		int retries = 36;
-		int success = FALSE;
+	    return -1;
+	}
+    }
 
-		SOCK_ERRNO;
-		while (retries-- && ((errno == ECONNREFUSED)
-							 || (errno == EINTR)))
-		{
-		    CHERROR("retrying...\n", "");
-		    mch_delay(3000L, TRUE);
-		    ui_breakcheck();
-		    if (got_int)
-		    {
-			errno = EINTR;
-			break;
-		    }
-		    if (connect(sd, (struct sockaddr *)&server,
-							 sizeof(server)) == 0)
-		    {
-			success = TRUE;
-			break;
-		    }
-		    SOCK_ERRNO;
-		}
-		if (!success)
-		{
-		    /* Get here when the server can't be found. */
-		    CHERROR("Cannot connect to port after retry\n", "");
-		    PERROR(_("E899: Cannot connect to port after retry2"));
-		    sock_close(sd);
-		    return -1;
-		}
-	    }
+    /* Try connecting to the server. */
+    ret = connect(sd, (struct sockaddr *)&server, sizeof(server));
+    SOCK_ERRNO;
+    if (ret < 0)
+    {
+	if (errno != EWOULDBLOCK && errno != EINPROGRESS)
+	{
+	    CHERROR("channel_open: Connect failed with errno %d\n", errno);
+	    CHERROR("Cannot connect to port\n", "");
+	    PERROR(_("E902: Cannot connect to port"));
+	    sock_close(sd);
+	    return -1;
 	}
-	else
+    }
+
+    if (waittime >= 0)
+    {
+	struct timeval	tv;
+	fd_set		rfds, wfds;
+
+	FD_ZERO(&rfds);
+	FD_ZERO(&wfds);
+	FD_SET(sd, &rfds);
+	FD_SET(sd, &wfds);
+	tv.tv_sec = waittime;
+	tv.tv_usec = 0;
+	ret = select((int)sd+1, &rfds, &wfds, NULL, &tv);
+	if (ret < 0)
 	{
+	    SOCK_ERRNO;
+	    CHERROR("channel_open: Connect failed with errno %d\n", errno);
 	    CHERROR("Cannot connect to port\n", "");
 	    PERROR(_("E902: Cannot connect to port"));
 	    sock_close(sd);
 	    return -1;
 	}
+	if (!FD_ISSET(sd, &rfds) && !FD_ISSET(sd, &wfds))
+	{
+	    errno = ECONNREFUSED;
+	    CHERROR("Cannot connect to port\n", "");
+	    PERROR(_("E902: Cannot connect to port"));
+	    sock_close(sd);
+	    return -1;
+	}
+
+#ifdef _WIN32
+	val = 0;
+	ioctlsocket(sd, FIONBIO, &val);
+#else
+	fcntl(sd, F_SETFL, 0);
+#endif
+    }
+
+    if (errno == ECONNREFUSED)
+    {
+	sock_close(sd);
+	if ((sd = (sock_T)socket(AF_INET, SOCK_STREAM, 0)) == (sock_T)-1)
+	{
+	    SOCK_ERRNO;
+	    CHERROR("socket() retry in channel_open()\n", "");
+	    PERROR("E900: socket() retry in channel_open()");
+	    return -1;
+	}
+	if (connect(sd, (struct sockaddr *)&server, sizeof(server)))
+	{
+	    int retries = 36;
+	    int success = FALSE;
+
+	    SOCK_ERRNO;
+	    while (retries-- && ((errno == ECONNREFUSED)
+						     || (errno == EINTR)))
+	    {
+		CHERROR("retrying...\n", "");
+		mch_delay(3000L, TRUE);
+		ui_breakcheck();
+		if (got_int)
+		{
+		    errno = EINTR;
+		    break;
+		}
+		if (connect(sd, (struct sockaddr *)&server,
+						     sizeof(server)) == 0)
+		{
+		    success = TRUE;
+		    break;
+		}
+		SOCK_ERRNO;
+	    }
+	    if (!success)
+	    {
+		/* Get here when the server can't be found. */
+		CHERROR("Cannot connect to port after retry\n", "");
+		PERROR(_("E899: Cannot connect to port after retry2"));
+		sock_close(sd);
+		return -1;
+	    }
+	}
     }
 
     channels[idx].ch_fd = sd;
@@ -427,6 +535,15 @@ channel_set_json_mode(int idx, int json_
 }
 
 /*
+ * Set the read timeout of channel "idx".
+ */
+    void
+channel_set_timeout(int idx, int timeout)
+{
+    channels[idx].ch_timeout = timeout;
+}
+
+/*
  * Set the callback for channel "idx".
  */
     void
@@ -898,6 +1015,7 @@ channel_close(int idx)
 #endif
 	vim_free(channel->ch_callback);
 	channel->ch_callback = NULL;
+	channel->ch_timeout = 2000;
 
 	while (channel_peek(idx) != NULL)
 	    vim_free(channel_get(idx));
@@ -1148,9 +1266,8 @@ channel_read_block(int idx)
 {
     if (channel_peek(idx) == NULL)
     {
-	/* Wait for up to 2 seconds.
-	 * TODO: use timeout set on the channel. */
-	if (channel_wait(channels[idx].ch_fd, 2000) == FAIL)
+	/* Wait for up to the channel timeout. */
+	if (channel_wait(channels[idx].ch_fd, channels[idx].ch_timeout) == FAIL)
 	    return NULL;
 	channel_read(idx);
     }
@@ -1161,7 +1278,7 @@ channel_read_block(int idx)
 /*
  * Read one JSON message from channel "ch_idx" with ID "id" and store the
  * result in "rettv".
- * Blocks until the message is received.
+ * Blocks until the message is received or the timeout is reached.
  */
     int
 channel_read_json_block(int ch_idx, int id, typval_T **rettv)
@@ -1183,10 +1300,10 @@ channel_read_json_block(int ch_idx, int 
 	    if (channel_parse_messages())
 		continue;
 
-	    /* Wait for up to 2 seconds.
-	     * TODO: use timeout set on the channel. */
+	    /* Wait for up to the channel timeout. */
 	    if (channels[ch_idx].ch_fd < 0
-			|| channel_wait(channels[ch_idx].ch_fd, 2000) == FAIL)
+			|| channel_wait(channels[ch_idx].ch_fd,
+					 channels[ch_idx].ch_timeout) == FAIL)
 		break;
 	    channel_read(ch_idx);
 	}