Keep Glass attached through target-worker shutdown
Early Platform.exit detached JavaFX while queued macOS activation callbacks still called Java. Close the owned example before terminating its JVM, make shutdown idempotent, and reject late preview work. Add real startup-cancellation regressions and native crash-report checks.
This commit is contained in:
@@ -8,6 +8,7 @@ import subprocess
|
||||
import tempfile
|
||||
import json
|
||||
import time
|
||||
from native_crashes import NativeCrashGuard
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
@@ -16,6 +17,7 @@ parser.add_argument("--idea-home", required=True)
|
||||
args = parser.parse_args()
|
||||
logs = ROOT / "target/connected-workflow"
|
||||
logs.mkdir(parents=True, exist_ok=True)
|
||||
crashes = NativeCrashGuard(ROOT)
|
||||
classpath = os.pathsep.join([str(ROOT / "designer-standalone/target/test-classes"),
|
||||
str(ROOT / "designer-standalone/target/classes"),
|
||||
(ROOT / "designer-standalone/target/smoke-classpath.txt").read_text().strip()])
|
||||
@@ -41,6 +43,9 @@ with tempfile.TemporaryDirectory(prefix="designer-connected-") as temporary:
|
||||
run(host + "-" + mode, [Path(os.environ["JAVA_HOME"]) / "bin/java", "-cp", classpath,
|
||||
"de.neitzel.fxdesigner.standalone.WorkflowSmoke", ROOT, args.target_java_home,
|
||||
fixture, mode])
|
||||
run("worker-lifecycle-" + mode, [Path(os.environ["JAVA_HOME"]) / "bin/java", "-cp", classpath,
|
||||
"de.neitzel.fxdesigner.standalone.WorkerLifecycleSmoke", ROOT, fixture])
|
||||
crashes.check()
|
||||
local = fixture / ".neitzel-fx-designer"
|
||||
(local / "runtimes.json").write_text(json.dumps({"schemaVersion":"1.0","runtimes":{"java21":{"build":args.target_java_home,"target":args.target_java_home}}}))
|
||||
tools = [ROOT / ("designer-" + name) / "target" / ("designer-" + name + "-0.1.0-SNAPSHOT.jar") for name in ("target-tools","contracts","preview-api")]
|
||||
@@ -82,3 +87,5 @@ with tempfile.TemporaryDirectory(prefix="designer-connected-") as temporary:
|
||||
"-PideaHome=" + args.idea_home, "-Ddesigner.workflow.fixtures=" + str(base),
|
||||
"-Ddesigner.repository=" + str(ROOT), "-Ddesigner.target.java=" + args.target_java_home,
|
||||
"test"])
|
||||
|
||||
crashes.check()
|
||||
|
||||
@@ -12,6 +12,7 @@ import subprocess
|
||||
import sys
|
||||
import time
|
||||
import zipfile
|
||||
from native_crashes import NativeCrashGuard, descendant_workers, await_worker_exit
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
@@ -22,6 +23,7 @@ args = p.parse_args()
|
||||
output = ROOT / "target/ide-workflow"
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
for mode in ("classpath", "modulepath"):
|
||||
crashes = NativeCrashGuard(ROOT)
|
||||
launch = subprocess.run([sys.executable, ROOT / "scripts/run-fixture-host.py", "intellij",
|
||||
"--check", "--mode", mode, "--java-home", args.java_home,
|
||||
"--target-java-home", args.target_java_home, "--idea-home", args.idea_home],
|
||||
@@ -58,9 +60,12 @@ for mode in ("classpath", "modulepath"):
|
||||
"result": (base / "SUCCESS.txt").read_text()}
|
||||
(output / (mode + ".json")).write_text(json.dumps(proof, indent=2) + "\n")
|
||||
shutil.copyfile(base / "intellij.png", output / (mode + ".png"))
|
||||
print("PASS registered IntelliJ " + mode + " editor and exclusive JavaFX 26 natives", flush=True)
|
||||
finally:
|
||||
workers = descendant_workers(pid)
|
||||
try:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
await_worker_exit(workers)
|
||||
crashes.check()
|
||||
print("PASS registered IntelliJ " + mode + " editor, exclusive JavaFX 26 natives and worker shutdown", flush=True)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"""Keep a passing UI marker from hiding a target JVM's native fatal report."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
|
||||
class NativeCrashGuard:
|
||||
def __init__(self, directory):
|
||||
self.directory = Path(directory)
|
||||
self.before = {p: p.stat().st_mtime_ns for p in self.directory.glob("hs_err_pid*.log")}
|
||||
|
||||
def check(self):
|
||||
reports = [p for p in self.directory.glob("hs_err_pid*.log")
|
||||
if self.before.get(p) != p.stat().st_mtime_ns]
|
||||
if reports:
|
||||
raise AssertionError("Native JVM crash reports: " + ", ".join(map(str, reports)))
|
||||
|
||||
|
||||
def descendant_workers(owner):
|
||||
rows = subprocess.check_output(["ps", "-axo", "pid=,ppid=,command="], text=True)
|
||||
processes = {}
|
||||
for line in rows.splitlines():
|
||||
fields = line.strip().split(None, 2)
|
||||
if len(fields) == 3:
|
||||
processes[int(fields[0])] = (int(fields[1]), fields[2])
|
||||
descendants = {owner}
|
||||
while True:
|
||||
found = {pid for pid, (parent, _) in processes.items() if parent in descendants}
|
||||
if found <= descendants:
|
||||
break
|
||||
descendants |= found
|
||||
return {pid for pid in descendants if pid in processes
|
||||
and "de.neitzel.fxdesigner.target.WorkerMain" in processes[pid][1]}
|
||||
|
||||
|
||||
def await_worker_exit(pids):
|
||||
pending = set(pids)
|
||||
deadline = time.monotonic() + 10
|
||||
while pending and time.monotonic() < deadline:
|
||||
for pid in tuple(pending):
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
except ProcessLookupError:
|
||||
pending.remove(pid)
|
||||
if pending:
|
||||
time.sleep(.05)
|
||||
if pending:
|
||||
raise AssertionError("Workers survived owner shutdown: " + str(sorted(pending)))
|
||||
Reference in New Issue
Block a user