This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-daemon.git
The following commit(s) were added to refs/heads/master by this push: new f0b4524 Arrange the timeout logic and values (#238) f0b4524 is described below commit f0b4524ac6981cc6b50d69612ab124c14d2f7d86 Author: Jean-Frederic Clere <jfcl...@gmail.com> AuthorDate: Thu May 15 16:58:45 2025 +0200 Arrange the timeout logic and values (#238) Change ONE_MINUTE to ONE_MINUTE_AS_MILLIS and ONE_MINUTE_SEC to ONE_MINUTE_AS_SEC. Don't allow to install service with negative StopTimeout value. Arrange explaination for StopTimeout Use ERROR and display the already waited time in case StopTimeout is too small. --- src/native/windows/apps/prunsrv/prunsrv.c | 191 ++++++++++++++++++++++++------ src/native/windows/src/javajni.c | 4 +- src/native/windows/src/rprocess.c | 16 +++ src/site/xdoc/procrun.xml | 12 +- 4 files changed, 181 insertions(+), 42 deletions(-) diff --git a/src/native/windows/apps/prunsrv/prunsrv.c b/src/native/windows/apps/prunsrv/prunsrv.c index ce01b88..771b87f 100644 --- a/src/native/windows/apps/prunsrv/prunsrv.c +++ b/src/native/windows/apps/prunsrv/prunsrv.c @@ -41,7 +41,8 @@ #define STDIN_FILENO 0 #define STDOUT_FILENO 1 #define STDERR_FILENO 2 -#define ONE_MINUTE (60 * 1000) +#define ONE_MINUTE_AS_MILLIS (60 * 1000) +#define ONE_MINUTE_AS_SEC 60 #ifdef _WIN64 #define KREG_WOW6432 KEY_WOW64_32KEY @@ -258,6 +259,8 @@ static BOOL gSignalValid = TRUE; static APXJAVA_THREADARGS gRargs; static APXJAVA_THREADARGS gSargs; +static DWORD stopCalledTime = 0; /* Not stop not started */ + DWORD WINAPI eventThread(LPVOID lpParam) { DWORD dwRotateCnt = SO_LOGROTATE; @@ -289,6 +292,21 @@ DWORD WINAPI eventThread(LPVOID lpParam) UNREFERENCED_PARAMETER(lpParam); } +/* Calculate how much time we already spent stopping the service */ +static DWORD waitedSinceStopCmd() +{ + DWORD now = 0; + DWORD waited = 0; + now = GetTickCount(); + if (now >= stopCalledTime) + waited = now - stopCalledTime; + else { + /* we have wrapped to zero */ + waited = (0xFFFFFFFF - stopCalledTime) + now; + } + return waited; +} + /* redirect console stdout/stderr to files * so that Java messages can get logged * If stderrfile is not specified it will @@ -777,6 +795,12 @@ static BOOL docmdInstallService(LPAPXCMDLINE lpCmdline) apxLogWrite(APXLOG_MARK_ERROR "Unable to open the Service Manager."); return FALSE; } + + /* Check the stop timeout value */ + if (SO_STOPTIMEOUT > 0x7FFFFFFF) { + apxLogWrite(APXLOG_MARK_ERROR "StopTimeout can't be lower that 0"); + return FALSE; + } /* Check the startup mode */ if (ST_STARTUP & APXCMDOPT_FOUND) { if (lstrcmpiW(SO_STARTUP, PRSRV_AUTO) == 0) { @@ -940,6 +964,7 @@ static BOOL docmdStopService(LPAPXCMDLINE lpCmdline) return FALSE; } + stopCalledTime = GetTickCount(); SetLastError(ERROR_SUCCESS); /* Open the service */ if (apxServiceOpen(hService, lpCmdline->szApplication, @@ -951,15 +976,34 @@ static BOOL docmdStopService(LPAPXCMDLINE lpCmdline) NULL); if (!rv) { /* Wait for the timeout if any */ - int timeout = SO_STOPTIMEOUT; - if (timeout) { - int i; - for (i = 0; i < timeout; i++) { - rv = apxServiceCheckStop(hService); - apxLogWrite(APXLOG_MARK_DEBUG "apxServiceCheck returns %d.", rv); - if (rv) - break; - } + int timeout = SO_STOPTIMEOUT; + int waited = waitedSinceStopCmd(); + int i; + if (!timeout) { + /* waiting for ever doesn't look OK here */ + timeout = ONE_MINUTE_AS_SEC; + } + apxLogWrite(APXLOG_MARK_DEBUG "docmdStopService Waited %d timeout %d", waited, timeout*1000); + if (timeout*1000 > waited) { + /* + * it took waited to send the command, the service starts + * counting the timeout once it receives the STOP command + * that is probably after waited but that depends on the + * box we are using, we add 1 second just to be sure. + */ + timeout = timeout*1000 + waited + 1000; + } else { + apxLogWrite(APXLOG_MARK_DEBUG "docmdStopService timeout %d too small.", timeout*1000); + timeout = 1000; /* we might wait 1 s too much but we check if we have already stopped */ + } + apxLogWrite(APXLOG_MARK_DEBUG "docmdStopService estimated timeout %d milliseconds.", timeout); + /* the SO_STOPTIMEOUT applies to the stop command and to the time service needs to stop */ + for (i = 0; i < timeout; i=i+1000) { + /* apxServiceCheckStop waits 1000 ms */ + rv = apxServiceCheckStop(hService); + apxLogWrite(APXLOG_MARK_DEBUG "apxServiceCheck returns %d.", rv); + if (rv) + break; } } if (rv) @@ -1223,16 +1267,26 @@ static DWORD WINAPI serviceStop(LPVOID lpParameter) BOOL wait_to_die = FALSE; DWORD timeout = SO_STOPTIMEOUT * 1000; DWORD dwCtrlType = (DWORD)((BYTE *)lpParameter - (BYTE *)0); + DWORD waited = 0; apxLogWrite(APXLOG_MARK_INFO "Stopping service..."); + stopCalledTime = GetTickCount(); + if (dwCtrlType == SERVICE_CONTROL_SHUTDOWN) + timeout = MIN(timeout, apxGetMaxServiceTimeout(gPool)); + if (!timeout) { + /* Use 1 minute default */ + timeout = ONE_MINUTE_AS_MILLIS; + } + apxLogWrite(APXLOG_MARK_INFO "Stopping service...timeout %d", timeout); + /* give a hint for shutdown time */ + reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, timeout); if (IS_INVALID_HANDLE(gWorker)) { apxLogWrite(APXLOG_MARK_INFO "Worker is not defined."); return TRUE; /* Nothing to do */ } - if (timeout > 0x7FFFFFFF) - timeout = INFINITE; /* If the timeout was '-1' wait forewer */ if (_jni_shutdown) { + apxLogWrite(APXLOG_MARK_INFO "Stopping service...timeout %d _jni_shutdown", timeout); if (!IS_VALID_STRING(SO_STARTPATH) && IS_VALID_STRING(SO_STOPPATH)) { /* If the Working path is specified change the current directory * but only if the start path wasn't specified already. @@ -1269,16 +1323,14 @@ static DWORD WINAPI serviceStop(LPVOID lpParameter) } else { if (lstrcmpA(_jni_sclass, "java/lang/System") == 0) { - reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 20 * 1000); + /* report progress */ + reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, timeout); apxLogWrite(APXLOG_MARK_DEBUG "Forcing Java JNI System.exit() worker to finish..."); return 0; } else { apxLogWrite(APXLOG_MARK_DEBUG "Waiting for Java JNI stop worker to finish for %s:%s...", _jni_sclass, _jni_smethod); - if (!timeout) - apxJavaWait(hWorker, INFINITE, FALSE); - else - apxJavaWait(hWorker, timeout, FALSE); + apxJavaWait(hWorker, timeout, FALSE); apxLogWrite(APXLOG_MARK_DEBUG "Java JNI stop worker finished."); } } @@ -1351,10 +1403,7 @@ static DWORD WINAPI serviceStop(LPVOID lpParameter) goto cleanup; } else { apxLogWrite(APXLOG_MARK_DEBUG "Waiting for stop worker to finish..."); - if (!timeout) - apxHandleWait(hWorker, INFINITE, FALSE); - else - apxHandleWait(hWorker, timeout, FALSE); + apxHandleWait(hWorker, timeout, FALSE); apxLogWrite(APXLOG_MARK_DEBUG "Stop worker finished."); } wait_to_die = TRUE; @@ -1375,12 +1424,21 @@ cleanup: CloseHandle(gSignalThread); gSignalEvent = NULL; } - if (wait_to_die && !timeout) - timeout = 300 * 1000; /* Use the 5 minute default shutdown */ - if (dwCtrlType == SERVICE_CONTROL_SHUTDOWN) - timeout = MIN(timeout, apxGetMaxServiceTimeout(gPool)); - reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, timeout); + /* We already waited here timeout is msec */ + waited = waitedSinceStopCmd(); + apxLogWrite(APXLOG_MARK_DEBUG "Waited %d timeout (%d)", waited, timeout); + if (timeout > waited) { + timeout = timeout - waited; + /* renew the hint message */ + reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, timeout); + } else { + /* something is wrong, the timeout is too small */ + apxLogWrite(APXLOG_MARK_DEBUG "Waiting more than the specified timeout (%d)", timeout); + /* tell we fail */ + reportServiceStatus(SERVICE_STOP_PENDING, ERROR_SERVICE_REQUEST_TIMEOUT, 0); + } + if (timeout) { FILETIME fts, fte; @@ -1609,11 +1667,14 @@ void WINAPI service_ctrl_handler(DWORD dwCtrlCode) case SERVICE_CONTROL_STOP: apxLogWrite(APXLOG_MARK_INFO "Service SERVICE_CONTROL_STOP signalled."); _exe_shutdown = TRUE; - if (SO_STOPTIMEOUT > 0) { + stopCalledTime = GetTickCount(); + /* hint for shutdown time */ + if (SO_STOPTIMEOUT) { reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, SO_STOPTIMEOUT * 1000); } else { - reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 3 * 1000); + /* Use 1 minute default */ + reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, ONE_MINUTE_AS_MILLIS); } /* Stop the service asynchronously */ stopThread = CreateThread(NULL, 0, @@ -1838,9 +1899,13 @@ void WINAPI serviceMain(DWORD argc, LPTSTR *argv) SetConsoleCtrlHandler((PHANDLER_ROUTINE)console_handler, TRUE); if (SO_STOPTIMEOUT) { - /* we have a stop timeout */ + /* we wait until the service has stopped or crashed, printing a debug message every 2 seconds */ + /* we also warn in case the service doesn't stop before the stop timeout */ BOOL bLoopWarningIssued = FALSE; + int waited = 0; + int timeout = SO_STOPTIMEOUT; do { + DWORD count = 0; /* wait 2 seconds */ DWORD rv = apxHandleWait(gWorker, 2000, FALSE); if (rv == WAIT_OBJECT_0 && !_exe_shutdown) { @@ -1848,12 +1913,31 @@ void WINAPI serviceMain(DWORD argc, LPTSTR *argv) apxLogWrite(APXLOG_MARK_WARN "Start method returned before stop method was called. This should not happen. Using loop with a fixed sleep of 2 seconds waiting for stop method to be called."); bLoopWarningIssued = TRUE; } + /* XXXX apart error are we getting there??? */ Sleep(2000); + count = count + 2; + if (count >= SO_STOPTIMEOUT) { + apxLogWrite(APXLOG_MARK_WARN "waited %d sec, Timeout reached!" , count); + break; + } } + if (rv == WAIT_OBJECT_0) + apxLogWrite(APXLOG_MARK_WARN "Worker has crashed or stopped!"); + else + apxLogWrite(APXLOG_MARK_DEBUG "waiting until Worker is done..."); } while (!_exe_shutdown); - apxLogWrite(APXLOG_MARK_DEBUG "waiting %d sec... shutdown: %d", SO_STOPTIMEOUT, _exe_shutdown); - apxHandleWait(gWorker, SO_STOPTIMEOUT*1000, FALSE); + + /* calculate remaing timeout */ + waited = waitedSinceStopCmd(); + if (timeout*1000 > waited) { + timeout = timeout*1000 - waited; + } else { + timeout = 1000; /* 1000 ms in the worse case */ + } + apxLogWrite(APXLOG_MARK_DEBUG "waiting %d milliseconds... shutdown: %d", timeout, _exe_shutdown); + apxHandleWait(gWorker, timeout, FALSE); } else { + apxLogWrite(APXLOG_MARK_DEBUG "waiting until Worker is done..."); apxHandleWait(gWorker, INFINITE, FALSE); } apxLogWrite(APXLOG_MARK_DEBUG "Worker finished."); @@ -1863,21 +1947,52 @@ void WINAPI serviceMain(DWORD argc, LPTSTR *argv) goto cleanup; } if (gShutdownEvent) { + int waited = waitedSinceStopCmd(); + int timeout = SO_STOPTIMEOUT; + BOOL btimeoutelapsed = FALSE; /* Ensure that shutdown thread exits before us */ apxLogWrite(APXLOG_MARK_DEBUG "Waiting for ShutdownEvent."); - reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, ONE_MINUTE); - WaitForSingleObject(gShutdownEvent, ONE_MINUTE); + reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, ONE_MINUTE_AS_MILLIS); + WaitForSingleObject(gShutdownEvent, ONE_MINUTE_AS_MILLIS); apxLogWrite(APXLOG_MARK_DEBUG "ShutdownEvent signaled."); CloseHandle(gShutdownEvent); gShutdownEvent = NULL; + /* calculate the next timeout here timeout is sec */ + if (!timeout) + timeout = ONE_MINUTE_AS_SEC; + apxLogWrite(APXLOG_MARK_DEBUG "Waited %d timeout (%d)", waited, timeout*1000); + if (timeout*1000 > waited) { + timeout = timeout*1000 - waited; + } else { + /* something is wrong, the timeout is too small */ + apxLogWrite(APXLOG_MARK_ERROR "Waiting more than the specified timeout (%d) already waited for %d", timeout, waited/1000); + timeout = ONE_MINUTE_AS_MILLIS; + btimeoutelapsed = TRUE; + } + /* This will cause to wait for all threads to exit */ - apxLogWrite(APXLOG_MARK_DEBUG "Waiting 1 minute for all threads to exit."); - reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, ONE_MINUTE); - apxDestroyJvm(ONE_MINUTE); - /* if we are not using JAVA apxDestroyJvm does nothing, check the chid processes in case they hang */ + apxLogWrite(APXLOG_MARK_DEBUG "Waiting %d milliseconds for all threads to exit.", timeout); + reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, ONE_MINUTE_AS_MILLIS); + if (!apxDestroyJvm(timeout)) { + /* if we are not using JAVA apxDestroyJvm does nothing, check the chid processes in case they hang */ + apxLogWrite(APXLOG_MARK_DEBUG "apxDestroyJvm did nothing or failed"); + for (;;) { + if (!apxProcessTerminateChild( GetCurrentProcessId(), TRUE)) { + /* Just print the children processes once for debugging */ + if (btimeoutelapsed) + break; + waited = waitedSinceStopCmd(); + if (waited >= timeout) + break; /* Done */ + Sleep(1000); + } else { + break; + } + } + } apxProcessTerminateChild( GetCurrentProcessId(), FALSE); /* FALSE kills! */ } else { @@ -1887,7 +2002,7 @@ void WINAPI serviceMain(DWORD argc, LPTSTR *argv) */ apxLogWrite(APXLOG_MARK_DEBUG "Waiting for all threads to exit."); apxDestroyJvm(INFINITE); - reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); + reportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, ONE_MINUTE_AS_MILLIS); } apxLogWrite(APXLOG_MARK_DEBUG "JVM destroyed."); reportServiceStatusStopped(apxGetVmExitCode()); diff --git a/src/native/windows/src/javajni.c b/src/native/windows/src/javajni.c index 1aad2e1..e98630e 100644 --- a/src/native/windows/src/javajni.c +++ b/src/native/windows/src/javajni.c @@ -486,8 +486,10 @@ apxDestroyJvm(DWORD dwTimeout) CloseHandle(hWaiter); return rv; } - else + else { + apxLogWrite(APXLOG_MARK_DEBUG "apxDestroyJvm No JVM so Done"); return FALSE; + } } static BOOL __apxIsJava9() diff --git a/src/native/windows/src/rprocess.c b/src/native/windows/src/rprocess.c index 46ca36f..3e23883 100644 --- a/src/native/windows/src/rprocess.c +++ b/src/native/windows/src/rprocess.c @@ -408,6 +408,22 @@ BOOL apxProcessTerminateChild(DWORD dwProcessId, BOOL dryrun) apxLogWrite(APXLOG_MARK_DEBUG "Process ID: 0x%08X (%d) Termination failed!", treeProcessId[i], treeProcessId[i]); } } + } else { + /* Check if we have child processes and return FALSE if not */ + HANDLE hProcess; + for (i=0; i<maxProcessId; i++) { + if (treeProcessId[i] == 0) { + break; /* Done */ + } + hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, treeProcessId[i]); + if(hProcess != NULL) { + apxLogWrite(APXLOG_MARK_DEBUG "Process ID: 0x%08X (%d) Still here!", treeProcessId[i], treeProcessId[i]); + CloseHandle(hProcess); + CloseHandle(hProcessSnap); + return(FALSE); + } + } + } CloseHandle(hProcessSnap); diff --git a/src/site/xdoc/procrun.xml b/src/site/xdoc/procrun.xml index 13213d1..f42a6a0 100644 --- a/src/site/xdoc/procrun.xml +++ b/src/site/xdoc/procrun.xml @@ -530,9 +530,15 @@ will add the new value(s) to any existing value(s). </tr> <tr> <td>--StopTimeout</td> - <td>No Timeout</td> - <td>Defines the timeout in seconds that procrun waits for service to - exit gracefully.</td> + <td>60 seconds</td> + <td>Defines the timeout in seconds that procrun waits for service + to stop. + <b>The timeout applies to the stop command and to the time service needs to stop</b> + Negative values are not allowed. + Note: Make sure you use a value big enough to give time for + procrun to stop the service. Once the time out is elapsed, + procrun will try to kill the whole process tree the service + has created.</td> </tr> <tr> <td>--LogPath</td>