Skip to content

Commit 7a623d7

Browse files
Add support for suspending warmup tasks
GitOrigin-RevId: 2b697d9b17e3f8c95f7f2e684e6d094de1ced62d
1 parent 055c4fc commit 7a623d7

File tree

5 files changed

+77
-3
lines changed

5 files changed

+77
-3
lines changed

misk-warmup/api/misk-warmup.api

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
public abstract class misk/warmup/SuspendingWarmupTask : misk/warmup/WarmupTask {
2+
public fun <init> ()V
3+
public fun execute ()V
4+
public abstract fun executeSuspending (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
5+
}
6+
17
public final class misk/warmup/WarmupModule : misk/inject/KAbstractModule {
28
public fun <init> (Ljava/lang/String;Lcom/google/inject/Key;)V
39
}

misk-warmup/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ plugins {
88

99
dependencies {
1010
api(libs.guice)
11+
api(libs.kotlinxCoroutinesCore)
1112
api(project(":misk-inject"))
1213
implementation(libs.guava)
1314
implementation(libs.jakartaInject)
1415
implementation(libs.loggingApi)
15-
implementation(project(":wisp:wisp-logging"))
16+
implementation(project(":misk-api"))
1617
implementation(project(":misk-core"))
18+
implementation(project(":wisp:wisp-logging"))
1719

1820
testImplementation(libs.assertj)
1921
testImplementation(libs.junitApi)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package misk.warmup
2+
3+
import misk.annotation.ExperimentalMiskApi
4+
5+
/**
6+
* @see WarmupTask
7+
*/
8+
@ExperimentalMiskApi
9+
abstract class SuspendingWarmupTask : WarmupTask() {
10+
override fun execute() {
11+
throw UnsupportedOperationException("Suspending warmup task should implement only executeSuspending method")
12+
}
13+
14+
/**
15+
* Perform production-like work to cause caches to be seeded, pools to be filled, and hot spots to
16+
* be compiled. This should return once warmup is complete.
17+
*
18+
* This function will be executed on a dedicated warmup thread.
19+
*/
20+
abstract suspend fun executeSuspending()
21+
}

misk-warmup/src/main/kotlin/misk/warmup/WarmupRunner.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import java.util.concurrent.atomic.AtomicInteger
1010
import jakarta.inject.Inject
1111
import com.google.inject.Provider
1212
import jakarta.inject.Singleton
13+
import kotlinx.coroutines.runBlocking
14+
import misk.annotation.ExperimentalMiskApi
1315

1416
/**
1517
* This class is a health check to defer production traffic until all warmup tasks have completed.
@@ -21,6 +23,7 @@ import jakarta.inject.Singleton
2123
* Note that if a warmup task fails by throwing an exception, that is not fatal. This will report
2224
* itself as healthy, and early call latency might not be as low as it should be.
2325
*/
26+
@OptIn(ExperimentalMiskApi::class)
2427
@Singleton
2528
internal class WarmupRunner @Inject constructor(
2629
private val executorServiceFactory: ExecutorServiceFactory,
@@ -36,8 +39,12 @@ internal class WarmupRunner @Inject constructor(
3639
executorService.submit {
3740
val stopwatch = Stopwatch.createStarted()
3841
try {
39-
val task = taskProvider.get()
40-
task.execute()
42+
when (val task = taskProvider.get()) {
43+
is SuspendingWarmupTask -> runBlocking {
44+
task.executeSuspending()
45+
}
46+
else -> task.execute()
47+
}
4148
logger.info { "Warmup task $name completed after $stopwatch" }
4249
} catch (t: Throwable) {
4350
logger.error(t) { "Warmup task $name crashed after $stopwatch" }

misk-warmup/src/test/kotlin/misk/warmup/WarmupTest.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import java.util.concurrent.BlockingDeque
2222
import java.util.concurrent.LinkedBlockingDeque
2323
import jakarta.inject.Inject
2424
import jakarta.inject.Singleton
25+
import misk.annotation.ExperimentalMiskApi
2526

2627
internal class WarmupTest {
2728
private val events = LinkedBlockingDeque<String>()
@@ -52,6 +53,29 @@ internal class WarmupTest {
5253
.startsWith("Warmup task LoggingWarmupTask completed")
5354
}
5455

56+
@Test
57+
fun `suspending warmup task`() {
58+
startUpAndShutDown(
59+
ServiceModule<LoggingService>(),
60+
WarmupModule<SuspendingLoggingWarmupTask>(),
61+
)
62+
63+
assertThat(events).containsExactly(
64+
"LoggingService startUp",
65+
"SuspendingLoggingWarmupTask created on warmup-0",
66+
"SuspendingLoggingWarmupTask warming on warmup-0 @coroutine#1",
67+
"HealthChecks all passed",
68+
"LoggingService shutDown",
69+
)
70+
71+
assertThat(logCollector.takeMessage(minLevel = Level.INFO))
72+
.isEqualTo("Starting ready service")
73+
assertThat(logCollector.takeMessage(minLevel = Level.INFO))
74+
.isEqualTo("Running warmup tasks: [SuspendingLoggingWarmupTask]")
75+
assertThat(logCollector.takeMessage(minLevel = Level.INFO))
76+
.startsWith("Warmup task SuspendingLoggingWarmupTask completed")
77+
}
78+
5579
@Test
5680
fun `service is healthy even after warmup task throws`() {
5781
startUpAndShutDown(
@@ -116,6 +140,20 @@ internal class WarmupTest {
116140
}
117141
}
118142

143+
@OptIn(ExperimentalMiskApi::class)
144+
@Singleton
145+
class SuspendingLoggingWarmupTask @Inject constructor(
146+
private val events: BlockingDeque<String>
147+
) : SuspendingWarmupTask() {
148+
init {
149+
events += "SuspendingLoggingWarmupTask created on ${Thread.currentThread().name}"
150+
}
151+
152+
override suspend fun executeSuspending() {
153+
events += "SuspendingLoggingWarmupTask warming on ${Thread.currentThread().name}"
154+
}
155+
}
156+
119157
/**
120158
* This test doesn't use `@MiskTest` so we can start up and shut down the injector in the test.
121159
* It also customizes the modules per-test.

0 commit comments

Comments
 (0)