diff options
| author | Sanzhar Kuandyk <92693103+SanzharKuandyk@users.noreply.github.com> | 2026-04-21 21:49:16 +0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-21 12:49:16 -0400 |
| commit | 5891f2f3dc41eda44c0072d726cf95e54aba85ad (patch) | |
| tree | ea03f3d86655c9b24a1226ce305e62cbcadb26d5 /src | |
| parent | d99e01ca8064c7785702519f3192b8191386f717 (diff) | |
fix(:restart): reuse --listen addr on Windows #38539
Problem:
On Windows, :restart cannot immediately reuse the canonical --listen
address because named pipe release is asynchronous.
Solution:
Start the new Nvim server on a temporary address; in the new Nvim,
retry serverstart() with the original ("canonical") address until it
succeeds.
Diffstat (limited to 'src')
| -rw-r--r-- | src/nvim/ex_docmd.c | 57 |
1 files changed, 50 insertions, 7 deletions
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 96a27bdf4b..79c53b2900 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4977,8 +4977,10 @@ static void ex_restart(exarg_T *eap) char **argv = xcalloc((size_t)argc + 3, sizeof(char *)); size_t i = 0; const char *listen_arg = NULL; -#ifdef MSWIN // FIXME: --listen doesn't work on Windows and needs to be dropped -# define HANDLE_LISTEN_ADDR li = next_li; continue +#ifdef MSWIN + // On Windows, don't pass --listen to new server (named pipe can't be reused immediately). + // Instead pass the address via RPC, and new server will rebind to it after startup. +# define HANDLE_LISTEN_ADDR listen_arg = addr; li = next_li; continue #else # define HANDLE_LISTEN_ADDR listen_arg = addr #endif @@ -5020,11 +5022,27 @@ static void ex_restart(exarg_T *eap) }); #undef HANDLE_LISTEN_ADDR - bool server_stopped = false; - if (listen_arg != NULL) { - // Stop listening on the --listen address so that the new server can listen. - server_stopped = server_stop(listen_arg, true); +#ifdef MSWIN + // On Windows, --listen is omitted from child argv because the named pipe can't be reused immediately. + // Recover the canonical address from the Lua module state (set by the previous rebind_old_addr_after_restart() call), + // and keep the current listener alive (new server reclaims it). + char *listen_arg_alloc = NULL; + if (listen_arg == NULL) { + Error lua_err = ERROR_INIT; + Object rv = NLUA_EXEC_STATIC("return require('vim._core.server').restart_canonical_addr", + (Array)ARRAY_DICT_INIT, kRetObject, NULL, &lua_err); + if (!ERROR_SET(&lua_err) && rv.type == kObjectTypeString && rv.data.string.size > 0) { + listen_arg_alloc = xstrdup(rv.data.string.data); + listen_arg = listen_arg_alloc; + } + api_free_object(rv); + api_clear_error(&lua_err); } + bool server_stopped = false; +#else + // Stop listening on the --listen address so that the new server can listen. + bool server_stopped = listen_arg ? server_stop(listen_arg, true) : false; +#endif #ifdef MSWIN bool restart_alloc_console_env = false; @@ -5086,7 +5104,8 @@ static void ex_restart(exarg_T *eap) result_mem = NULL; } - // Get new server's listen address. + // Get the new server's initial listen address. On Windows this is the + // temporary bootstrap address that UIs should reconnect to first. MAXSIZE_TEMP_ARRAY(servername_args, 1); ADD_C(servername_args, CSTR_AS_OBJ("servername")); Object result = rpc_send_call(channel->id, "nvim_get_vvar", servername_args, &result_mem, &err); @@ -5101,6 +5120,27 @@ static void ex_restart(exarg_T *eap) arena_mem_free(result_mem); result_mem = NULL; +#ifdef MSWIN + if (listen_arg != NULL) { + // Tell the new server to reclaim the canonical --listen address once the old listener exits, + // then retire the bootstrap address after all UIs have reattached (or timeout). + MAXSIZE_TEMP_ARRAY(lua_args, 2); + ADD_C(lua_args, + CSTR_AS_OBJ("return require('vim._core.server').rebind_old_addr_after_restart(...)")); + MAXSIZE_TEMP_ARRAY(handoff_params, 3); + ADD_C(handoff_params, CSTR_AS_OBJ(listen_arg)); + ADD_C(handoff_params, CSTR_AS_OBJ(listen_addr)); + ADD_C(handoff_params, INTEGER_OBJ((Integer)ui_active())); + ADD_C(lua_args, ARRAY_OBJ(handoff_params)); + rpc_send_call(channel->id, "nvim_exec_lua", lua_args, &result_mem, &err); + if (ERROR_SET(&err)) { + goto fail_2; + } + arena_mem_free(result_mem); + result_mem = NULL; + } +#endif + // Send restart event with new listen address to all UIs. ui_call_restart(cstr_as_string(listen_addr)); ui_flush(); @@ -5152,6 +5192,9 @@ fail_1: if (server_stopped && server_start(listen_arg) != 0) { semsg("couldn't resume listening on %s", listen_arg); } +#ifdef MSWIN + xfree(listen_arg_alloc); +#endif } /// ":close": close current window, unless it is the last one |
