-
-
Notifications
You must be signed in to change notification settings - Fork 5
Add hot restarting #232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add hot restarting #232
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
b113ef7
Added an internal application start event
freya022 ee36ca6
Add restarter module
freya022 cbec51f
Add implementation of RestartClassLoaderAdapterFactory
freya022 97f8907
test-bot: Use restarter module
freya022 c0c585a
Remove unused code
freya022 a7bb8e3
Rename config file
freya022 d500a0e
Add README.md
freya022 719c044
Clear cache of method accessors on restart
freya022 6be361d
Update config
freya022 4a3c762
Add missing badge
freya022 d103d09
Reset AppEmojisLoader on (re)start
freya022 d87363c
Add admonition about the feature being development only
freya022 b3f3ba9
Use same docs model for opt-in annotation
freya022 64358a1
Update comments
freya022 ee2f9a9
Simplify `lateinit` `Future` with a pre-completed future
freya022 a8e1418
Log invalid key resets at debug
freya022 91218ab
Cleanup
freya022 f183e0c
Update README.md
freya022 30e2d7f
Expose `ImmediateRestartException`
freya022 90e513b
test-bot: Rethrow `ImmediateRestartException`
freya022 9ae7dbb
Update README.md
freya022 0bf33b5
Cleanup
freya022 d15b1f0
Several improvements
freya022 7a3bebf
Remove unused JDA dependency
freya022 3b4cdf2
Move `@ExperimentalRestartApi` to package
freya022 e796b24
Private `ImmediateRestartException` ctor
freya022 56d1d58
Add visibility modifiers
freya022 f781870
Update README
freya022 5a8c7bc
Update README
freya022 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| [bc-module-maven-central-shield]: https://img.shields.io/maven-central/v/io.github.freya022/BotCommands-restarter?label=Maven%20central&logo=apachemaven&versionPrefix=3 | ||
| [bc-module-maven-central-link]: https://central.sonatype.com/artifact/io.github.freya022/BotCommands-restarter | ||
|
|
||
| # BotCommands module - Hot restarter | ||
| When you build changes of your code, this modules restarts your app automatically, in the same JVM, | ||
| leading to much faster restarts, as it doesn't need to recompile most of the code. | ||
|
|
||
| > [!WARNING] | ||
| > If you are using Spring, use [`spring-boot-devtools`](https://docs.spring.io/spring-boot/reference/using/devtools.html) instead. | ||
|
|
||
| ## Installing | ||
| [![BotCommands-restarter on maven central][bc-module-maven-central-shield] ][bc-module-maven-central-link] | ||
|
|
||
| ### Maven | ||
| ```xml | ||
| <dependencies> | ||
| <dependency> | ||
| <groupId>io.github.freya022</groupId> | ||
| <artifactId>BotCommands-restarter</artifactId> | ||
| <version>VERSION</version> | ||
| </dependency> | ||
| </dependencies> | ||
| ``` | ||
|
|
||
| ### Gradle | ||
| ```gradle | ||
| repositories { | ||
| mavenCentral() | ||
| } | ||
|
|
||
| dependencies { | ||
| implementation("io.github.freya022:BotCommands-restarter:VERSION") | ||
| } | ||
| ``` | ||
|
|
||
| ### Snapshots | ||
|
|
||
| To use the latest, unreleased changes, see [SNAPSHOTS.md](../SNAPSHOTS.md). | ||
|
|
||
| ## Usage | ||
| You can enable the feature by doing so, after which, every build will restart your application. | ||
|
|
||
| > [!NOTE] | ||
| > You should minimize the amount of code executed before calling `BotCommandsRestarter.initialize`, | ||
| > as it will run twice on startup, then everytime it is restarted. | ||
|
|
||
| > [!IMPORTANT] | ||
| > You must only use this feature during development, here are a few ways to do so: | ||
| > - Using a program argument like `--dev` then reading it from `args` | ||
| > - Using a configuration file with a `IS_DEV` property | ||
| > - Using an environment variable | ||
|
|
||
| ### Kotlin | ||
| ```kotlin | ||
| fun main(args: Array<out String>) { | ||
| // You should enable this only during development | ||
| @OptIn(ExperimentalRestartApi::class) | ||
| BotCommandsRestarter.initialize(args) { | ||
| // Optional configuration | ||
| } | ||
|
|
||
| // ... | ||
| BotCommands.create { | ||
| // ... | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Java | ||
| ```java | ||
| void main(String[] args) { | ||
| // You should enable this only during development | ||
| BotCommandsRestarter.initialize(args, builder -> { | ||
| // Optional configuration | ||
| }); | ||
|
|
||
| // ... | ||
| BotCommands.create(config -> { | ||
| // ... | ||
| }); | ||
| } | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import dev.freya02.botcommands.plugins.configureJarArtifact | ||
|
|
||
| plugins { | ||
| id("repositories-conventions") | ||
| id("kotlin-conventions") | ||
| id("publish-conventions") | ||
| id("dokka-conventions") | ||
| } | ||
|
|
||
| dependencies { | ||
| api(projects.botCommandsCore) | ||
|
|
||
| // Logging | ||
| implementation(libs.kotlin.logging) | ||
| } | ||
|
|
||
| kotlin { | ||
| compilerOptions { | ||
| freeCompilerArgs.addAll( | ||
| "-opt-in=dev.freya02.botcommands.restarter.api.annotations.ExperimentalRestartApi", | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| publishedProjectEnvironment { | ||
| configureJarArtifact( | ||
| artifactId = "BotCommands-restarter", | ||
| description = "Enables restarting your bot on the same JVM during development.", | ||
| url = "https://github.com/freya022/BotCommands/tree/3.X/BotCommands-restarter", | ||
| ) | ||
| } |
44 changes: 44 additions & 0 deletions
44
...s-restarter/src/main/kotlin/dev/freya02/botcommands/restarter/api/BotCommandsRestarter.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package dev.freya02.botcommands.restarter.api | ||
|
|
||
| import dev.freya02.botcommands.restarter.api.BotCommandsRestarter.initialize | ||
| import dev.freya02.botcommands.restarter.api.annotations.ExperimentalRestartApi | ||
| import dev.freya02.botcommands.restarter.api.config.RestarterConfigBuilder | ||
| import dev.freya02.botcommands.restarter.api.exceptions.ImmediateRestartException | ||
| import dev.freya02.botcommands.restarter.internal.Restarter | ||
| import io.github.freya022.botcommands.api.ReceiverConsumer | ||
|
|
||
| /** | ||
| * Entry point for the "hot restart" feature. | ||
| * | ||
| * @see initialize | ||
| */ | ||
| @ExperimentalRestartApi | ||
| object BotCommandsRestarter { | ||
|
|
||
| /** | ||
| * Enables hot restarting for this application. All changes to your code will restart the application. | ||
| * | ||
| * This method will intentionally throw [ImmediateRestartException] on the first run, | ||
| * if it is caught, you must rethrow it. | ||
| * | ||
| * It is recommended to run this function as soon as possible to avoid running the same code twice on startup. | ||
| * | ||
| * **Note:** If you configure your logger programmatically, it must be done before calling this function. | ||
| * | ||
| * @param args The program arguments, they will be passed back to the main function upon restarting | ||
| * @param configBuilder To further configure the feature, this will only run once per application | ||
| */ | ||
| @JvmStatic | ||
| @JvmOverloads | ||
| fun initialize(args: Array<out String>, configBuilder: ReceiverConsumer<RestarterConfigBuilder> = {}) { | ||
| if (!Restarter.isInitialized) { | ||
| // 1st restart | ||
| val config = RestarterConfigBuilder.create(args) | ||
| .apply(configBuilder) | ||
| .build() | ||
| Restarter.initialize(config) | ||
| } | ||
|
|
||
| // After 1st restart | ||
| } | ||
| } |
31 changes: 31 additions & 0 deletions
31
...c/main/kotlin/dev/freya02/botcommands/restarter/api/annotations/ExperimentalRestartApi.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package dev.freya02.botcommands.restarter.api.annotations | ||
|
|
||
| import kotlin.annotation.AnnotationTarget.* | ||
|
|
||
| /** | ||
| * Opt-in marker annotation for "hot restart" APIs that are considered experimental and are not subject to compatibility guarantees: | ||
| * The behavior of such API may be changed or the API may be removed completely in any further release. | ||
| * | ||
| * Please create an issue or join the Discord server if you encounter a problem or want to submit feedback. | ||
| * | ||
| * Any usage of a declaration annotated with `@ExperimentalRestartApi` must be accepted either by | ||
| * annotating that usage with the [@OptIn][OptIn] annotation, e.g. `@OptIn(ExperimentalRestartApi::class)`, | ||
| * or by using the compiler argument `-opt-in=dev.freya02.botcommands.restarter.api.ExperimentalRestartApi`. | ||
| */ | ||
| @RequiresOptIn(level = RequiresOptIn.Level.ERROR) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| @Target( | ||
| CLASS, | ||
| ANNOTATION_CLASS, | ||
| PROPERTY, | ||
| FIELD, | ||
| LOCAL_VARIABLE, | ||
| VALUE_PARAMETER, | ||
| CONSTRUCTOR, | ||
| FUNCTION, | ||
| PROPERTY_GETTER, | ||
| PROPERTY_SETTER, | ||
| TYPEALIAS | ||
| ) | ||
| @MustBeDocumented | ||
| annotation class ExperimentalRestartApi |
74 changes: 74 additions & 0 deletions
74
...restarter/src/main/kotlin/dev/freya02/botcommands/restarter/api/config/RestarterConfig.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package dev.freya02.botcommands.restarter.api.config | ||
|
|
||
| import dev.freya02.botcommands.restarter.api.BotCommandsRestarter | ||
| import dev.freya02.botcommands.restarter.api.annotations.ExperimentalRestartApi | ||
| import io.github.freya022.botcommands.internal.core.config.ConfigDSL | ||
| import java.time.Duration as JavaDuration | ||
| import kotlin.time.Duration | ||
| import kotlin.time.Duration.Companion.seconds | ||
| import kotlin.time.toJavaDuration | ||
| import kotlin.time.toKotlinDuration | ||
|
|
||
| @ExperimentalRestartApi | ||
| interface RestarterConfig { | ||
|
|
||
| /** | ||
| * The program arguments passed to the main function upon restarting. | ||
| */ | ||
| val startArgs: List<String> | ||
|
|
||
| /** | ||
| * The time to wait before assuming all changes were compiled, | ||
| * so the application can be restarted with the new changes. | ||
| * | ||
| * Default: 1 second | ||
| */ | ||
| val restartDelay: Duration | ||
|
|
||
| /** | ||
| * Returns the time to wait before assuming all changes were compiled, | ||
| * so the application can be restarted with the new changes. | ||
| * | ||
| * Default: 1 second | ||
| */ | ||
| fun getRestartDelay(): JavaDuration = restartDelay.toJavaDuration() | ||
| } | ||
|
|
||
| /** | ||
| * Builder of [RestarterConfig]. | ||
| * | ||
| * @see [BotCommandsRestarter.initialize] | ||
| */ | ||
| @ConfigDSL | ||
| @ExperimentalRestartApi | ||
| class RestarterConfigBuilder private constructor( | ||
| override val startArgs: List<String>, | ||
| ) : RestarterConfig { | ||
|
|
||
| override var restartDelay: Duration = 1.seconds | ||
|
|
||
| /** | ||
| * Sets the time to wait before assuming all changes were compiled, | ||
| * so the application can be restarted with the new changes. | ||
| * | ||
| * Default: 1 second | ||
| */ | ||
| fun setRestartDelay(delay: JavaDuration): RestarterConfigBuilder { | ||
| this.restartDelay = delay.toKotlinDuration() | ||
| return this | ||
| } | ||
|
|
||
| @JvmSynthetic | ||
| internal fun build(): RestarterConfig = object : RestarterConfig { | ||
| override val startArgs = this@RestarterConfigBuilder.startArgs | ||
| override val restartDelay = this@RestarterConfigBuilder.restartDelay | ||
| } | ||
|
|
||
| internal companion object { | ||
|
|
||
| @JvmSynthetic | ||
| internal fun create(args: Array<out String>): RestarterConfigBuilder { | ||
| return RestarterConfigBuilder(args.toList()) | ||
| } | ||
| } | ||
| } |
35 changes: 35 additions & 0 deletions
35
...main/kotlin/dev/freya02/botcommands/restarter/api/exceptions/ImmediateRestartException.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package dev.freya02.botcommands.restarter.api.exceptions | ||
|
|
||
| import java.lang.reflect.InvocationTargetException | ||
|
|
||
| /** | ||
| * Exception thrown intentionally after enabling the hot restart feature. | ||
| * | ||
| * This exception must propagate to the main method, and must not be caught, if it is, you must rethrow it. | ||
| */ | ||
| class ImmediateRestartException private constructor() : RuntimeException("Dummy exception to stop the execution of the first main thread") { | ||
|
|
||
| internal companion object { | ||
| @JvmSynthetic | ||
| internal fun throwAndHandle(): Nothing { | ||
| val currentThread = Thread.currentThread() | ||
| currentThread.uncaughtExceptionHandler = ExpectedRestartExceptionHandler(currentThread.uncaughtExceptionHandler) | ||
| throw ImmediateRestartException() | ||
| } | ||
| } | ||
|
|
||
| private class ExpectedRestartExceptionHandler(private val delegate: Thread.UncaughtExceptionHandler?) : Thread.UncaughtExceptionHandler { | ||
|
|
||
| override fun uncaughtException(t: Thread, e: Throwable) { | ||
| if (e is ImmediateRestartException || (e is InvocationTargetException && e.targetException is ImmediateRestartException)) { | ||
| return | ||
| } | ||
|
|
||
| if (delegate != null) { | ||
| delegate.uncaughtException(t, e) | ||
| } else { | ||
| e.printStackTrace() | ||
| } | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.