Skip to content

Fix GUI startup freeze, JSON crash, and WebSocket thread bugs#5

Open
JoOkuma wants to merge 4 commits into
mainfrom
fix/gui-startup-and-json-error
Open

Fix GUI startup freeze, JSON crash, and WebSocket thread bugs#5
JoOkuma wants to merge 4 commits into
mainfrom
fix/gui-startup-and-json-error

Conversation

@JoOkuma

@JoOkuma JoOkuma commented Apr 10, 2026

Copy link
Copy Markdown
Member

Problem

Three distinct bugs were causing the plugin to be unusable on first launch and during tracking:

  1. GUI freeze on startup (#243, server connection issue #4): The JavaFX Application Thread was blocked inside MainApp while CondaEnvironmentFinder waited for user input via lock.wait(). Because the JAT was stalled, the loading screen never rendered, and dialogs couldn't receive events — the window appeared frozen.

  2. Crash when server sends a plain-text error (error running ultrack plugging inside fiji ultrack#160): Python's raise_error() sends a raw traceback string over the WebSocket. JavaConnector.onMessageConsumer passed this directly to gson.fromJson(..., JsonObject.class), which threw JsonSyntaxException and crashed the message handler.

  3. GUI freeze during WebSocket connect: UltrackConnector.connectToWebsocket called latch.await() on the calling thread. Since connectToUltrackWebsocket is invoked from JavaScript (JavaFX thread), this blocked the JAT during the WebSocket handshake — the same class of bug as Minor refactor #1.

Changes

MainApp.java

  • Show the loading screen immediately on startup.
  • Move CondaEnvironmentFinder.getUltrackPath() to a daemon background thread so the JAT is never blocked.
  • Fix onUpdateCondaEnv: restore interrupt flag with Thread.currentThread().interrupt() instead of wrapping in RuntimeException.

JavaConnector.java

  • Wrap gson.fromJson() in a try-catch; route plain-text / malformed responses to the error log instead of crashing.
  • Add jsonObject.has() guards before accessing "err_log", "std_log", and "status" keys to prevent NPE on partial JSON.
  • Guard startUltrackServer against port == -1 (returned when ultrackPath is null) so the JS polling loop doesn't spin on 127.0.0.1:-1/config/available forever.

UltrackConnector.java

  • Move latch.await() + c.send() into a daemon background thread so the WebSocket handshake no longer blocks the JavaFX Application Thread.
  • Add null/empty check on ultrackPath at the start of startServer() with a clear error dialog.

CondaEnvironmentFinder.java

  • Switch updateCondaEnvironments from Runtime.exec(String) to ProcessBuilder so conda paths containing spaces are handled correctly on Windows.

ultrack_server.js

  • Guard against undefined fetch response data.
  • Add .catch() to the polling loop so network errors are logged rather than silently swallowed.

Related issues

Fixes royerlab/ultrack#160, royerlab/ultrack#243, #4

JoOkuma added 2 commits April 10, 2026 11:38
The GUI would freeze on first launch because the JavaFX Application Thread
was blocked inside MainApp while CondaEnvironmentFinder waited for user
input (lock.wait).  Move the conda-path lookup to a daemon thread so the
loading screen renders immediately and Swing dialogs can receive events.

JavaConnector crashed with JsonSyntaxException when the Python server sent
a plain-text error message instead of a JSON object (e.g. an unhandled
traceback).  Wrap the Gson parse in a try-catch and route non-JSON payloads
to the error log.

Additional hardening:
- UltrackConnector.startServer: guard against a null/empty ultrackPath
  before launching the subprocess, showing a clear error dialog instead of
  a silent NullPointerException.
- CondaEnvironmentFinder.updateCondaEnvironments: switch from
  Runtime.exec(String) to ProcessBuilder so conda paths containing spaces
  are handled correctly on Windows.
- ultrack_server.js: guard against undefined fetch response data and add a
  .catch() so network errors during server polling are logged rather than
  silently swallowed.

Fixes royerlab/ultrack#160, royerlab/ultrack#243, #4
…-1 race

- UltrackConnector.connectToWebsocket: latch.await() was called on the
  calling thread; since connectToUltrackWebsocket is invoked from JS
  (JavaFX Application Thread), this froze the UI during the WebSocket
  handshake — same class of bug as the startup freeze.  The await+send
  is now done in a daemon background thread.

- JavaConnector.onMessageConsumer: jsonObject.get("err_log"),
  "std_log", and "status" had no null checks.  If the server sends JSON
  that omits any of these keys, a NullPointerException crashed the
  message handler silently.  Replaced with jsonObject.has() guards.

- JavaConnector.startUltrackServer: when startServer() returned early
  because ultrackPath was null, port stayed -1.  The method still called
  javascriptConnector.call("setPort", -1), causing the JS polling loop
  to spin on 127.0.0.1:-1/config/available indefinitely.  Added an
  early-return guard on port == -1.

- MainApp.onUpdateCondaEnv: InterruptedException was wrapped in
  RuntimeException and re-thrown, which crashed the background thread
  silently instead of restoring the interrupt flag.
@JoOkuma JoOkuma changed the title Fix plugin startup freeze and JSON parsing crash Fix GUI startup freeze, JSON crash, and WebSocket thread bugs Apr 13, 2026
JoOkuma added 2 commits April 13, 2026 08:39
UltrackConnector.connectToWebsocket: if the server isn't listening when
c.connect() fires, onOpen is never called so the CountDownLatch stays at
1 and the background connect thread blocks forever.  Add a 30-second
timeout and an isOpen guard so the thread always exits — either by
sending the message, reporting a timeout error, or silently returning
when onErrorConsumer has already surfaced the failure.

ultrack_server.js: the viewButton fetch had no null guard on the response
data and no .catch(), so a non-OK response or network error would
silently drop the view request.  Mirror the same pattern already applied
to the server-polling loop.
MainApp.java
- onLoadUltrackPath: keep a reference to the ChangeListener and remove it
  before adding a new one, so repeated calls (conda env change) don't
  accumulate stale listeners that re-wire javaConnector on every page load.
- Replace 'assert url != null' with a proper null check + error dialog;
  assertions are disabled at runtime by default and would silently pass.

JavaConnector.java
- stopUltrackServer: guard against null ultrackConnector so calling stop
  before the server is ever started doesn't throw NPE.

CondaEnvironmentFinder.java
- getUltrackPath: convert tail-recursive implementation to an iterative
  while-loop; users who repeatedly pick an environment without ultrack
  installed would otherwise overflow the call stack.

ultrack_server.js
- Declare all implicit globals with let/const so they are properly scoped
  rather than silently leaking onto the window object:
  connection_successfull, available_configs, link, config, human_name,
  inputs, input, value, id, prev, json, images_json, image_options,
  available, experimentJson, xml.

pom.xml / tests
- Add JUnit 5 dependency (test scope).
- UltrackConnectorTest: isPortOpen utility and null/empty-path startServer
  guards.
- CondaEnvironmentFinderTest: getUltrackPath resolves executable in
  bin/ultrack and Scripts/ultrack.exe without opening a dialog.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

error running ultrack plugging inside fiji

1 participant