summaryrefslogtreecommitdiffstatshomepage
path: root/src
diff options
context:
space:
mode:
authorSanzhar Kuandyk <92693103+SanzharKuandyk@users.noreply.github.com>2026-04-21 21:49:16 +0500
committerGitHub <noreply@github.com>2026-04-21 12:49:16 -0400
commit5891f2f3dc41eda44c0072d726cf95e54aba85ad (patch)
treeea03f3d86655c9b24a1226ce305e62cbcadb26d5 /src
parentd99e01ca8064c7785702519f3192b8191386f717 (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.c57
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