This is an automated email from the ASF dual-hosted git repository. sseifert pushed a commit to branch feature/SLING-11934-windows-shutdown-fix in repository https://gitbox.apache.org/repos/asf/sling-feature-launcher-maven-plugin.git
commit 7f80888dde6fbae5c0c647a606219cbc58c63e46 Author: Stefan Seifert <[email protected]> AuthorDate: Mon Nov 24 16:14:57 2025 +0100 SLING-11934 Fix shutting down launcher started via bat file on Windows --- pom.xml | 16 +---- .../maven/feature/launcher/ProcessTracker.java | 73 ++++++++++++++++++---- 2 files changed, 63 insertions(+), 26 deletions(-) diff --git a/pom.xml b/pom.xml index b8c6908..f690130 100644 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ </prerequisites> <properties> - <sling.java.version>8</sling.java.version> + <sling.java.version>11</sling.java.version> <maven.version>3.8.1</maven.version> <project.build.outputTimestamp>1757086693</project.build.outputTimestamp> <github.project.id>apache/sling-feature-launcher-maven-plugin</github.project.id> @@ -196,18 +196,4 @@ </plugins> </reporting> - <profiles> - <!-- remove once SLING-11934 is fixed --> - <profile> - <id>skip-invoker-windows</id> - <activation> - <os> - <family>Windows</family> - </os> - </activation> - <properties> - <invoker.skip>true</invoker.skip> - </properties> - </profile> - </profiles> </project> diff --git a/src/main/java/org/apache/sling/maven/feature/launcher/ProcessTracker.java b/src/main/java/org/apache/sling/maven/feature/launcher/ProcessTracker.java index cc26d5e..72e33ae 100644 --- a/src/main/java/org/apache/sling/maven/feature/launcher/ProcessTracker.java +++ b/src/main/java/org/apache/sling/maven/feature/launcher/ProcessTracker.java @@ -25,51 +25,102 @@ import java.util.concurrent.TimeUnit; import javax.inject.Named; import javax.inject.Singleton; +import org.apache.maven.shared.utils.Os; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + @Named @Singleton public class ProcessTracker { + private static final Logger LOG = LoggerFactory.getLogger(ProcessTracker.class); + static void stop(Process process) throws InterruptedException { + LOG.debug("Destroy process: {}", process); process.destroy(); boolean stopped = process.waitFor(30, TimeUnit.SECONDS); - if ( !stopped ) + if ( !stopped ) { + LOG.debug("Forcibly destroy process after 30sec: {}", process); process.destroyForcibly(); + } + LOG.debug("Destroy process finished: {}", process); + } + + /** + * On windows, this method is used for stopping the launcher. + * The Launcher is started from a .bat file, and killing the known process only kills the .bat process, not the spawned java process. + * So we try to kill all descendant processes first. + */ + static void stopWithDescendants(Process process) throws InterruptedException { + LOG.debug("Destroy process with descendants: {}", process); + + ProcessHandle processHandle = ProcessHandle.of(process.pid()).orElse(null); + if (processHandle == null) { + LOG.error("Unable to shutdown process, no process handle for pid {}", process.pid()); + return; + } + + processHandle.descendants().forEach(childProcess -> { + LOG.debug("Destroy child process: {}", childProcess); + childProcess.destroy(); + try { + boolean stopped = childProcess.onExit().get(30, TimeUnit.SECONDS) != null; + if ( !stopped ) { + LOG.debug("Forcibly destroy child process after 30sec: {}", childProcess); + childProcess.destroyForcibly(); + } + LOG.debug("Destroy child process finished: {}", childProcess); + } catch (Exception ex) { + LOG.error("Error while stopping child process {}: {}", childProcess, ex.getMessage(), ex); + } + }); + + stop(process); } - + private final Object sync = new Object(); - + private boolean hookAdded = false; private final Map<String, Process> processes = new HashMap<>(); - + public void startTracking(String launchId, Process process) { synchronized (sync) { if ( processes.containsKey(launchId) ) throw new IllegalArgumentException("Launch id " + launchId + " already associated with a process"); + LOG.debug("Start tracking process for launch {}: {}", launchId, process); processes.put(launchId, process); if ( ! hookAdded ) { Runtime.getRuntime().addShutdownHook(new Thread("process-tracker-shutdown") { @Override public void run() { + LOG.debug("Shutdown hook is running for launch {}: {}", launchId, process); for ( Map.Entry<String, Process> entry : processes.entrySet() ) { - System.err.println("Launch " + entry.getKey() + " was not shut down! Destroying forcibly from shutdown hook.."); + LOG.error("Launch {} was not shut down! Destroying forcibly from shutdown hook.", entry.getKey()); process.destroyForcibly(); } } - + }); hookAdded = true; } } } - + public void stop(String id) throws InterruptedException { Process process; synchronized (sync) { - process = processes.remove(id); + process = processes.remove(id); } - if ( process == null ) + if ( process == null ) { + LOG.warn("Process not found in process list, skip stopping: {}", id); return; - - stop(process); + } + + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + stopWithDescendants(process); + } + else { + stop(process); + } } }
