0
0
mirror of https://github.com/vim/vim.git synced 2025-07-26 11:04:33 -04:00

patch 8.0.0492: a failing client-server request can make Vim hang

Problem:    A failing client-server request can make Vim hang.
Solution:   Add a timeout argument to functions that wait.
This commit is contained in:
Bram Moolenaar 2017-03-19 21:20:53 +01:00
parent bfd830d3e2
commit 81b9d0bd5c
10 changed files with 84 additions and 76 deletions

View File

@ -6320,15 +6320,17 @@ reltimestr({time}) *reltimestr()*
{only available when compiled with the |+reltime| feature} {only available when compiled with the |+reltime| feature}
*remote_expr()* *E449* *remote_expr()* *E449*
remote_expr({server}, {string} [, {idvar}]) remote_expr({server}, {string} [, {idvar} [, {timeout}]])
Send the {string} to {server}. The string is sent as an Send the {string} to {server}. The string is sent as an
expression and the result is returned after evaluation. expression and the result is returned after evaluation.
The result must be a String or a |List|. A |List| is turned The result must be a String or a |List|. A |List| is turned
into a String by joining the items with a line break in into a String by joining the items with a line break in
between (not at the end), like with join(expr, "\n"). between (not at the end), like with join(expr, "\n").
If {idvar} is present, it is taken as the name of a If {idvar} is present and not empty, it is taken as the name
variable and a {serverid} for later use with of a variable and a {serverid} for later use with
remote_read() is stored there. remote_read() is stored there.
If {timeout} is given the read times out after this many
seconds. Otherwise a timeout of 600 seconds is used.
See also |clientserver| |RemoteReply|. See also |clientserver| |RemoteReply|.
This function is not available in the |sandbox|. This function is not available in the |sandbox|.
{only available when compiled with the |+clientserver| feature} {only available when compiled with the |+clientserver| feature}
@ -6367,9 +6369,10 @@ remote_peek({serverid} [, {retvar}]) *remote_peek()*
:let repl = "" :let repl = ""
:echo "PEEK: ".remote_peek(id, "repl").": ".repl :echo "PEEK: ".remote_peek(id, "repl").": ".repl
remote_read({serverid}) *remote_read()* remote_read({serverid}, [{timeout}]) *remote_read()*
Return the oldest available reply from {serverid} and consume Return the oldest available reply from {serverid} and consume
it. It blocks until a reply is available. it. Unless a {timeout} in seconds is given, it blocks until a
reply is available.
See also |clientserver|. See also |clientserver|.
This function is not available in the |sandbox|. This function is not available in the |sandbox|.
{only available when compiled with the |+clientserver| feature} {only available when compiled with the |+clientserver| feature}

View File

@ -739,10 +739,10 @@ static struct fst
{"reltimefloat", 1, 1, f_reltimefloat}, {"reltimefloat", 1, 1, f_reltimefloat},
#endif #endif
{"reltimestr", 1, 1, f_reltimestr}, {"reltimestr", 1, 1, f_reltimestr},
{"remote_expr", 2, 3, f_remote_expr}, {"remote_expr", 2, 4, f_remote_expr},
{"remote_foreground", 1, 1, f_remote_foreground}, {"remote_foreground", 1, 1, f_remote_foreground},
{"remote_peek", 1, 2, f_remote_peek}, {"remote_peek", 1, 2, f_remote_peek},
{"remote_read", 1, 1, f_remote_read}, {"remote_read", 1, 2, f_remote_read},
{"remote_send", 2, 3, f_remote_send}, {"remote_send", 2, 3, f_remote_send},
{"remote_startserver", 1, 1, f_remote_startserver}, {"remote_startserver", 1, 1, f_remote_startserver},
{"remove", 2, 3, f_remove}, {"remove", 2, 3, f_remove},
@ -8515,6 +8515,7 @@ remote_common(typval_T *argvars, typval_T *rettv, int expr)
char_u *keys; char_u *keys;
char_u *r = NULL; char_u *r = NULL;
char_u buf[NUMBUFLEN]; char_u buf[NUMBUFLEN];
int timeout = 0;
# ifdef WIN32 # ifdef WIN32
HWND w; HWND w;
# else # else
@ -8528,16 +8529,19 @@ remote_common(typval_T *argvars, typval_T *rettv, int expr)
if (check_connection() == FAIL) if (check_connection() == FAIL)
return; return;
# endif # endif
if (argvars[2].v_type != VAR_UNKNOWN
&& argvars[3].v_type != VAR_UNKNOWN)
timeout = get_tv_number(&argvars[3]);
server_name = get_tv_string_chk(&argvars[0]); server_name = get_tv_string_chk(&argvars[0]);
if (server_name == NULL) if (server_name == NULL)
return; /* type error; errmsg already given */ return; /* type error; errmsg already given */
keys = get_tv_string_buf(&argvars[1], buf); keys = get_tv_string_buf(&argvars[1], buf);
# ifdef WIN32 # ifdef WIN32
if (serverSendToVim(server_name, keys, &r, &w, expr, TRUE) < 0) if (serverSendToVim(server_name, keys, &r, &w, expr, timeout, TRUE) < 0)
# else # else
if (serverSendToVim(X_DISPLAY, server_name, keys, &r, &w, expr, 0, TRUE) if (serverSendToVim(X_DISPLAY, server_name, keys, &r, &w, expr, timeout,
< 0) 0, TRUE) < 0)
# endif # endif
{ {
if (r != NULL) if (r != NULL)
@ -8555,13 +8559,15 @@ remote_common(typval_T *argvars, typval_T *rettv, int expr)
char_u str[30]; char_u str[30];
char_u *idvar; char_u *idvar;
sprintf((char *)str, PRINTF_HEX_LONG_U, (long_u)w);
v.di_tv.v_type = VAR_STRING;
v.di_tv.vval.v_string = vim_strsave(str);
idvar = get_tv_string_chk(&argvars[2]); idvar = get_tv_string_chk(&argvars[2]);
if (idvar != NULL) if (idvar != NULL && *idvar != NUL)
{
sprintf((char *)str, PRINTF_HEX_LONG_U, (long_u)w);
v.di_tv.v_type = VAR_STRING;
v.di_tv.vval.v_string = vim_strsave(str);
set_var(idvar, &v.di_tv, FALSE); set_var(idvar, &v.di_tv, FALSE);
vim_free(v.di_tv.vval.v_string); vim_free(v.di_tv.vval.v_string);
}
} }
} }
#endif #endif
@ -8633,7 +8639,7 @@ f_remote_peek(typval_T *argvars UNUSED, typval_T *rettv)
rettv->vval.v_number = -1; rettv->vval.v_number = -1;
else else
{ {
s = serverGetReply((HWND)n, FALSE, FALSE, FALSE); s = serverGetReply((HWND)n, FALSE, FALSE, FALSE, 0);
rettv->vval.v_number = (s != NULL); rettv->vval.v_number = (s != NULL);
} }
# else # else
@ -8670,17 +8676,23 @@ f_remote_read(typval_T *argvars UNUSED, typval_T *rettv)
if (serverid != NULL && !check_restricted() && !check_secure()) if (serverid != NULL && !check_restricted() && !check_secure())
{ {
int timeout = 0;
if (argvars[1].v_type != VAR_UNKNOWN)
timeout = get_tv_number(&argvars[1]);
# ifdef WIN32 # ifdef WIN32
/* The server's HWND is encoded in the 'id' parameter */ /* The server's HWND is encoded in the 'id' parameter */
long_u n = 0; long_u n = 0;
sscanf((char *)serverid, SCANF_HEX_LONG_U, &n); sscanf((char *)serverid, SCANF_HEX_LONG_U, &n);
if (n != 0) if (n != 0)
r = serverGetReply((HWND)n, FALSE, TRUE, TRUE); r = serverGetReply((HWND)n, FALSE, TRUE, TRUE, timeout);
if (r == NULL) if (r == NULL)
# else # else
if (check_connection() == FAIL || serverReadReply(X_DISPLAY, if (check_connection() == FAIL
serverStrToWin(serverid), &r, FALSE) < 0) || serverReadReply(X_DISPLAY, serverStrToWin(serverid),
&r, FALSE, timeout) < 0)
# endif # endif
EMSG(_("E277: Unable to read a server reply")); EMSG(_("E277: Unable to read a server reply"));
} }

View File

@ -373,6 +373,7 @@ serverSendToVim(
char_u **result, /* Result of eval'ed expression */ char_u **result, /* Result of eval'ed expression */
Window *server, /* Actual ID of receiving app */ Window *server, /* Actual ID of receiving app */
Bool asExpr, /* Interpret as keystrokes or expr ? */ Bool asExpr, /* Interpret as keystrokes or expr ? */
int timeout, /* seconds to wait or zero */
Bool localLoop, /* Throw away everything but result */ Bool localLoop, /* Throw away everything but result */
int silent) /* don't complain about no server */ int silent) /* don't complain about no server */
{ {
@ -485,7 +486,8 @@ serverSendToVim(
pending.nextPtr = pendingCommands; pending.nextPtr = pendingCommands;
pendingCommands = &pending; pendingCommands = &pending;
ServerWait(dpy, w, WaitForPend, &pending, localLoop, 600); ServerWait(dpy, w, WaitForPend, &pending, localLoop,
timeout > 0 ? timeout : 600);
/* /*
* Unregister the information about the pending command * Unregister the information about the pending command
@ -790,6 +792,7 @@ WaitForReply(void *p)
/* /*
* Wait for replies from id (win) * Wait for replies from id (win)
* When "timeout" is non-zero wait up to this many seconds.
* Return 0 and the malloc'ed string when a reply is available. * Return 0 and the malloc'ed string when a reply is available.
* Return -1 if the window becomes invalid while waiting. * Return -1 if the window becomes invalid while waiting.
*/ */
@ -798,13 +801,15 @@ serverReadReply(
Display *dpy, Display *dpy,
Window win, Window win,
char_u **str, char_u **str,
int localLoop) int localLoop,
int timeout)
{ {
int len; int len;
char_u *s; char_u *s;
struct ServerReply *p; struct ServerReply *p;
ServerWait(dpy, win, WaitForReply, &win, localLoop, -1); ServerWait(dpy, win, WaitForReply, &win, localLoop,
timeout > 0 ? timeout : -1);
if ((p = ServerReplyFind(win, SROP_Find)) != NULL && p->strings.ga_len > 0) if ((p = ServerReplyFind(win, SROP_Find)) != NULL && p->strings.ga_len > 0)
{ {

View File

@ -3791,10 +3791,10 @@ cmdsrv_main(
} }
else else
ret = serverSendToVim(xterm_dpy, sname, *serverStr, ret = serverSendToVim(xterm_dpy, sname, *serverStr,
NULL, &srv, 0, 0, silent); NULL, &srv, 0, 0, 0, silent);
# else # else
/* Win32 always works? */ /* Win32 always works? */
ret = serverSendToVim(sname, *serverStr, NULL, &srv, 0, silent); ret = serverSendToVim(sname, *serverStr, NULL, &srv, 0, 0, silent);
# endif # endif
if (ret < 0) if (ret < 0)
{ {
@ -3854,11 +3854,11 @@ cmdsrv_main(
while (memchr(done, 0, numFiles) != NULL) while (memchr(done, 0, numFiles) != NULL)
{ {
# ifdef WIN32 # ifdef WIN32
p = serverGetReply(srv, NULL, TRUE, TRUE); p = serverGetReply(srv, NULL, TRUE, TRUE, 0);
if (p == NULL) if (p == NULL)
break; break;
# else # else
if (serverReadReply(xterm_dpy, srv, &p, TRUE) < 0) if (serverReadReply(xterm_dpy, srv, &p, TRUE, -1) < 0)
break; break;
# endif # endif
j = atoi((char *)p); j = atoi((char *)p);
@ -3885,12 +3885,12 @@ cmdsrv_main(
# ifdef WIN32 # ifdef WIN32
/* Win32 always works? */ /* Win32 always works? */
if (serverSendToVim(sname, (char_u *)argv[i + 1], if (serverSendToVim(sname, (char_u *)argv[i + 1],
&res, NULL, 1, FALSE) < 0) &res, NULL, 1, 0, FALSE) < 0)
# else # else
if (xterm_dpy == NULL) if (xterm_dpy == NULL)
mch_errmsg(_("No display: Send expression failed.\n")); mch_errmsg(_("No display: Send expression failed.\n"));
else if (serverSendToVim(xterm_dpy, sname, (char_u *)argv[i + 1], else if (serverSendToVim(xterm_dpy, sname, (char_u *)argv[i + 1],
&res, NULL, 1, 1, FALSE) < 0) &res, NULL, 1, 0, 1, FALSE) < 0)
# endif # endif
{ {
if (res != NULL && *res != NUL) if (res != NULL && *res != NUL)

View File

@ -2401,6 +2401,7 @@ serverSendToVim(
char_u **result, /* Result of eval'ed expression */ char_u **result, /* Result of eval'ed expression */
void *ptarget, /* HWND of server */ void *ptarget, /* HWND of server */
int asExpr, /* Expression or keys? */ int asExpr, /* Expression or keys? */
int timeout, /* timeout in seconds or zero */
int silent) /* don't complain about no server */ int silent) /* don't complain about no server */
{ {
HWND target; HWND target;
@ -2444,7 +2445,7 @@ serverSendToVim(
return -1; return -1;
if (asExpr) if (asExpr)
retval = serverGetReply(target, &retcode, TRUE, TRUE); retval = serverGetReply(target, &retcode, TRUE, TRUE, timeout);
if (result == NULL) if (result == NULL)
vim_free(retval); vim_free(retval);
@ -2521,14 +2522,17 @@ save_reply(HWND server, char_u *reply, int expr)
* if "wait" is TRUE block until a message arrives (or the server exits). * if "wait" is TRUE block until a message arrives (or the server exits).
*/ */
char_u * char_u *
serverGetReply(HWND server, int *expr_res, int remove, int wait) serverGetReply(HWND server, int *expr_res, int remove, int wait, int timeout)
{ {
int i; int i;
char_u *reply; char_u *reply;
reply_T *rep; reply_T *rep;
int did_process = FALSE; int did_process = FALSE;
time_t start;
time_t now;
/* When waiting, loop until the message waiting for is received. */ /* When waiting, loop until the message waiting for is received. */
time(&start);
for (;;) for (;;)
{ {
/* Reset this here, in case a message arrives while we are going /* Reset this here, in case a message arrives while we are going
@ -2584,6 +2588,10 @@ serverGetReply(HWND server, int *expr_res, int remove, int wait)
#ifdef FEAT_TIMERS #ifdef FEAT_TIMERS
check_due_timer(); check_due_timer();
#endif #endif
time(&now);
if (timeout > 0 && (now - start) >= timeout)
break;
/* Wait for a SendMessage() call to us. This could be the reply /* Wait for a SendMessage() call to us. This could be the reply
* we are waiting for. Use a timeout of a second, to catch the * we are waiting for. Use a timeout of a second, to catch the
* situation that the server died unexpectedly. */ * situation that the server died unexpectedly. */

View File

@ -1,11 +1,11 @@
/* if_xcmdsrv.c */ /* if_xcmdsrv.c */
int serverRegisterName(Display *dpy, char_u *name); int serverRegisterName(Display *dpy, char_u *name);
void serverChangeRegisteredWindow(Display *dpy, Window newwin); void serverChangeRegisteredWindow(Display *dpy, Window newwin);
int serverSendToVim(Display *dpy, char_u *name, char_u *cmd, char_u **result, Window *server, int asExpr, int localLoop, int silent); int serverSendToVim(Display *dpy, char_u *name, char_u *cmd, char_u **result, Window *server, int asExpr, int timeout, int localLoop, int silent);
char_u *serverGetVimNames(Display *dpy); char_u *serverGetVimNames(Display *dpy);
Window serverStrToWin(char_u *str); Window serverStrToWin(char_u *str);
int serverSendReply(char_u *name, char_u *str); int serverSendReply(char_u *name, char_u *str);
int serverReadReply(Display *dpy, Window win, char_u **str, int localLoop); int serverReadReply(Display *dpy, Window win, char_u **str, int localLoop, int timeout);
int serverPeekReply(Display *dpy, Window win, char_u **str); int serverPeekReply(Display *dpy, Window win, char_u **str);
void serverEventProc(Display *dpy, XEvent *eventPtr, int immediate); void serverEventProc(Display *dpy, XEvent *eventPtr, int immediate);
void server_parse_messages(void); void server_parse_messages(void);

View File

@ -43,9 +43,9 @@ void serverInitMessaging(void);
void serverSetName(char_u *name); void serverSetName(char_u *name);
char_u *serverGetVimNames(void); char_u *serverGetVimNames(void);
int serverSendReply(char_u *name, char_u *reply); int serverSendReply(char_u *name, char_u *reply);
int serverSendToVim(char_u *name, char_u *cmd, char_u **result, void *ptarget, int asExpr, int silent); int serverSendToVim(char_u *name, char_u *cmd, char_u **result, void *ptarget, int asExpr, int timeout, int silent);
void serverForeground(char_u *name); void serverForeground(char_u *name);
char_u *serverGetReply(HWND server, int *expr_res, int remove, int wait); char_u *serverGetReply(HWND server, int *expr_res, int remove, int wait, int timeout);
void serverProcessPendingMessages(void); void serverProcessPendingMessages(void);
char *charset_id2name(int id); char *charset_id2name(int id);
char *quality_id2name(DWORD id); char *quality_id2name(DWORD id);

View File

@ -6,22 +6,12 @@ endif
source shared.vim source shared.vim
let s:where = 0
func Abort(id)
call assert_report('Test timed out at ' . s:where)
call FinishTesting()
endfunc
func Test_client_server() func Test_client_server()
let cmd = GetVimCommand() let cmd = GetVimCommand()
if cmd == '' if cmd == ''
return return
endif endif
" Some of these commands may hang when failing.
call timer_start(10000, 'Abort')
let s:where = 1
let name = 'XVIMTEST' let name = 'XVIMTEST'
let cmd .= ' --servername ' . name let cmd .= ' --servername ' . name
let g:job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'}) let g:job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
@ -30,62 +20,53 @@ func Test_client_server()
call assert_report('Cannot run the Vim server') call assert_report('Cannot run the Vim server')
return return
endif endif
let s:where = 2
" Takes a short while for the server to be active. " Takes a short while for the server to be active.
call WaitFor('serverlist() =~ "' . name . '"') call WaitFor('serverlist() =~ "' . name . '"')
call assert_match(name, serverlist()) call assert_match(name, serverlist())
let s:where = 3
call remote_foreground(name) call remote_foreground(name)
let s:where = 4
call remote_send(name, ":let testvar = 'yes'\<CR>") call remote_send(name, ":let testvar = 'yes'\<CR>")
let s:where = 5 call WaitFor('remote_expr("' . name . '", "testvar", "", 1) == "yes"')
call WaitFor('remote_expr("' . name . '", "testvar") == "yes"') call assert_equal('yes', remote_expr(name, "testvar", "", 2))
let s:where = 6
call assert_equal('yes', remote_expr(name, "testvar"))
let s:where = 7
if has('unix') && has('gui') && !has('gui_running') if has('unix') && has('gui') && !has('gui_running')
" Running in a terminal and the GUI is avaiable: Tell the server to open " Running in a terminal and the GUI is avaiable: Tell the server to open
" the GUI and check that the remote command still works. " the GUI and check that the remote command still works.
" Need to wait for the GUI to start up, otherwise the send hangs in trying " Need to wait for the GUI to start up, otherwise the send hangs in trying
" to send to the terminal window. " to send to the terminal window.
call remote_send(name, ":gui -f\<CR>") if has('gui_athena') || has('gui_motif')
let s:where = 8 " For those GUIs, ignore the 'failed to create input context' error.
sleep 500m call remote_send(name, ":call test_ignore_error('E285') | gui -f\<CR>")
else
call remote_send(name, ":gui -f\<CR>")
endif
" Wait for the server to be up and answering requests.
call WaitFor('remote_expr("' . name . '", "v:version", "", 1) != ""')
call remote_send(name, ":let testvar = 'maybe'\<CR>") call remote_send(name, ":let testvar = 'maybe'\<CR>")
let s:where = 9 call WaitFor('remote_expr("' . name . '", "testvar", "", 1) == "maybe"')
call WaitFor('remote_expr("' . name . '", "testvar") == "maybe"') call assert_equal('maybe', remote_expr(name, "testvar", "", 2))
let s:where = 10
call assert_equal('maybe', remote_expr(name, "testvar"))
let s:where = 11
endif endif
call assert_fails('call remote_send("XXX", ":let testvar = ''yes''\<CR>")', 'E241') call assert_fails('call remote_send("XXX", ":let testvar = ''yes''\<CR>")', 'E241')
let s:where = 12
" Expression evaluated locally. " Expression evaluated locally.
if v:servername == '' if v:servername == ''
call remote_startserver('MYSELF') call remote_startserver('MYSELF')
let s:where = 13 " May get MYSELF1 when running the test again.
call assert_equal('MYSELF', v:servername) call assert_match('MYSELF', v:servername)
endif endif
let g:testvar = 'myself' let g:testvar = 'myself'
call assert_equal('myself', remote_expr(v:servername, 'testvar')) call assert_equal('myself', remote_expr(v:servername, 'testvar'))
let s:where = 14
call remote_send(name, ":call server2client(expand('<client>'), 'got it')\<CR>", 'g:myserverid') call remote_send(name, ":call server2client(expand('<client>'), 'got it')\<CR>", 'g:myserverid')
let s:where = 15 call assert_equal('got it', remote_read(g:myserverid, 2))
call assert_equal('got it', remote_read(g:myserverid))
let s:where = 16
call remote_send(name, ":call server2client(expand('<client>'), 'another')\<CR>", 'g:myserverid') call remote_send(name, ":call server2client(expand('<client>'), 'another')\<CR>", 'g:myserverid')
let s:where = 151
let peek_result = 'nothing' let peek_result = 'nothing'
let r = remote_peek(g:myserverid, 'peek_result') let r = remote_peek(g:myserverid, 'peek_result')
let s:where = 161
" unpredictable whether the result is already avaialble. " unpredictable whether the result is already avaialble.
if r > 0 if r > 0
call assert_equal('another', peek_result) call assert_equal('another', peek_result)
@ -96,16 +77,11 @@ func Test_client_server()
endif endif
let g:peek_result = 'empty' let g:peek_result = 'empty'
call WaitFor('remote_peek(g:myserverid, "g:peek_result") > 0') call WaitFor('remote_peek(g:myserverid, "g:peek_result") > 0')
let s:where = 171
call assert_equal('another', g:peek_result) call assert_equal('another', g:peek_result)
let s:where = 181 call assert_equal('another', remote_read(g:myserverid, 2))
call assert_equal('another', remote_read(g:myserverid))
let s:where = 191
call remote_send(name, ":qa!\<CR>") call remote_send(name, ":qa!\<CR>")
let s:where = 17
call WaitFor('job_status(g:job) == "dead"') call WaitFor('job_status(g:job) == "dead"')
let s:where = 18
if job_status(g:job) != 'dead' if job_status(g:job) != 'dead'
call assert_report('Server did not exit') call assert_report('Server did not exit')
call job_stop(g:job, 'kill') call job_stop(g:job, 'kill')

View File

@ -764,6 +764,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
492,
/**/ /**/
491, 491,
/**/ /**/

View File

@ -2506,7 +2506,9 @@ typedef enum {
# define ELAPSED_INIT(v) v = GetTickCount() # define ELAPSED_INIT(v) v = GetTickCount()
# define ELAPSED_FUNC(v) elapsed(v) # define ELAPSED_FUNC(v) elapsed(v)
# define ELAPSED_TYPE DWORD # define ELAPSED_TYPE DWORD
long elapsed(DWORD start_tick); # ifndef PROTO
long elapsed(DWORD start_tick);
# endif
# endif # endif
#endif #endif