Skip to content

Commit 403bcea

Browse files
committed
Add a timeout to shutdown
1 parent ab8da33 commit 403bcea

1 file changed

Lines changed: 35 additions & 4 deletions

File tree

core/src/main/scala/ox/OxApp.scala

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ox
33
import scala.util.boundary.*
44
import scala.util.control.NonFatal
55
import java.util.concurrent.ThreadFactory
6+
import scala.concurrent.duration.*
67

78
enum 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

Comments
 (0)