Skip to content

Hang when using ox and scopt --help #368

Description

@jackkoenig

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions