Mercurial > vim
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); }