@@ -3,6 +3,7 @@ package ox
33import scala .util .boundary .*
44import scala .util .control .NonFatal
55import java .util .concurrent .ThreadFactory
6+ import scala .concurrent .duration .*
67
78enum ExitCode (val code : Int ):
89 case Success extends ExitCode (0 )
@@ -47,7 +48,11 @@ trait OxApp:
4748 }
4849
4950 // on shutdown, the above fork is cancelled, causing interruption
50- val interruptThread = new Thread (() => cancellableMainFork.cancel().discard)
51+ val interruptThread =
52+ new Thread (() =>
53+ if timeoutOption(settings.shutdownTimeout)(cancellableMainFork.cancel()).isEmpty then
54+ Console .err.println(s " Clean shutdown timed out after ${settings.shutdownTimeout}, exiting. " )
55+ )
5156 interruptThread.setName(" ox-interrupt-hook" )
5257 mountShutdownHook(interruptThread)
5358
@@ -95,10 +100,36 @@ object OxApp:
95100 interruptedExitCode : ExitCode ,
96101 handleInterruptedException : InterruptedException => Unit ,
97102 handleException : Throwable => Unit ,
98- threadFactory : Option [ThreadFactory ]
99- )
103+ threadFactory : Option [ThreadFactory ],
104+ shutdownTimeout : FiniteDuration
105+ ):
106+ // required for binary compatibility
107+ def this (
108+ interruptedExitCode : ExitCode ,
109+ handleInterruptedException : InterruptedException => Unit ,
110+ handleException : Throwable => Unit ,
111+ threadFactory : Option [ThreadFactory ]
112+ ) = this (interruptedExitCode, handleInterruptedException, handleException, threadFactory, 10 .seconds)
113+
114+ // required for binary compatibility
115+ def copy (
116+ interruptedExitCode : ExitCode ,
117+ handleInterruptedException : InterruptedException => Unit ,
118+ handleException : Throwable => Unit ,
119+ threadFactory : Option [ThreadFactory ]
120+ ): Settings = Settings (interruptedExitCode, handleInterruptedException, handleException, threadFactory, shutdownTimeout)
121+ end Settings
100122
101123 object Settings :
124+ // required for binary compatibility
125+ def apply (
126+ interruptedExitCode : ExitCode ,
127+ handleInterruptedException : InterruptedException => Unit ,
128+ handleException : Throwable => Unit ,
129+ threadFactory : Option [ThreadFactory ]
130+ ): Settings =
131+ Settings (interruptedExitCode, handleInterruptedException, handleException, threadFactory, 10 .seconds)
132+
102133 val DefaultLogException : Throwable => Unit = (t : Throwable ) =>
103134 val defaultHandler = Thread .getDefaultUncaughtExceptionHandler
104135 if defaultHandler != null then defaultHandler.uncaughtException(Thread .currentThread(), t) else t.printStackTrace()
@@ -111,7 +142,7 @@ object OxApp:
111142 case _ => logException(t2)
112143
113144 val Default : Settings =
114- Settings (ExitCode .Success , defaultHandleInterruptedException(DefaultLogException ), DefaultLogException , None )
145+ Settings (ExitCode .Success , defaultHandleInterruptedException(DefaultLogException ), DefaultLogException , None , 10 .seconds )
115146 end Settings
116147
117148 /** Simple variant of OxApp does not pass command line arguments and exits with exit code 0 if no exceptions were thrown. */
0 commit comments