From f448a6346823b8156b5a2547ee684b3158e55015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Sat, 17 Mar 2018 19:50:25 +0100 Subject: [PATCH] Update W32Service#stopService to be more resilent to delayed status updates If the service asked to be stopped does not immediatly update its state in response to a stop command, the stopService method raised an exception. This change follows a sample on MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/ms686335(v=vs.85).aspx It only uses the status query to determine the final state and not to see if the services reacted to the stop request. Closes: #797 --- .../com/sun/jna/platform/win32/Advapi32.java | 19 +++++++++ .../sun/jna/platform/win32/W32Service.java | 39 ++++++++++++++++--- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Advapi32.java b/contrib/platform/src/com/sun/jna/platform/win32/Advapi32.java index 43b8a5f1f0..9462087600 100755 --- a/contrib/platform/src/com/sun/jna/platform/win32/Advapi32.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Advapi32.java @@ -1623,6 +1623,25 @@ boolean QueryServiceStatusEx(SC_HANDLE hService, int InfoLevel, SERVICE_STATUS_PROCESS lpBuffer, int cbBufSize, IntByReference pcbBytesNeeded); + /** + * Retrieves the current status of the specified service based on the + * specified information level. + * + * @param hService + * A handle to the service. This handle is returned by the + * OpenService(SC_HANDLE, String, int) or CreateService() + * function, and it must have the SERVICE_QUERY_STATUS access + * right. For more information, see Service Security and Access Rights. + * @param lpServiceStatus + * A pointer to a SERVICE_STATUS structure that receives the status information. + * @return If the function succeeds, the return value is true. If the + * function fails, the return value is false. To get extended error + * information, call GetLastError. + */ + boolean QueryServiceStatus(SC_HANDLE hService, SERVICE_STATUS lpServiceStatus); + /** * Sends a control code to a service. To specify additional information when * stopping a service, use the ControlServiceEx function. diff --git a/contrib/platform/src/com/sun/jna/platform/win32/W32Service.java b/contrib/platform/src/com/sun/jna/platform/win32/W32Service.java index 919f9bf8af..fcfe53c167 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/W32Service.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/W32Service.java @@ -38,8 +38,10 @@ import com.sun.jna.platform.win32.Winsvc.SC_ACTION; import com.sun.jna.platform.win32.Winsvc.SC_HANDLE; import com.sun.jna.platform.win32.Winsvc.SC_STATUS_TYPE; +import static com.sun.jna.platform.win32.Winsvc.SERVICE_CONTROL_STOP; import com.sun.jna.platform.win32.Winsvc.SERVICE_FAILURE_ACTIONS; import com.sun.jna.platform.win32.Winsvc.SERVICE_FAILURE_ACTIONS_FLAG; +import com.sun.jna.platform.win32.Winsvc.SERVICE_STATUS; import com.sun.jna.platform.win32.Winsvc.SERVICE_STATUS_PROCESS; import com.sun.jna.ptr.IntByReference; @@ -204,19 +206,46 @@ public void startService() { } } + /** + * Stop service. + */ + public void stopService() { + stopService(30000); + } + /** * Stop service. - */ - public void stopService() { + * + * @param timeout timeout in ms until the service must report to be + * stopped + */ + public void stopService(long timeout) { + long startTime = System.currentTimeMillis(); waitForNonPendingState(); // If the service is already stopped - return if (queryStatus().dwCurrentState == Winsvc.SERVICE_STOPPED) { return; } - if (! Advapi32.INSTANCE.ControlService(_handle, Winsvc.SERVICE_CONTROL_STOP, - new Winsvc.SERVICE_STATUS())) { + SERVICE_STATUS status = new SERVICE_STATUS(); + if (! Advapi32.INSTANCE.ControlService(_handle, SERVICE_CONTROL_STOP, status)) { throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } + // This following the sample from the MSDN + // the previouos implementation queried the service status and + // failed if the application did not correctly update its state + while(status.dwCurrentState != Winsvc.SERVICE_STOPPED) { + try { + Thread.sleep( status.dwWaitHint ); + } catch (InterruptedException e){ + throw new RuntimeException(e); + } + if(! Advapi32.INSTANCE.QueryServiceStatus(_handle, status)) { + throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); + } + if((System.currentTimeMillis() - startTime) > timeout) { + throw new RuntimeException(String.format("Service stop exceeded timeout time of %d ms", timeout)); + } + } waitForNonPendingState(); if (queryStatus().dwCurrentState != Winsvc.SERVICE_STOPPED) { throw new RuntimeException("Unable to stop the service"); @@ -269,7 +298,7 @@ public void waitForNonPendingState() { SERVICE_STATUS_PROCESS status = queryStatus(); int previousCheckPoint = status.dwCheckPoint; - int checkpointStartTickCount = Kernel32.INSTANCE.GetTickCount();; + int checkpointStartTickCount = Kernel32.INSTANCE.GetTickCount(); while (isPendingState(status.dwCurrentState)) {