Skip to content

Commit 8e2f484

Browse files
authored
Add shutdown and shutdown await functions (#255)
1 parent ffd97e6 commit 8e2f484

File tree

13 files changed

+389
-49
lines changed

13 files changed

+389
-49
lines changed

BotCommands-core/src/main/kotlin/io/github/freya022/botcommands/api/commands/ratelimit/handler/DefaultRateLimitHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import java.time.Instant
2929
import kotlin.time.Duration.Companion.minutes
3030
import kotlin.time.Duration.Companion.nanoseconds
3131

32-
private val deleteScope = namedDefaultScope("Rate limit message delete", 1)
32+
private val deleteScope = namedDefaultScope("Rate limit message delete", 1, isDaemon = true)
3333

3434
/**
3535
* Default [RateLimitHandler] implementation based on [rate limit scopes][RateLimitScope].

BotCommands-core/src/main/kotlin/io/github/freya022/botcommands/api/core/BContext.kt

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import io.github.freya022.botcommands.api.core.service.annotations.InterfacedSer
1111
import io.github.freya022.botcommands.api.core.service.getService
1212
import io.github.freya022.botcommands.internal.core.exceptions.ServiceException
1313
import net.dv8tion.jda.api.JDA
14+
import java.time.Duration as JavaDuration
15+
import java.util.concurrent.TimeUnit
16+
import kotlin.time.Duration
17+
import kotlin.time.toKotlinDuration
1418

1519
/**
1620
* Main context for BotCommands framework.
@@ -43,7 +47,21 @@ interface BContext {
4347
*
4448
* Fires [BReadyEvent].
4549
*/
46-
READY
50+
READY,
51+
52+
/**
53+
* The shutdown sequence has been initiated.
54+
*
55+
* A [BStatusChangeEvent] if fired, but no dedicated event.
56+
*/
57+
SHUTTING_DOWN,
58+
59+
/**
60+
* The instance has been completely shutdown alongside the underlying JDA instance(s)
61+
*
62+
* Fires [BShutdownEvent].
63+
*/
64+
SHUTDOWN,
4765
}
4866

4967
//region Configs
@@ -99,7 +117,7 @@ interface BContext {
99117
val jda: JDA get() = getService<JDA>()
100118

101119
/**
102-
* Returns the initialization status of the framework.
120+
* Returns the status of the framework.
103121
*
104122
* @see Status
105123
*/
@@ -146,6 +164,71 @@ interface BContext {
146164
*/
147165
fun getExceptionContent(message: String, t: Throwable?, extraContext: Map<String, Any?>): String
148166

167+
/**
168+
* Shuts down this instance of BotCommands, shutting down JDA in the process, then shutting down all executors.
169+
*
170+
* All currently running tasks will not be interrupted, unless [shutdownNow] is called.
171+
*
172+
* @see JDA.shutdown
173+
* @see shutdownNow
174+
*/
175+
fun shutdown()
176+
177+
/**
178+
* Immediately shuts down this instance of BotCommands, shutting down JDA in the process, then shutting down all executors.
179+
*
180+
* All currently running tasks will be interrupted, and all coroutine scopes are canceled.
181+
*
182+
* @see JDA.shutdownNow
183+
*/
184+
fun shutdownNow()
185+
186+
/**
187+
* Blocks the current thread until the [status] is set to [SHUTDOWN][BContext.Status.SHUTDOWN].
188+
*
189+
* This will wait indefinitely, specify a duration if you want a timeout.
190+
*
191+
* Unless [shutdownNow] has been used, the shutdown time depends on the amount of requests queued in JDA,
192+
* and the amount of tasks submitted to the various executors.
193+
*
194+
* @return Always `true`
195+
*/
196+
fun awaitShutdown(): Boolean = awaitShutdown(Duration.INFINITE)
197+
198+
/**
199+
* Blocks the current thread until the [status] is set to [SHUTDOWN][BContext.Status.SHUTDOWN],
200+
* or until the timeout has been reached.
201+
*
202+
* Unless [shutdownNow] has been used, the shutdown time depends on the amount of requests queued in JDA,
203+
* and the amount of tasks submitted to the various executors.
204+
*
205+
* @return `true` if the shutdown finished before the timeout, `false` if the timeout was reached
206+
*/
207+
fun awaitShutdown(timeout: JavaDuration): Boolean = awaitShutdown(timeout.toKotlinDuration())
208+
209+
/**
210+
* Blocks the current thread until the [status] is set to [SHUTDOWN][BContext.Status.SHUTDOWN],
211+
* or until the timeout has been reached.
212+
*
213+
* Unless [shutdownNow] has been used, the shutdown time depends on the amount of requests queued in JDA,
214+
* and the amount of tasks submitted to the various executors.
215+
*
216+
* @return `true` if the shutdown finished before the timeout, `false` if the timeout was reached
217+
*/
218+
fun awaitShutdown(timeout: Duration): Boolean
219+
220+
/**
221+
* Blocks the current thread until the [status] is set to [SHUTDOWN][BContext.Status.SHUTDOWN],
222+
* or until the timeout has been reached.
223+
*
224+
* Unless [shutdownNow] has been used, the shutdown time depends on the amount of requests queued in JDA,
225+
* and the amount of tasks submitted to the various executors.
226+
*
227+
* @return `true` if the shutdown finished before the timeout, `false` if the timeout was reached
228+
*/
229+
fun awaitShutdown(timeout: Long, unit: TimeUnit): Boolean =
230+
awaitShutdown(JavaDuration.of(timeout, unit.toChronoUnit()))
231+
149232
/**
150233
* Returns the [TextCommandsContext] service.
151234
*

BotCommands-core/src/main/kotlin/io/github/freya022/botcommands/api/core/config/BConfig.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.github.freya022.botcommands.api.core.config
22

33
import io.github.freya022.botcommands.api.ReceiverConsumer
44
import io.github.freya022.botcommands.api.commands.text.annotations.Hidden
5+
import io.github.freya022.botcommands.api.core.BContext
56
import io.github.freya022.botcommands.api.core.BotOwners
67
import io.github.freya022.botcommands.api.core.annotations.BEventListener
78
import io.github.freya022.botcommands.api.core.requests.PriorityGlobalRestRateLimiter
@@ -133,6 +134,18 @@ interface BConfigProps {
133134
val ignoreRestRateLimiter: Boolean
134135

135136
val classGraphProcessors: List<ClassGraphProcessor>
137+
138+
/**
139+
* Whether to use a [shutdown hook][Runtime.addShutdownHook] to call [BContext.shutdownNow] when the JVM is exiting **gracefully**.
140+
*
141+
* If disabled you'll have to shut it down yourself.
142+
*
143+
* Default: `true`
144+
*
145+
* Spring property: `botcommands.core.enableShutdownHook`
146+
*/
147+
@ConfigurationValue("botcommands.core.enableShutdownHook", defaultValue = "true")
148+
val enableShutdownHook: Boolean
136149
}
137150

138151
/**
@@ -169,6 +182,8 @@ class BConfigBuilder : BConfigProps {
169182

170183
override val classGraphProcessors: MutableList<ClassGraphProcessor> = arrayListOf()
171184

185+
override var enableShutdownHook: Boolean = true
186+
172187
private val _configs: MutableMap<Class<out IConfig>, IConfig> = hashMapOf()
173188

174189
val eventManagerConfig = BEventManagerConfigBuilder()
@@ -338,6 +353,7 @@ class BConfigBuilder : BConfigProps {
338353
override val ignoredEventIntents = this@BConfigBuilder.ignoredEventIntents.toImmutableSet()
339354
override val ignoreRestRateLimiter = this@BConfigBuilder.ignoreRestRateLimiter
340355
override val classGraphProcessors = this@BConfigBuilder.classGraphProcessors.toImmutableList()
356+
override val enableShutdownHook = this@BConfigBuilder.enableShutdownHook
341357
override val eventManagerConfig = this@BConfigBuilder.eventManagerConfig.build()
342358
override val serviceConfig = this@BConfigBuilder.serviceConfig.build()
343359
override val databaseConfig = this@BConfigBuilder.databaseConfig.build()

BotCommands-core/src/main/kotlin/io/github/freya022/botcommands/api/core/db/Database.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ internal fun <R> Database.withStatementJava(sql: String, readOnly: Boolean = fal
166166
}
167167

168168
@PublishedApi
169-
internal val dbLeakScope = namedDefaultScope("Connection leak watcher", 1)
169+
internal val dbLeakScope = namedDefaultScope("Connection leak watcher", 1, isDaemon = true)
170170

171171
private val currentTransaction = ThreadLocal<Transaction>()
172172

@@ -445,4 +445,4 @@ internal suspend inline fun <R> Database.withReusedConnection(readOnly: Boolean,
445445
if (connection != null)
446446
return block(connection)
447447
return fetchConnection(readOnly).use(block)
448-
}
448+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.github.freya022.botcommands.api.core.events
2+
3+
import io.github.freya022.botcommands.api.core.BContext
4+
5+
/**
6+
* Event fired after the framework status changed to [SHUTDOWN][BContext.Status.SHUTDOWN].
7+
*/
8+
class BShutdownEvent internal constructor(context: BContext) : BEvent(context)

BotCommands-core/src/main/kotlin/io/github/freya022/botcommands/api/core/utils/Utils.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ fun <R> withResource(url: String, block: (InputStream) -> R): R {
7575
*
7676
* @param name The base name of the threads and coroutines, will be prefixed by the number if [corePoolSize] > 1
7777
* @param corePoolSize The number of threads to keep in the pool, even if they are idle, must not be negative
78+
* @param isDaemon If the new threads are daemons, useful for background tasks that should not prevent the JVM from exiting.
7879
* @param job The parent job used for coroutines which can be used to cancel all children, uses [SupervisorJob] by default
7980
* @param errorHandler The [CoroutineExceptionHandler] used for handling uncaught exceptions,
8081
* uses a logging handler which cancels the parent job on [Error] by default
@@ -83,6 +84,7 @@ fun <R> withResource(url: String, block: (InputStream) -> R): R {
8384
fun namedDefaultScope(
8485
name: String,
8586
corePoolSize: Int,
87+
isDaemon: Boolean = false,
8688
job: Job? = null,
8789
errorHandler: CoroutineExceptionHandler? = null,
8890
context: CoroutineContext = EmptyCoroutineContext
@@ -99,6 +101,8 @@ fun namedDefaultScope(
99101
} else {
100102
this.name = "$name ${count.getAndIncrement()}"
101103
}
104+
105+
this.isDaemon = isDaemon
102106
}
103107
}
104108

0 commit comments

Comments
 (0)