From 6a19f2a024b4cede80e2896318696008d1dd1b21 Mon Sep 17 00:00:00 2001 From: Stefan Kangas Date: Tue, 6 Sep 2022 02:05:18 +0200 Subject: [PATCH] Add new --timeout flag to emacsclient * lib-src/emacsclient.c (DEFAULT_TIMEOUT): New constant. (timeout): New static variable. (longopts, shortopts, decode_options, print_help_and_exit): Add new flag --timeout. (set_socket_timeout, check_socket_timeout): New helper functions. (main): Display a status message or exit after Emacs has not responded for a while, depending on above new --timeout flag. (Bug#50849) * doc/emacs/misc.texi (emacsclient Options): * doc/man/emacsclient.1: Document the above new option. * etc/NEWS: Announce it. --- doc/emacs/misc.texi | 7 ++++ doc/man/emacsclient.1 | 11 ++++--- etc/NEWS | 7 +++- lib-src/emacsclient.c | 75 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 92 insertions(+), 8 deletions(-) diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi index df74577592a..d8ad0bee34f 100644 --- a/doc/emacs/misc.texi +++ b/doc/emacs/misc.texi @@ -2089,6 +2089,13 @@ all server buffers are finished. You can take as long as you like to edit the server buffers within Emacs, and they are @emph{not} killed when you type @kbd{C-x #} in them. +@item -w +@itemx --timeout=@var{N} +Wait for a response from Emacs for @var{N} seconds before giving up. +If there is no response within that time, @command{emacsclient} will +display a warning and exit. The default is @samp{0}, which means to +wait forever. + @item --parent-id @var{id} Open an @command{emacsclient} frame as a client frame in the parent X window with id @var{id}, via the XEmbed protocol. Currently, this diff --git a/doc/man/emacsclient.1 b/doc/man/emacsclient.1 index e5d1bbe09ae..83c8a366f8b 100644 --- a/doc/man/emacsclient.1 +++ b/doc/man/emacsclient.1 @@ -1,5 +1,5 @@ .\" See section COPYING for conditions for redistribution. -.TH EMACSCLIENT 1 "2021-11-05" "GNU Emacs" "GNU" +.TH EMACSCLIENT 1 "2022-09-05" "GNU Emacs" "GNU" .\" NAME should be all caps, SECTION should be 1-8, maybe w/ subsection .\" other params are allowed: see man(7), man(1) .SH NAME @@ -87,9 +87,12 @@ Use TCP configuration file FILENAME for communication. This can also be specified via the EMACS_SERVER_FILE environment variable. .TP .B \-n, \-\-no-wait -Return -immediately without waiting for you to "finish" the buffer in Emacs. -If combined with --eval, this option is ignored. +Return immediately without waiting for you to "finish" the buffer in +Emacs. If combined with --eval, this option is ignored. +.TP +.B \-w, \-\-timeout=N +How long to wait, in seconds, for Emacs to respond before giving up. +The default is 0, which means to wait forever. .TP .B \-nw, \-t, \-\-tty Open a new Emacs frame on the current terminal. diff --git a/etc/NEWS b/etc/NEWS index e99c2f21982..b61b88d6fbe 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1003,10 +1003,15 @@ suspicious and could be malicious. ** Emacs server and client changes +++ -*** New command-line option '-r' for emacsclient. +*** New command-line option '-r'/'--reuse-frame' for emacsclient. With this command-line option, Emacs reuses an existing graphical client frame if one exists; otherwise it creates a new frame. ++++ +*** New command-line option '-w N'/'--timeout=N' for emacsclient. +With this command-line option, emacsclient will exit if Emacs does not +respond within N seconds. The default is to wait forever. + +++ *** 'server-stop-automatically' can be used to automatically stop the server. The Emacs server will be automatically stopped when certain conditions diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index 73c8e45a865..15acb4589a9 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -1,6 +1,6 @@ /* Client process that communicates with GNU Emacs acting as server. -Copyright (C) 1986-1987, 1994, 1999-2022 Free Software Foundation, Inc. +Copyright (C) 1986-2022 Free Software Foundation, Inc. This file is part of GNU Emacs. @@ -55,6 +55,8 @@ char *w32_getenv (const char *); # include # include +# define DEFAULT_TIMEOUT (30) + # define SOCKETS_IN_FILE_SYSTEM # define INVALID_SOCKET (-1) @@ -144,6 +146,9 @@ static char const *socket_name; /* If non-NULL, the filename of the authentication file. */ static char const *server_file; +/* Seconds to wait before timing out (0 means wait forever). */ +static uintmax_t timeout; + /* If non-NULL, the tramp prefix emacs must use to find the files. */ static char const *tramp_prefix; @@ -178,6 +183,7 @@ static struct option const longopts[] = { "server-file", required_argument, NULL, 'f' }, { "display", required_argument, NULL, 'd' }, { "parent-id", required_argument, NULL, 'p' }, + { "timeout", required_argument, NULL, 'w' }, { "tramp", required_argument, NULL, 'T' }, { 0, 0, 0, 0 } }; @@ -185,7 +191,7 @@ static struct option const longopts[] = /* Short options, in the same order as the corresponding long options. There is no '-p' short option. */ static char const shortopts[] = - "nqueHVtca:F:" + "nqueHVtca:F:w:" #ifdef SOCKETS_IN_FILE_SYSTEM "s:" #endif @@ -497,6 +503,7 @@ decode_options (int argc, char **argv) if (opt < 0) break; + char* endptr; switch (opt) { case 0: @@ -530,6 +537,17 @@ decode_options (int argc, char **argv) nowait = true; break; + case 'w': + timeout = strtoumax (optarg, &endptr, 10); + if (timeout <= 0 || + ((timeout == INTMAX_MAX || timeout == INTMAX_MIN) + && errno == ERANGE)) + { + fprintf (stderr, "Invalid timeout: \"%s\"\n", optarg); + exit (EXIT_FAILURE); + } + break; + case 'e': eval = true; break; @@ -671,6 +689,7 @@ The following OPTIONS are accepted:\n\ Set the parameters of a new frame\n\ -e, --eval Evaluate the FILE arguments as ELisp expressions\n\ -n, --no-wait Don't wait for the server to return\n\ +-w, --timeout Seconds to wait before timing out\n\ -q, --quiet Don't display messages on success\n\ -u, --suppress-output Don't display return values from the server\n\ -d DISPLAY, --display=DISPLAY\n\ @@ -1870,6 +1889,33 @@ start_daemon_and_retry_set_socket (void) return emacs_socket; } +static void +set_socket_timeout (HSOCKET socket, int seconds) +{ +#ifndef WINDOWSNT + struct timeval timeout; + timeout.tv_sec = seconds; + timeout.tv_usec = 0; + setsockopt (socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout); +#else + DWORD timeout = seconds * 1000; + setsockopt (socket, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof timeout); +#endif +} + +static bool +check_socket_timeout (int rl) +{ +#ifndef WINDOWSNT + return (rl == -1) + && (errno == EAGAIN) + && (errno == EWOULDBLOCK); +#else + return (rl == SOCKET_ERROR) + && (WSAGetLastError() == WSAETIMEDOUT); +#endif +} + int main (int argc, char **argv) { @@ -2086,19 +2132,42 @@ main (int argc, char **argv) } fflush (stdout); + set_socket_timeout (emacs_socket, timeout > 0 ? timeout : DEFAULT_TIMEOUT); + bool saw_response = false; /* Now, wait for an answer and print any messages. */ while (exit_status == EXIT_SUCCESS) { + bool retry = true; + bool msg_showed = quiet; do { act_on_signals (emacs_socket); rl = recv (emacs_socket, string, BUFSIZ, 0); + retry = check_socket_timeout (rl); + if (retry) + { + if (timeout > 0 && !saw_response) + { + /* Don't retry if we were given a --timeout flag. */ + fprintf (stderr, "\nServer not responding; timed out after %lu seconds", + timeout); + retry = false; + } + else if (!msg_showed) + { + msg_showed = true; + fprintf (stderr, "\nServer not responding; use Ctrl+C to break"); + } + } } - while (rl < 0 && errno == EINTR); + while ((rl < 0 && errno == EINTR) || retry); if (rl <= 0) break; + if (msg_showed) + fprintf (stderr, "\nGot response from server"); + saw_response = true; string[rl] = '\0'; /* Loop over all NL-terminated messages. */ -- 2.39.2