mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 12:02:42 +01:00
27b064d882
The effect is reduced performance, at least on hybrid processor systems. To fix, turn off throttling power explicitly at server startup.
392 lines
11 KiB
C++
392 lines
11 KiB
C++
/* Copyright (C) 2020 MariaDB Corporation
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
|
|
*/
|
|
|
|
/*
|
|
main() function for the server on Windows is implemented here.
|
|
The core functionality is implemented elsewhere, in mysqld_main(), and running as
|
|
service is done here.
|
|
|
|
Main tasks of the service are
|
|
|
|
1. Report current status back to service control manager. Here we're
|
|
providing callbacks so code outside of winmain.cc can call it
|
|
(via mysqld_set_service_status_callback())
|
|
|
|
2. React to notification, the only one we care about is the "stop"
|
|
notification. we initiate shutdown, when instructed.
|
|
|
|
Note that our service might not be too Windows-friendly, as it might take
|
|
a while to startup (recovery), and a while to shut down(innodb cleanups).
|
|
|
|
Most of the code more of less standard service stuff, taken from Microsoft
|
|
docs examples.
|
|
|
|
Notable oddity in running services, is that we do not know for sure,
|
|
whether we should run as a service or not (there is no --service parameter that
|
|
would tell).Heuristics are used, and if the last command line argument is
|
|
valid service name, we try to run as service, but fallback to usual process
|
|
if this fails.
|
|
|
|
As an example, even if mysqld.exe is started with command line like "mysqld.exe --help",
|
|
it is entirely possible that mysqld.exe run as service "--help".
|
|
|
|
Apart from that, now deprecated and obsolete service registration/removal functionality is
|
|
still provided (mysqld.exe --install/--remove)
|
|
*/
|
|
|
|
#include <my_global.h>
|
|
#include <mysqld.h>
|
|
#include <log.h>
|
|
|
|
#include <stdio.h>
|
|
#include <windows.h>
|
|
#include <string>
|
|
#include <cassert>
|
|
|
|
static SERVICE_STATUS svc_status{SERVICE_WIN32_OWN_PROCESS};
|
|
static SERVICE_STATUS_HANDLE svc_status_handle;
|
|
static char *svc_name;
|
|
|
|
static char **save_argv;
|
|
static int save_argc;
|
|
|
|
static int install_service(int argc, char **argv, const char *name);
|
|
static int remove_service(const char *name);
|
|
|
|
/*
|
|
Report service status to SCM. This function is indirectly invoked
|
|
by the server to report state transitions.
|
|
|
|
1. from START_PENDING to SERVICE_RUNNING, when we start accepting user connections
|
|
2. from SERVICE_RUNNING to STOP_PENDING, when we start shutdown
|
|
3. from STOP_PENDING to SERVICE_STOPPED, in mysqld_exit()
|
|
sometimes also START_PENDING to SERVICE_STOPPED, on startup errors
|
|
*/
|
|
static void report_svc_status(DWORD current_state, DWORD exit_code, DWORD wait_hint)
|
|
{
|
|
if (!svc_status_handle)
|
|
return;
|
|
|
|
static DWORD check_point= 1;
|
|
if (current_state != (DWORD)-1)
|
|
svc_status.dwCurrentState= current_state;
|
|
svc_status.dwWaitHint= wait_hint;
|
|
|
|
if (exit_code)
|
|
{
|
|
svc_status.dwWin32ExitCode= ERROR_SERVICE_SPECIFIC_ERROR;
|
|
svc_status.dwServiceSpecificExitCode= exit_code;
|
|
}
|
|
else
|
|
{
|
|
svc_status.dwWin32ExitCode= 0;
|
|
}
|
|
|
|
if (current_state == SERVICE_START_PENDING)
|
|
svc_status.dwControlsAccepted= 0;
|
|
else
|
|
svc_status.dwControlsAccepted= SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN;
|
|
|
|
if ((current_state == SERVICE_RUNNING) || (current_state == SERVICE_STOPPED))
|
|
svc_status.dwCheckPoint= 0;
|
|
else
|
|
svc_status.dwCheckPoint= check_point++;
|
|
|
|
SetServiceStatus(svc_status_handle, &svc_status);
|
|
}
|
|
|
|
/* Report unexpected errors. */
|
|
static void svc_report_event(const char *svc_name, const char *command)
|
|
{
|
|
char buffer[80];
|
|
sprintf_s(buffer, "mariadb service %s, %s failed with %d",
|
|
svc_name, command, GetLastError());
|
|
OutputDebugString(buffer);
|
|
}
|
|
|
|
/*
|
|
Service control function.
|
|
Reacts to service stop, initiates shutdown.
|
|
*/
|
|
static void WINAPI svc_ctrl_handle(DWORD cntrl)
|
|
{
|
|
switch (cntrl)
|
|
{
|
|
case SERVICE_CONTROL_SHUTDOWN:
|
|
case SERVICE_CONTROL_STOP:
|
|
sql_print_information(
|
|
"Windows service \"%s\": received %s",
|
|
svc_name,
|
|
cntrl == SERVICE_CONTROL_STOP? "SERVICE_CONTROL_STOP": "SERVICE_CONTROL_SHUTDOWN");
|
|
|
|
/* The below will also set the status to STOP_PENDING. */
|
|
mysqld_win_initiate_shutdown();
|
|
break;
|
|
|
|
case SERVICE_CONTROL_INTERROGATE:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Service main routine, mainly runs mysqld_main() */
|
|
static void WINAPI svc_main(DWORD svc_argc, char **svc_argv)
|
|
{
|
|
/* Register the handler function for the service */
|
|
char *name= svc_argv[0];
|
|
|
|
svc_status_handle= RegisterServiceCtrlHandler(name, svc_ctrl_handle);
|
|
if (!svc_status_handle)
|
|
{
|
|
svc_report_event(name, "RegisterServiceCtrlHandler");
|
|
return;
|
|
}
|
|
report_svc_status(SERVICE_START_PENDING, NO_ERROR, 0);
|
|
|
|
/* Make server report service status via our callback.*/
|
|
mysqld_set_service_status_callback(report_svc_status);
|
|
|
|
/* This would add service name entry to load_defaults.*/
|
|
mysqld_win_set_service_name(name);
|
|
|
|
/*
|
|
Do not pass the service name parameter (last on the command line)
|
|
to mysqld_main(), it is unaware of it.
|
|
*/
|
|
save_argv[save_argc - 1]= 0;
|
|
mysqld_main(save_argc - 1, save_argv);
|
|
}
|
|
|
|
/*
|
|
This start the service. Sometimes it will fail, because
|
|
currently we do not know for sure whether we run as service or not.
|
|
If this fails, the fallback is to run as normal process.
|
|
*/
|
|
static int run_as_service(char *name)
|
|
{
|
|
SERVICE_TABLE_ENTRY stb[]= {{name, svc_main}, {0, 0}};
|
|
if (!StartServiceCtrlDispatcher(stb))
|
|
{
|
|
assert(GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
Check for valid existing service name.
|
|
Part of our guesswork, whether we run as service or not.
|
|
*/
|
|
static bool is_existing_service(const char *name)
|
|
{
|
|
if (strchr(name, '\\') || strchr(name, '/'))
|
|
{
|
|
/* Invalid characters in service name */
|
|
return false;
|
|
}
|
|
|
|
SC_HANDLE sc_service= 0, scm= 0;
|
|
bool ret= ((scm= OpenSCManager(0, 0, SC_MANAGER_ENUMERATE_SERVICE)) != 0) &&
|
|
((sc_service= OpenService(scm, name, SERVICE_QUERY_STATUS)) != 0);
|
|
|
|
if (sc_service)
|
|
CloseServiceHandle(sc_service);
|
|
if (scm)
|
|
CloseServiceHandle(scm);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
If service name is not given to --install/--remove
|
|
it is assumed to be "MySQL" (traditional handling)
|
|
*/
|
|
static const char *get_svc_name(const char *arg)
|
|
{
|
|
return arg ? arg : "MySQL";
|
|
}
|
|
|
|
/*
|
|
Disable CPU throttling for the process.
|
|
|
|
Windows 11 heuristics misdetects server as a background process and runs it
|
|
on "efficiency" cores, in hybrid architectures such as Alder Lake (12th
|
|
generation Intel Core).This results in serious performance degradation.
|
|
*/
|
|
void disable_cpu_throttling()
|
|
{
|
|
#ifdef PROCESS_POWER_THROTTLING_EXECUTION_SPEED
|
|
PROCESS_POWER_THROTTLING_STATE power_throttling{};
|
|
power_throttling.Version= PROCESS_POWER_THROTTLING_CURRENT_VERSION;
|
|
power_throttling.ControlMask= PROCESS_POWER_THROTTLING_EXECUTION_SPEED;
|
|
power_throttling.StateMask= 0;
|
|
SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling,
|
|
&power_throttling, sizeof(power_throttling));
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
Main function on Windows.
|
|
Runs mysqld as normal process, or as a service.
|
|
|
|
Plus, the obsolete functionality to register/remove services.
|
|
*/
|
|
__declspec(dllexport) int mysqld_win_main(int argc, char **argv)
|
|
{
|
|
save_argv= argv;
|
|
save_argc= argc;
|
|
|
|
disable_cpu_throttling();
|
|
/*
|
|
If no special arguments are given, service name is nor present
|
|
run as normal program.
|
|
*/
|
|
if (argc == 1)
|
|
return mysqld_main(argc, argv);
|
|
|
|
auto cmd= argv[1];
|
|
|
|
/* Handle install/remove */
|
|
if (!strcmp(cmd, "--install") || !strcmp(cmd, "--install-manual"))
|
|
return install_service(argc, argv, get_svc_name(argv[2]));
|
|
|
|
if (!strcmp(cmd, "--remove"))
|
|
return remove_service(get_svc_name(argv[2]));
|
|
|
|
/* Try to run as service, and fallback to mysqld_main(), if this fails */
|
|
svc_name= argv[argc - 1];
|
|
if (is_existing_service(svc_name) && !run_as_service(svc_name))
|
|
return 0;
|
|
svc_name= 0;
|
|
|
|
/* Run as normal program.*/
|
|
return mysqld_main(argc, argv);
|
|
}
|
|
|
|
|
|
/*
|
|
Register/remove services functionality.
|
|
This is kept for backward compatibility only, and is
|
|
superseeded by much more versatile mysql_install_db.exe
|
|
|
|
"mysqld --remove=svc" has no advantage over
|
|
OS own "sc delete svc"
|
|
*/
|
|
static void ATTRIBUTE_NORETURN die(const char *func, const char *name)
|
|
{
|
|
DWORD err= GetLastError();
|
|
fprintf(stderr, "FATAL ERROR : %s failed (%lu)\n", func, err);
|
|
switch (err)
|
|
{
|
|
case ERROR_SERVICE_EXISTS:
|
|
fprintf(stderr, "Service %s already exists.\n", name);
|
|
break;
|
|
case ERROR_SERVICE_DOES_NOT_EXIST:
|
|
fprintf(stderr, "Service %s does not exist.\n", name);
|
|
break;
|
|
case ERROR_ACCESS_DENIED:
|
|
fprintf(stderr, "Access is denied. "
|
|
"Make sure to run as elevated admin user.\n");
|
|
break;
|
|
case ERROR_INVALID_NAME:
|
|
fprintf(stderr, "Invalid service name '%s'\n", name);
|
|
default:
|
|
break;
|
|
}
|
|
exit(1);
|
|
}
|
|
|
|
static inline std::string quoted(const char *src)
|
|
{
|
|
std::string s;
|
|
s.append("\"").append(src).append("\"");
|
|
return s;
|
|
}
|
|
|
|
static int install_service(int argc, char **argv, const char *name)
|
|
{
|
|
std::string cmdline;
|
|
|
|
char path[MAX_PATH];
|
|
auto nSize = GetModuleFileName(0, path, sizeof(path));
|
|
|
|
if (nSize == (DWORD) sizeof(path) && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
|
die("GetModuleName", name);
|
|
|
|
cmdline.append(quoted(path));
|
|
|
|
const char *user= 0;
|
|
// mysqld --install[-manual] name ...[--local-service]
|
|
if (argc > 2)
|
|
{
|
|
for (int i= 3; argv[i]; i++)
|
|
{
|
|
if (!strcmp(argv[i], "--local-service"))
|
|
user= "NT AUTHORITY\\LocalService";
|
|
else
|
|
{
|
|
cmdline.append(" ").append(quoted(argv[i]));
|
|
}
|
|
}
|
|
}
|
|
cmdline.append(" ").append(quoted(name));
|
|
|
|
DWORD start_type;
|
|
if (!strcmp(argv[1], "--install-manual"))
|
|
start_type= SERVICE_DEMAND_START;
|
|
else
|
|
start_type= SERVICE_AUTO_START;
|
|
|
|
SC_HANDLE scm, sc_service;
|
|
if (!(scm= OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE)))
|
|
die("OpenSCManager", name);
|
|
|
|
if (!(sc_service= CreateService(
|
|
scm, name, name, SERVICE_ALL_ACCESS,
|
|
SERVICE_WIN32_OWN_PROCESS, start_type, SERVICE_ERROR_NORMAL,
|
|
cmdline.c_str(), 0, 0, 0, user, 0)))
|
|
die("CreateService", name);
|
|
|
|
char description[]= "MariaDB database server";
|
|
SERVICE_DESCRIPTION sd= {description};
|
|
ChangeServiceConfig2(sc_service, SERVICE_CONFIG_DESCRIPTION, &sd);
|
|
|
|
CloseServiceHandle(sc_service);
|
|
CloseServiceHandle(scm);
|
|
|
|
printf("Service '%s' successfully installed.\n", name);
|
|
return 0;
|
|
}
|
|
|
|
static int remove_service(const char *name)
|
|
{
|
|
SC_HANDLE scm, sc_service;
|
|
|
|
if (!(scm= OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE)))
|
|
die("OpenSCManager", name);
|
|
|
|
if (!(sc_service= OpenService(scm, name, DELETE)))
|
|
die("OpenService", name);
|
|
|
|
if (!DeleteService(sc_service))
|
|
die("DeleteService", name);
|
|
|
|
CloseServiceHandle(sc_service);
|
|
CloseServiceHandle(scm);
|
|
|
|
printf("Service '%s' successfully deleted.\n", name);
|
|
return 0;
|
|
}
|