Consider the following small example of a minimal OxApp with a minimal CLI
//> using scala 3.7.3
//> using dep com.softwaremill.ox::core:1.0.0
//> using dep com.github.scopt::scopt:4.1.0
import ox.*
import scopt.OParser
case class Config(
input: String = "",
)
object MinimalApp extends OxApp {
def run(args: Vector[String])(using Ox): ExitCode = {
val builder = OParser.builder[Config]
val parser = {
import builder.*
OParser.sequence(
programName("minimal-app"),
head("minimal-app", "1.0"),
arg[String]("<input>")
.required()
.action((x, c) => c.copy(input = x))
.text("input file path (required)"),
help("help").text("prints this usage text")
)
}
OParser.parse(parser, args.toSeq, Config()) match {
case Some(config) =>
println(s"Running with config: $config")
ExitCode.Success
case None =>
// Command line arguments parsing failed
ExitCode.Failure(2)
}
}
}
This just parses the arguments and returns Success if it can or Failure if it can't.
If you run this with scala-cli, you'll see that it works fine with correct or incorrect arguments. However, if you pass --help, the process hangs deep in ox. It will ignore SIGINT, you have to SIGKILL it to terminate the process.
Using jstack the trace of where it is stuck is:
2025-10-01 19:57:30
Full thread dump OpenJDK 64-Bit Server VM (25+37-jvmci-b01 mixed mode, sharing):
Threads class SMR info:
_java_thread_list=0x00007f80dc001d30, length=16, elements={
0x00007f815002c080, 0x00007f815020ccb0, 0x00007f815020db40, 0x00007f815020eeb0,
0x00007f8150210190, 0x00007f81502113d0, 0x00007f8150213230, 0x00007f8150214c20,
0x00007f8150261110, 0x00007f81502636d0, 0x00007f815047e570, 0x00007f8150480420,
0x00007f60880035a0, 0x00007f608806f070, 0x00007f607c004730, 0x00007f80dc000eb0
}
"main" #3 [2834181] prio=5 os_prio=0 cpu=187.34ms elapsed=21.38s tid=0x00007f815002c080 nid=2834181 waiting on condition [0x00007f815a585000]
java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@25/Native Method)
- parking to wait for <0x00000001001bdc78> (a java.util.concurrent.CompletableFuture$Signaller)
at java.util.concurrent.locks.LockSupport.park(java.base@25/LockSupport.java:223)
at java.util.concurrent.CompletableFuture$Signaller.block(java.base@25/CompletableFuture.java:1885)
at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@25/ForkJoinPool.java:4364)
at java.util.concurrent.ForkJoinPool.managedBlock(java.base@25/ForkJoinPool.java:4310)
at java.util.concurrent.CompletableFuture.waitingGet(java.base@25/CompletableFuture.java:1919)
at java.util.concurrent.CompletableFuture.get(java.base@25/CompletableFuture.java:2093)
at ox.ForkUsingResult.f$proxy1$1(fork.scala:220)
at ox.ForkUsingResult.join(fork.scala:220)
at ox.ForkUsingResult.join$(fork.scala:219)
at ox.fork$package$$anon$7.join(fork.scala:187)
at ox.UnsupervisedFork.joinEither(fork.scala:270)
at ox.UnsupervisedFork.joinEither$(fork.scala:262)
at ox.fork$package$$anon$7.joinEither(fork.scala:187)
at ox.OxApp.main$$anonfun$2(OxApp.scala:54)
at ox.OxApp$$Lambda/0x0000000012048210.applyVoid(Unknown Source)
at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
at ox.unsupervised$package$.runWithCurrentScopeSet$1(unsupervised.scala:56)
at ox.unsupervised$package$.scopedWithCapability(unsupervised.scala:69)
at ox.unsupervised$package$.unsupervised(unsupervised.scala:25)
at ox.unsupervised$package$.unsupervised(unsupervised.scala:22)
at ox.OxApp.main(OxApp.scala:37)
at ox.OxApp.main$(OxApp.scala:31)
at MinimalApp$.main(bug.scala:12)
at MinimalApp.main(bug.scala)
"Reference Handler" #17 [2834195] daemon prio=10 os_prio=0 cpu=0.13ms elapsed=21.37s tid=0x00007f815020ccb0 nid=2834195 waiting on condition [0x00007f80fefce000]
java.lang.Thread.State: RUNNABLE
at java.lang.ref.Reference.waitForReferencePendingList(java.base@25/Native Method)
at java.lang.ref.Reference.processPendingReferences(java.base@25/Reference.java:246)
at java.lang.ref.Reference$ReferenceHandler.run(java.base@25/Reference.java:208)
"Finalizer" #18 [2834196] daemon prio=8 os_prio=0 cpu=0.10ms elapsed=21.37s tid=0x00007f815020db40 nid=2834196 in Object.wait() [0x00007f80fedcc000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait0(java.base@25/Native Method)
- waiting on <0x0000000101002368> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.Object.wait(java.base@25/Object.java:389)
at java.lang.Object.wait(java.base@25/Object.java:351)
at java.lang.ref.ReferenceQueue.remove0(java.base@25/ReferenceQueue.java:137)
at java.lang.ref.ReferenceQueue.remove(java.base@25/ReferenceQueue.java:215)
- locked <0x0000000101002368> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.Finalizer$FinalizerThread.run(java.base@25/Finalizer.java:165)
"Signal Dispatcher" #19 [2834197] daemon prio=9 os_prio=0 cpu=0.40ms elapsed=21.37s tid=0x00007f815020eeb0 nid=2834197 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Service Thread" #20 [2834198] daemon prio=9 os_prio=0 cpu=0.40ms elapsed=21.37s tid=0x00007f8150210190 nid=2834198 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Deflation Thread" #21 [2834199] daemon prio=9 os_prio=0 cpu=0.79ms elapsed=21.37s tid=0x00007f81502113d0 nid=2834199 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"JVMCI-native CompilerThread0" #22 [2834200] daemon prio=9 os_prio=0 cpu=72.41ms elapsed=21.37s tid=0x00007f8150213230 nid=2834200 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
No compile task
"C1 CompilerThread0" #33 [2834201] daemon prio=9 os_prio=0 cpu=49.05ms elapsed=21.37s tid=0x00007f8150214c20 nid=2834201 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
No compile task
"Notification Thread" #40 [2834233] daemon prio=9 os_prio=0 cpu=0.06ms elapsed=21.34s tid=0x00007f8150261110 nid=2834233 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Common-Cleaner" #41 [2834234] daemon prio=8 os_prio=0 cpu=0.15ms elapsed=21.34s tid=0x00007f81502636d0 nid=2834234 in Object.wait() [0x00007f80ca840000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait0(java.base@25/Native Method)
- waiting on <0x000000010113fc38> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.Object.wait(java.base@25/Object.java:389)
at java.lang.ref.ReferenceQueue.remove0(java.base@25/ReferenceQueue.java:123)
at java.lang.ref.ReferenceQueue.remove(java.base@25/ReferenceQueue.java:201)
- locked <0x000000010113fc38> (a java.lang.ref.ReferenceQueue$Lock)
at jdk.internal.ref.CleanerImpl.run(java.base@25/CleanerImpl.java:146)
at java.lang.Thread.runWith(java.base@25/Thread.java:1487)
at java.lang.Thread.run(java.base@25/Thread.java:1474)
at jdk.internal.misc.InnocuousThread.run(java.base@25/InnocuousThread.java:148)
"VirtualThread-unblocker" #42 [2834248] daemon prio=5 os_prio=0 cpu=0.10ms elapsed=21.20s tid=0x00007f815047e570 nid=2834248 runnable [0x00007f80c9f37000]
java.lang.Thread.State: RUNNABLE
at java.lang.VirtualThread.takeVirtualThreadListToUnblock(java.base@25/Native Method)
at java.lang.VirtualThread.unblockVirtualThreads(java.base@25/VirtualThread.java:1507)
at java.lang.VirtualThread$$Lambda/0x000000001200e718.run(java.base@25/Unknown Source)
at java.lang.Thread.runWith(java.base@25/Thread.java:1487)
at java.lang.Thread.run(java.base@25/Thread.java:1474)
at jdk.internal.misc.InnocuousThread.run(java.base@25/InnocuousThread.java:148)
"ForkJoinPool-1-worker-1" #44 [2834249] daemon prio=5 os_prio=0 cpu=76.58ms elapsed=21.20s tid=0x00007f8150480420 nid=2834249 waiting on condition [0x00007f80c9e36000]
java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@25/Native Method)
- parking to wait for <0x0000000100186350> (a java.util.concurrent.ForkJoinPool)
at java.util.concurrent.ForkJoinPool.awaitWork(java.base@25/ForkJoinPool.java:2109)
at java.util.concurrent.ForkJoinPool.deactivate(java.base@25/ForkJoinPool.java:2063)
at java.util.concurrent.ForkJoinPool.runWorker(java.base@25/ForkJoinPool.java:2027)
at java.util.concurrent.ForkJoinWorkerThread.run(java.base@25/ForkJoinWorkerThread.java:187)
"ForkJoinPool-1-worker-2" #47 [2834250] daemon prio=5 os_prio=0 cpu=5.44ms elapsed=21.19s tid=0x00007f60880035a0 nid=2834250 waiting on condition [0x00007f80c9d35000]
java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@25/Native Method)
- parking to wait for <0x0000000100186350> (a java.util.concurrent.ForkJoinPool)
at java.util.concurrent.ForkJoinPool.awaitWork(java.base@25/ForkJoinPool.java:2109)
at java.util.concurrent.ForkJoinPool.deactivate(java.base@25/ForkJoinPool.java:2063)
at java.util.concurrent.ForkJoinPool.runWorker(java.base@25/ForkJoinPool.java:2027)
at java.util.concurrent.ForkJoinWorkerThread.run(java.base@25/ForkJoinWorkerThread.java:187)
"ox-interrupt-hook" #45 [2834252] prio=5 os_prio=0 cpu=0.28ms elapsed=21.12s tid=0x00007f608806f070 nid=2834252 waiting on condition [0x00007f80c9c34000]
java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@25/Native Method)
- parking to wait for <0x000000010099a7f0> (a java.util.concurrent.CompletableFuture$Signaller)
at java.util.concurrent.locks.LockSupport.park(java.base@25/LockSupport.java:223)
at java.util.concurrent.CompletableFuture$Signaller.block(java.base@25/CompletableFuture.java:1885)
at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@25/ForkJoinPool.java:4364)
at java.util.concurrent.ForkJoinPool.managedBlock(java.base@25/ForkJoinPool.java:4310)
at java.util.concurrent.CompletableFuture.waitingGet(java.base@25/CompletableFuture.java:1919)
at java.util.concurrent.CompletableFuture.get(java.base@25/CompletableFuture.java:2093)
at ox.fork$package$$anon$7.cancel(fork.scala:190)
at ox.OxApp.$anonfun$2(OxApp.scala:50)
at ox.OxApp$$Lambda/0x0000000012096f10.run(Unknown Source)
at java.lang.Thread.runWith(java.base@25/Thread.java:1487)
at java.lang.Thread.run(java.base@25/Thread.java:1474)
"ForkJoinPool-1-worker-3" #49 [2834253] daemon prio=5 os_prio=0 cpu=0.52ms elapsed=21.11s tid=0x00007f607c004730 nid=2834253 waiting on condition [0x00007f80c9b33000]
java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@25/Native Method)
- parking to wait for <0x0000000100186350> (a java.util.concurrent.ForkJoinPool)
at java.util.concurrent.ForkJoinPool.awaitWork(java.base@25/ForkJoinPool.java:2109)
at java.util.concurrent.ForkJoinPool.deactivate(java.base@25/ForkJoinPool.java:2063)
at java.util.concurrent.ForkJoinPool.runWorker(java.base@25/ForkJoinPool.java:2027)
at java.util.concurrent.ForkJoinWorkerThread.run(java.base@25/ForkJoinWorkerThread.java:187)
"Attach Listener" #50 [2834603] daemon prio=9 os_prio=0 cpu=0.89ms elapsed=2.52s tid=0x00007f80dc000eb0 nid=2834603 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"VM Thread" os_prio=0 cpu=1.48ms elapsed=21.38s tid=0x00007f8150202ee0 nid=2834194 runnable
"VM Periodic Task Thread" os_prio=0 cpu=0.22ms elapsed=21.38s tid=0x00007f81501eed90 nid=2834193 waiting on condition
"G1 Service" os_prio=0 cpu=0.41ms elapsed=21.38s tid=0x00007f81501dfae0 nid=2834186 runnable
"G1 Refine#0" os_prio=0 cpu=0.05ms elapsed=21.38s tid=0x00007f81501de8f0 nid=2834185 runnable
"G1 Conc#0" os_prio=0 cpu=0.03ms elapsed=21.39s tid=0x00007f815007c340 nid=2834184 runnable
"G1 Main Marker" os_prio=0 cpu=0.04ms elapsed=21.39s tid=0x00007f815007b100 nid=2834183 runnable
"GC Thread#0" os_prio=0 cpu=0.04ms elapsed=21.39s tid=0x00007f8150062f60 nid=2834182 runnable
JNI global refs: 23, weak refs: 0
That's as far as I've gotten debugging, very puzzling.
Consider the following small example of a minimal
OxAppwith a minimal CLIThis just parses the arguments and returns Success if it can or Failure if it can't.
If you run this with scala-cli, you'll see that it works fine with correct or incorrect arguments. However, if you pass
--help, the process hangs deep in ox. It will ignore SIGINT, you have to SIGKILL it to terminate the process.Using
jstackthe trace of where it is stuck is:That's as far as I've gotten debugging, very puzzling.