MDEV-36283 "OpenEvent() failed" fatal error in mariadb-upgrade-service

The issue occurs when mariadb-upgrade-service shuts down the server it
started. There is a race condition where the server is online and
responsive to pings but has not yet created the named Windows shutdown
event.

This fix refactors the startup/shutdown logic to ensure startup waits for
the shutdown event. Additionally, mariadb-admin is unnecessary, as
waiting for the named pipe is already sufficient.
This commit is contained in:
Vladislav Vaintroub 2025-03-28 12:16:51 +01:00
parent 730dcf7e6d
commit fc60b89d0c

View File

@ -302,13 +302,29 @@ void stop_mysqld_service()
our --skip-grant-tables do not work anymore after mysql_upgrade
that does "flush privileges". Instead, the shutdown event is set.
*/
#define OPEN_EVENT_RETRY_SLEEP_MS 100
#define OPEN_EVENT_MAX_RETRIES 50
void initiate_mysqld_shutdown()
{
char event_name[32];
DWORD pid= GetProcessId(mysqld_process);
sprintf_s(event_name, "MySQLShutdown%d", pid);
HANDLE shutdown_handle= OpenEvent(EVENT_MODIFY_STATE, FALSE, event_name);
if(!shutdown_handle)
HANDLE shutdown_handle;
for (int i= 0;; i++)
{
shutdown_handle= OpenEvent(EVENT_MODIFY_STATE, FALSE, event_name);
if(shutdown_handle != nullptr || i == OPEN_EVENT_MAX_RETRIES)
break;
if (WaitForSingleObject(mysqld_process, OPEN_EVENT_RETRY_SLEEP_MS) !=
WAIT_TIMEOUT)
{
die("server process exited before shutdown event was created");
break;
}
}
if (!shutdown_handle)
{
die("OpenEvent() failed for shutdown event");
}
@ -403,6 +419,26 @@ static void change_service_config()
}
/**
Waits until starting server can be connected to, via given named pipe, with timeout
Dies if either server process exited meanwhile, or when timeout was exceeded.
*/
static void wait_for_server_startup(HANDLE process, const char *named_pipe, DWORD timeout_sec)
{
unsigned long long end_time= GetTickCount64() + 1000ULL*timeout_sec;
for (;;)
{
if (WaitNamedPipe(named_pipe, 0))
return;
if (GetTickCount64() >= end_time)
die("Server did not startup after %lu seconds", timeout_sec);
if (WaitForSingleObject(process, 100) != WAIT_TIMEOUT)
die("Server did not start");
}
}
int main(int argc, char **argv)
{
@ -482,6 +518,10 @@ int main(int argc, char **argv)
DWORD start_duration_ms = 0;
char pipe_name[64];
snprintf(pipe_name, sizeof(pipe_name),
"\\\\.\\pipe\\mysql_upgrade_service_%lu", GetCurrentProcessId());
if (do_start_stop_server)
{
/* Start/stop server with --loose-innodb-fast-shutdown=1 */
@ -493,37 +533,23 @@ int main(int argc, char **argv)
{
die("Cannot start mysqld.exe process, last error =%u", GetLastError());
}
char pipe_name[64];
snprintf(pipe_name, sizeof(pipe_name), "\\\\.\\pipe\\mysql_upgrade_service_%lu",
GetCurrentProcessId());
for (;;)
wait_for_server_startup(mysqld_process, pipe_name, startup_timeout);
// Server started, shut it down.
initiate_mysqld_shutdown();
if (WaitForSingleObject((HANDLE)mysqld_process, shutdown_timeout * 1000) != WAIT_OBJECT_0)
{
if (WaitForSingleObject(mysqld_process, 0) != WAIT_TIMEOUT)
die("mysqld.exe did not start");
if (WaitNamedPipe(pipe_name, 0))
{
// Server started, shut it down.
initiate_mysqld_shutdown();
if (WaitForSingleObject((HANDLE)mysqld_process, shutdown_timeout * 1000) != WAIT_OBJECT_0)
{
die("Could not shutdown server started with '--innodb-fast-shutdown=0'");
}
DWORD exit_code;
if (!GetExitCodeProcess((HANDLE)mysqld_process, &exit_code))
{
die("Could not get mysqld's exit code");
}
if (exit_code)
{
die("Could not get successfully shutdown mysqld");
}
CloseHandle(mysqld_process);
break;
}
Sleep(500);
start_duration_ms += 500;
die("Could not shutdown server");
}
DWORD exit_code;
if (!GetExitCodeProcess((HANDLE)mysqld_process, &exit_code))
{
die("Could not get server's exit code");
}
if (exit_code)
{
die("Could not get successfully shutdown server (exit code %u)",exit_code);
}
CloseHandle(mysqld_process);
}
log("Phase %d/%d: Fixing server config file%s", ++phase, max_phases,
@ -550,22 +576,7 @@ int main(int argc, char **argv)
}
log("Phase %d/%d: Waiting for startup to complete",++phase,max_phases);
start_duration_ms= 0;
for(;;)
{
if (WaitForSingleObject(mysqld_process, 0) != WAIT_TIMEOUT)
die("mysqld.exe did not start");
if (run_tool(P_WAIT, mysqladmin_path, "--protocol=pipe", socket_param,
"ping", "--no-beep", NULL) == 0)
{
break;
}
if (start_duration_ms > startup_timeout*1000)
die("Server did not come up in %d seconds",startup_timeout);
Sleep(500);
start_duration_ms+= 500;
}
wait_for_server_startup(mysqld_process, pipe_name, startup_timeout);
log("Phase %d/%d: Running mysql_upgrade",++phase,max_phases);
int upgrade_err= (int) run_tool(P_WAIT, mysqlupgrade_path,