4 SERVICE_STATUS service_status;
\r
5 SERVICE_STATUS_HANDLE service_handle;
\r
6 HANDLE process_handle;
\r
9 static char service_name[SERVICE_NAME_LENGTH];
\r
10 char exe[EXE_LENGTH];
\r
11 char flags[CMD_LENGTH];
\r
15 unsigned long throttle_delay;
\r
16 unsigned long stop_method;
\r
17 CRITICAL_SECTION throttle_section;
\r
18 CONDITION_VARIABLE throttle_condition;
\r
19 HANDLE throttle_timer;
\r
20 LARGE_INTEGER throttle_duetime;
\r
21 bool use_critical_section;
\r
22 FILETIME creation_time;
\r
24 extern imports_t imports;
\r
26 static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY, NSSM_EXIT_UNCLEAN } exit_actions;
\r
27 static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };
\r
29 static unsigned long throttle;
\r
31 static inline int throttle_milliseconds() {
\r
32 /* pow() operates on doubles. */
\r
33 int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;
\r
37 /* Connect to the service manager */
\r
38 SC_HANDLE open_service_manager() {
\r
39 SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
\r
41 if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);
\r
48 /* About to install the service */
\r
49 int pre_install_service(int argc, char **argv) {
\r
50 /* Show the dialogue box if we didn't give the service name and path */
\r
51 if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);
\r
53 /* Arguments are optional */
\r
55 size_t flagslen = 0;
\r
58 for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;
\r
59 if (! flagslen) flagslen = 1;
\r
61 flags = (char *) HeapAlloc(GetProcessHeap(), 0, flagslen);
\r
63 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "flags", "pre_install_service()", 0);
\r
66 ZeroMemory(flags, flagslen);
\r
69 This probably isn't UTF8-safe and should use std::string or something
\r
70 but it's been broken for the best part of a decade and due for a rewrite
\r
71 anyway so it'll do as a quick-'n'-dirty fix. Note that we don't free
\r
72 the flags buffer but as the program exits that isn't a big problem.
\r
74 for (i = 2; i < argc; i++) {
\r
75 size_t len = strlen(argv[i]);
\r
76 memmove(flags + s, argv[i], len);
\r
78 if (i < argc - 1) flags[s++] = ' ';
\r
81 return install_service(argv[0], argv[1], flags);
\r
84 /* About to remove the service */
\r
85 int pre_remove_service(int argc, char **argv) {
\r
86 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
87 if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);
\r
88 if (str_equiv(argv[1], "confirm")) return remove_service(argv[0]);
\r
89 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
93 /* Install the service */
\r
94 int install_service(char *name, char *exe, char *flags) {
\r
95 /* Open service manager */
\r
96 SC_HANDLE services = open_service_manager();
\r
98 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
102 /* Get path of this program */
\r
103 char path[MAX_PATH];
\r
104 GetModuleFileName(0, path, MAX_PATH);
\r
106 /* Construct command */
\r
107 char command[CMD_LENGTH];
\r
108 size_t pathlen = strlen(path);
\r
109 if (pathlen + 1 >= VALUE_LENGTH) {
\r
110 print_message(stderr, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);
\r
113 if (_snprintf_s(command, sizeof(command), _TRUNCATE, "\"%s\"", path) < 0) {
\r
114 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY_FOR_IMAGEPATH);
\r
118 /* Work out directory name */
\r
119 size_t len = strlen(exe);
\r
121 for (i = len; i && exe[i] != '\\' && exe[i] != '/'; i--);
\r
122 char dir[MAX_PATH];
\r
123 memmove(dir, exe, i);
\r
126 /* Create the service */
\r
127 SC_HANDLE service = CreateService(services, name, name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, command, 0, 0, 0, 0, 0);
\r
129 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
130 CloseServiceHandle(services);
\r
134 /* Now we need to put the parameters into the registry */
\r
135 if (create_parameters(name, exe, flags, dir)) {
\r
136 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
137 DeleteService(service);
\r
138 CloseServiceHandle(services);
\r
142 set_service_recovery(service, name);
\r
145 CloseServiceHandle(service);
\r
146 CloseServiceHandle(services);
\r
148 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, name);
\r
152 /* Remove the service */
\r
153 int remove_service(char *name) {
\r
154 /* Open service manager */
\r
155 SC_HANDLE services = open_service_manager();
\r
157 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
161 /* Try to open the service */
\r
162 SC_HANDLE service = OpenService(services, name, SC_MANAGER_ALL_ACCESS);
\r
164 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
165 CloseServiceHandle(services);
\r
169 /* Try to delete the service */
\r
170 if (! DeleteService(service)) {
\r
171 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
172 CloseServiceHandle(service);
\r
173 CloseServiceHandle(services);
\r
178 CloseServiceHandle(service);
\r
179 CloseServiceHandle(services);
\r
181 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, name);
\r
185 /* Service initialisation */
\r
186 void WINAPI service_main(unsigned long argc, char **argv) {
\r
187 if (_snprintf_s(service_name, sizeof(service_name), _TRUNCATE, "%s", argv[0]) < 0) {
\r
188 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service_name", "service_main()", 0);
\r
192 /* We can use a condition variable in a critical section on Vista or later. */
\r
193 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
194 else use_critical_section = false;
\r
196 /* Initialise status */
\r
197 ZeroMemory(&service_status, sizeof(service_status));
\r
198 service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
199 service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
200 service_status.dwWin32ExitCode = NO_ERROR;
\r
201 service_status.dwServiceSpecificExitCode = 0;
\r
202 service_status.dwCheckPoint = 0;
\r
203 service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
205 /* Signal we AREN'T running the server */
\r
206 process_handle = 0;
\r
209 /* Register control handler */
\r
210 service_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, 0);
\r
211 if (! service_handle) {
\r
212 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
216 log_service_control(service_name, 0, true);
\r
218 service_status.dwCurrentState = SERVICE_START_PENDING;
\r
219 service_status.dwWaitHint = throttle_delay + NSSM_WAITHINT_MARGIN;
\r
220 SetServiceStatus(service_handle, &service_status);
\r
223 /* Try to create the exit action parameters; we don't care if it fails */
\r
224 create_exit_action(argv[0], exit_action_strings[0]);
\r
226 set_service_recovery(0, service_name);
\r
229 /* Used for signalling a resume if the service pauses when throttled. */
\r
230 if (use_critical_section) InitializeCriticalSection(&throttle_section);
\r
232 throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
233 if (! throttle_timer) {
\r
234 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service_name, error_string(GetLastError()), 0);
\r
241 /* Make sure service recovery actions are taken where necessary */
\r
242 void set_service_recovery(SC_HANDLE service, char *service_name) {
\r
243 SC_HANDLE services = 0;
\r
246 services = open_service_manager();
\r
247 if (! services) return;
\r
249 service = OpenService(services, service_name, SC_MANAGER_ALL_ACCESS);
\r
250 if (! service) return;
\r
253 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
254 ZeroMemory(&flag, sizeof(flag));
\r
255 flag.fFailureActionsOnNonCrashFailures = true;
\r
257 /* This functionality was added in Vista so the call may fail */
\r
258 if (! ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
259 unsigned long error = GetLastError();
\r
260 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
261 if (error != ERROR_INVALID_LEVEL) {
\r
262 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service_name, error_string(error), 0);
\r
267 CloseServiceHandle(service);
\r
268 CloseServiceHandle(services);
\r
272 int monitor_service() {
\r
273 /* Set service status to started */
\r
274 int ret = start_service();
\r
277 _snprintf_s(code, sizeof(code), _TRUNCATE, "%d", ret);
\r
278 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, exe, service_name, ret, 0);
\r
281 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);
\r
283 /* Monitor service service */
\r
284 if (! RegisterWaitForSingleObject(&wait_handle, process_handle, end_service, (void *) pid, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
285 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service_name, exe, error_string(GetLastError()), 0);
\r
291 char *service_control_text(unsigned long control) {
\r
293 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
294 case 0: return "START";
\r
295 case SERVICE_CONTROL_STOP: return "STOP";
\r
296 case SERVICE_CONTROL_SHUTDOWN: return "SHUTDOWN";
\r
297 case SERVICE_CONTROL_PAUSE: return "PAUSE";
\r
298 case SERVICE_CONTROL_CONTINUE: return "CONTINUE";
\r
299 case SERVICE_CONTROL_INTERROGATE: return "INTERROGATE";
\r
304 void log_service_control(char *service_name, unsigned long control, bool handled) {
\r
305 char *text = service_control_text(control);
\r
306 unsigned long event;
\r
309 /* "0x" + 8 x hex + NULL */
\r
310 text = (char *) HeapAlloc(GetProcessHeap(), 0, 11);
\r
312 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control", 0);
\r
315 if (_snprintf_s(text, 11, _TRUNCATE, "0x%08x", control) < 0) {
\r
316 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control", 0);
\r
317 HeapFree(GetProcessHeap(), 0, text);
\r
321 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
323 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
324 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
326 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
328 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
329 HeapFree(GetProcessHeap(), 0, text);
\r
333 /* Service control handler */
\r
334 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
336 case SERVICE_CONTROL_INTERROGATE:
\r
337 /* We always keep the service status up-to-date so this is a no-op. */
\r
340 case SERVICE_CONTROL_SHUTDOWN:
\r
341 case SERVICE_CONTROL_STOP:
\r
342 log_service_control(service_name, control, true);
\r
343 stop_service(0, true, true);
\r
346 case SERVICE_CONTROL_CONTINUE:
\r
347 log_service_control(service_name, control, true);
\r
349 if (use_critical_section) imports.WakeConditionVariable(&throttle_condition);
\r
351 if (! throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
352 ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));
\r
353 SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);
\r
355 service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
356 service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;
\r
357 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);
\r
358 SetServiceStatus(service_handle, &service_status);
\r
361 case SERVICE_CONTROL_PAUSE:
\r
363 We don't accept pause messages but it isn't possible to register
\r
364 only for continue messages so we have to handle this case.
\r
366 log_service_control(service_name, control, false);
\r
367 return ERROR_CALL_NOT_IMPLEMENTED;
\r
370 /* Unknown control */
\r
371 log_service_control(service_name, control, false);
\r
372 return ERROR_CALL_NOT_IMPLEMENTED;
\r
375 /* Start the service */
\r
376 int start_service() {
\r
378 allow_restart = true;
\r
380 if (process_handle) return 0;
\r
382 /* Allocate a STARTUPINFO structure for a new process */
\r
384 ZeroMemory(&si, sizeof(si));
\r
385 si.cb = sizeof(si);
\r
387 /* Allocate a PROCESSINFO structure for the process */
\r
388 PROCESS_INFORMATION pi;
\r
389 ZeroMemory(&pi, sizeof(pi));
\r
391 /* Get startup parameters */
\r
393 int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &stop_method, &si);
\r
395 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);
\r
396 return stop_service(2, true, true);
\r
399 /* Launch executable with arguments */
\r
400 char cmd[CMD_LENGTH];
\r
401 if (_snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "\"%s\" %s", exe, flags) < 0) {
\r
402 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);
\r
403 close_output_handles(&si);
\r
404 return stop_service(2, true, true);
\r
407 throttle_restart();
\r
409 bool inherit_handles = false;
\r
410 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
411 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, env, dir, &si, &pi)) {
\r
412 unsigned long error = GetLastError();
\r
413 if (error == ERROR_INVALID_PARAMETER && env) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service_name, exe, NSSM_REG_ENV, 0);
\r
414 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0);
\r
415 close_output_handles(&si);
\r
416 return stop_service(3, true, true);
\r
418 process_handle = pi.hProcess;
\r
419 pid = pi.dwProcessId;
\r
421 if (get_process_creation_time(process_handle, &creation_time)) ZeroMemory(&creation_time, sizeof(creation_time));
\r
423 close_output_handles(&si);
\r
425 /* Wait for a clean startup. */
\r
426 if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0;
\r
428 /* Signal successful start */
\r
429 service_status.dwCurrentState = SERVICE_RUNNING;
\r
430 SetServiceStatus(service_handle, &service_status);
\r
435 /* Stop the service */
\r
436 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
\r
437 allow_restart = false;
\r
438 if (wait_handle) UnregisterWait(wait_handle);
\r
440 if (default_action && ! exitcode && ! graceful) {
\r
441 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_GRACEFUL_SUICIDE, service_name, exe, exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_REALLY] ,0);
\r
445 /* Signal we are stopping */
\r
447 service_status.dwCurrentState = SERVICE_STOP_PENDING;
\r
448 service_status.dwWaitHint = NSSM_KILL_WINDOW_GRACE_PERIOD + NSSM_KILL_THREADS_GRACE_PERIOD + NSSM_WAITHINT_MARGIN;
\r
449 SetServiceStatus(service_handle, &service_status);
\r
452 /* Nothing to do if service isn't running */
\r
454 /* Shut down service */
\r
455 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);
\r
456 kill_process(service_name, stop_method, process_handle, pid, 0);
\r
458 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);
\r
460 end_service((void *) pid, true);
\r
462 /* Signal we stopped */
\r
464 service_status.dwCurrentState = SERVICE_STOPPED;
\r
466 service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
467 service_status.dwServiceSpecificExitCode = exitcode;
\r
470 service_status.dwWin32ExitCode = NO_ERROR;
\r
471 service_status.dwServiceSpecificExitCode = 0;
\r
473 SetServiceStatus(service_handle, &service_status);
\r
479 /* Callback function triggered when the server exits */
\r
480 void CALLBACK end_service(void *arg, unsigned char why) {
\r
481 if (stopping) return;
\r
485 pid = (unsigned long) arg;
\r
487 /* Check exit code */
\r
488 unsigned long exitcode = 0;
\r
490 FILETIME exit_time;
\r
491 GetExitCodeProcess(process_handle, &exitcode);
\r
492 if (exitcode == STILL_ACTIVE || get_process_exit_time(process_handle, &exit_time)) GetSystemTimeAsFileTime(&exit_time);
\r
493 CloseHandle(process_handle);
\r
496 Log that the service ended BEFORE logging about killing the process
\r
497 tree. See below for the possible values of the why argument.
\r
500 _snprintf_s(code, sizeof(code), _TRUNCATE, "%d", exitcode);
\r
501 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);
\r
505 kill_process_tree(service_name, stop_method, pid, exitcode, pid, &creation_time, &exit_time);
\r
508 The why argument is true if our wait timed out or false otherwise.
\r
509 Our wait is infinite so why will never be true when called by the system.
\r
510 If it is indeed true, assume we were called from stop_service() because
\r
511 this is a controlled shutdown, and don't take any restart action.
\r
514 if (! allow_restart) return;
\r
516 /* What action should we take? */
\r
517 int action = NSSM_EXIT_RESTART;
\r
518 unsigned char action_string[ACTION_LEN];
\r
519 bool default_action;
\r
520 if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {
\r
521 for (int i = 0; exit_action_strings[i]; i++) {
\r
522 if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
529 process_handle = 0;
\r
532 /* Try to restart the service or return failure code to service manager */
\r
533 case NSSM_EXIT_RESTART:
\r
534 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);
\r
535 while (monitor_service()) {
\r
536 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);
\r
541 /* Do nothing, just like srvany would */
\r
542 case NSSM_EXIT_IGNORE:
\r
543 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);
\r
547 /* Tell the service manager we are finished */
\r
548 case NSSM_EXIT_REALLY:
\r
549 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);
\r
550 stop_service(exitcode, true, default_action);
\r
553 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
554 case NSSM_EXIT_UNCLEAN:
\r
555 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);
\r
556 stop_service(exitcode, false, default_action);
\r
563 void throttle_restart() {
\r
564 /* This can't be a restart if the service is already running. */
\r
565 if (! throttle++) return;
\r
567 int ms = throttle_milliseconds();
\r
569 if (throttle > 7) throttle = 8;
\r
571 char threshold[8], milliseconds[8];
\r
572 _snprintf_s(threshold, sizeof(threshold), _TRUNCATE, "%d", throttle_delay);
\r
573 _snprintf_s(milliseconds, sizeof(milliseconds), _TRUNCATE, "%d", ms);
\r
574 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);
\r
576 if (use_critical_section) EnterCriticalSection(&throttle_section);
\r
577 else if (throttle_timer) {
\r
578 ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));
\r
579 throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
580 SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);
\r
583 service_status.dwCurrentState = SERVICE_PAUSED;
\r
584 SetServiceStatus(service_handle, &service_status);
\r
586 if (use_critical_section) {
\r
587 imports.SleepConditionVariableCS(&throttle_condition, &throttle_section, ms);
\r
588 LeaveCriticalSection(&throttle_section);
\r
591 if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);
\r