Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion src/jdk.jpackage/windows/native/applauncher/WinLauncher.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -210,6 +210,16 @@ class RunExecutorWithMsgLoop {
};


void enableConsoleCtrlHandler(bool enable) {
if (!SetConsoleCtrlHandler(NULL, enable ? FALSE : TRUE)) {
JP_THROW(SysError(tstrings::any() << "SetConsoleCtrlHandler(NULL, "
<< (enable ? "FALSE" : "TRUE")
<< ") failed",
SetConsoleCtrlHandler));
}
}


void launchApp() {
// [RT-31061] otherwise UI can be left in back of other windows.
::AllowSetForegroundWindow(ASFW_ANY);
Expand Down Expand Up @@ -256,6 +266,19 @@ void launchApp() {
exec.arg(arg);
});

exec.afterProcessCreated([&](HANDLE pid) {
//
// Ignore Ctrl+C in the current process.
// This will prevent child process termination without allowing
// it to handle Ctrl+C events.
//
// Disable the default Ctrl+C handler *after* the child process
// has been created as it is inheritable and we want the child
// process to have the default handler.
//
enableConsoleCtrlHandler(false);
});

DWORD exitCode = RunExecutorWithMsgLoop::apply(exec);

exit(exitCode);
Expand Down
6 changes: 5 additions & 1 deletion src/jdk.jpackage/windows/native/common/Executor.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -161,6 +161,10 @@ UniqueHandle Executor::startProcess(UniqueHandle* threadHandle) const {
}
}

if (afterProcessCreatedCallback) {
afterProcessCreatedCallback(processInfo.hProcess);
}

// Return process handle.
return UniqueHandle(processInfo.hProcess);
}
13 changes: 12 additions & 1 deletion src/jdk.jpackage/windows/native/common/Executor.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -26,6 +26,8 @@
#ifndef EXECUTOR_H
#define EXECUTOR_H

#include <functional>

#include "tstrings.h"
#include "UniqueHandle.h"

Expand Down Expand Up @@ -97,6 +99,14 @@ class Executor {
*/
int execAndWaitForExit() const;

/**
* Call provided function after the process hass been created.
*/
Executor& afterProcessCreated(const std::function<void(HANDLE)>& v) {
afterProcessCreatedCallback = v;
return *this;
}

private:
UniqueHandle startProcess(UniqueHandle* threadHandle=0) const;

Expand All @@ -106,6 +116,7 @@ class Executor {
HANDLE jobHandle;
tstring_array argsArray;
std::wstring appPath;
std::function<void(HANDLE)> afterProcessCreatedCallback;
};

#endif // #ifndef EXECUTOR_H
88 changes: 88 additions & 0 deletions test/jdk/tools/jpackage/apps/UseShutdownHook.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

public class UseShutdownHook {

public static void main(String[] args) throws InterruptedException {
trace("Started");

var outputFile = Path.of(args[0]);
trace(String.format("Write output in [%s] file", outputFile));

var shutdownTimeoutSeconds = Integer.parseInt(args[1]);
trace(String.format("Automatically shutdown the app in %ss", shutdownTimeoutSeconds));

Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
output(outputFile, "shutdown hook executed");
}
});

var startTime = System.currentTimeMillis();
var lock = new Object();
do {
synchronized (lock) {
lock.wait(shutdownTimeoutSeconds * 1000);
}
} while ((System.currentTimeMillis() - startTime) < (shutdownTimeoutSeconds * 1000));

output(outputFile, "exit");
}

private static void output(Path outputFilePath, String msg) {

trace(String.format("Writing [%s] into [%s]", msg, outputFilePath));

try {
Files.createDirectories(outputFilePath.getParent());
Files.writeString(outputFilePath, msg, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}

private static void trace(String msg) {
Date time = new Date(System.currentTimeMillis());
msg = String.format("UseShutdownHook [%s]: %s", SDF.format(time), msg);
System.out.println(msg);
try {
Files.write(traceFile, List.of(msg), StandardOpenOption.APPEND, StandardOpenOption.CREATE);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}

private static final SimpleDateFormat SDF = new SimpleDateFormat("HH:mm:ss.SSS");

private static final Path traceFile = Path.of(System.getProperty("jpackage.test.trace-file"));
}
12 changes: 9 additions & 3 deletions test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CfgFile.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -59,13 +59,18 @@ public String getValueUnchecked(String sectionName, String key) {
}
}

public void addValue(String sectionName, String key, String value) {
public CfgFile addValue(String sectionName, String key, String value) {
var section = getSection(sectionName);
if (section == null) {
section = new Section(sectionName, new ArrayList<>());
data.add(section);
}
section.data.add(Map.entry(key, value));
return this;
}

public CfgFile add(CfgFile other) {
return combine(this, other);
}

public CfgFile() {
Expand All @@ -89,7 +94,7 @@ private CfgFile(List<Section> data, String id) {
this.id = id;
}

public void save(Path path) {
public CfgFile save(Path path) {
var lines = data.stream().flatMap(section -> {
return Stream.concat(
Stream.of(String.format("[%s]", section.name)),
Expand All @@ -98,6 +103,7 @@ public void save(Path path) {
}));
});
TKit.createTextFile(path, lines);
return this;
}

private Section getSection(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -270,7 +271,7 @@ PackageTest addHelloAppFileAssociationsVerifier(FileAssociations fa,
TKit.trace(String.format("Use desktop to open [%s] file",
testFile));
Desktop.getDesktop().open(testFile.toFile());
TKit.waitForFileCreated(appOutput, 7);
TKit.waitForFileCreated(appOutput, Duration.ofSeconds(7), Duration.ofSeconds(3));

List<String> expectedArgs = new ArrayList<>(List.of(
faLauncherDefaultArgs));
Expand Down
81 changes: 45 additions & 36 deletions test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -521,49 +522,57 @@ public static Path createRelativePathCopy(final Path file) {
return file;
}

static void waitForFileCreated(Path fileToWaitFor,
long timeoutSeconds) throws IOException {
public static void waitForFileCreated(Path fileToWaitFor,
Duration timeout, Duration afterCreatedTimeout) throws IOException {
waitForFileCreated(fileToWaitFor, timeout);
// Wait after the file has been created to ensure it is fully written.
ThrowingConsumer.<Long>toConsumer(Thread::sleep).accept(afterCreatedTimeout.getSeconds());
}

private static void waitForFileCreated(Path fileToWaitFor, Duration timeout) throws IOException {

trace(String.format("Wait for file [%s] to be available",
fileToWaitFor.toAbsolutePath()));

WatchService ws = FileSystems.getDefault().newWatchService();

Path watchDirectory = fileToWaitFor.toAbsolutePath().getParent();
watchDirectory.register(ws, ENTRY_CREATE, ENTRY_MODIFY);

long waitUntil = System.currentTimeMillis() + timeoutSeconds * 1000;
for (;;) {
long timeout = waitUntil - System.currentTimeMillis();
assertTrue(timeout > 0, String.format(
"Check timeout value %d is positive", timeout));

WatchKey key = ThrowingSupplier.toSupplier(() -> ws.poll(timeout,
TimeUnit.MILLISECONDS)).get();
if (key == null) {
if (fileToWaitFor.toFile().exists()) {
trace(String.format(
"File [%s] is available after poll timeout expired",
fileToWaitFor));
return;
try (var ws = FileSystems.getDefault().newWatchService()) {

Path watchDirectory = fileToWaitFor.toAbsolutePath().getParent();
watchDirectory.register(ws, ENTRY_CREATE, ENTRY_MODIFY);

var waitUntil = Instant.now().plus(timeout);
for (;;) {
Instant n = Instant.now();
Duration remainderTimeout = Duration.between(n, waitUntil);
assertTrue(!remainderTimeout.isNegative() && !remainderTimeout.isZero(), String.format(
"Check timeout value %dms is positive", remainderTimeout.toMillis()));

WatchKey key = ThrowingSupplier.toSupplier(() -> {
return ws.poll(remainderTimeout.toMillis(), TimeUnit.MILLISECONDS);
}).get();
if (key == null) {
if (Files.exists(fileToWaitFor)) {
trace(String.format(
"File [%s] is available after poll timeout expired",
fileToWaitFor));
return;
}
assertUnexpected(String.format("Timeout %dms expired", remainderTimeout.toMillis()));
}
assertUnexpected(String.format("Timeout expired", timeout));
}

for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
continue;
}
Path contextPath = (Path) event.context();
if (Files.isSameFile(watchDirectory.resolve(contextPath),
fileToWaitFor)) {
trace(String.format("File [%s] is available", fileToWaitFor));
return;
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
continue;
}
Path contextPath = (Path) event.context();
if (Files.exists(fileToWaitFor) && Files.isSameFile(watchDirectory.resolve(contextPath), fileToWaitFor)) {
trace(String.format("File [%s] is available", fileToWaitFor));
return;
}
}
}

if (!key.reset()) {
assertUnexpected("Watch key invalidated");
if (!key.reset()) {
assertUnexpected("Watch key invalidated");
}
}
}
}
Expand Down
Loading