Skip to content
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

Automatic reload for dev-server #1109

Open
pkkummermo opened this issue Nov 11, 2020 · 21 comments
Open

Automatic reload for dev-server #1109

pkkummermo opened this issue Nov 11, 2020 · 21 comments

Comments

@pkkummermo
Copy link

Describe the feature
Quarkus has a nifty dev-server feature, which enables hot deployment with background compilation. This could make it easier to develop, as it'll refresh all endpoints and resources upon browser request if either kotlin/java/resources has any changes without the need to restart the process.

Additional context
Quarkus development mode
Quarkus github dev-mode code

@tipsy
Copy link
Member

tipsy commented Jun 29, 2021

I've been looking at bit at this, and it seems most projects provide a Maven or Gradle plugin to handle this. Some users of Javalin (ok, it's probably just me) use Bazel, so ideally any implementation of this should work across build tools. That might be unrealistic though...

@tomhillgreyridge
Copy link
Contributor

Hi @tipsy . Check #1309 for two short and sweet methods for getting 90% of the way there - SpringLoaded and DCEVM. These both work across build tools as they work at the JVM layer.

Also worth looking at Jooby which I seem to remember uses JBoss Modules to do hot reloading.

@tomhillgreyridge
Copy link
Contributor

Hi @tipsy

I spent some time this morning trying to work out how DCEVM Plugins worked. I've got to the point where I can call arbitrary user code whenever any class in the project changes.

The problem I have is that, in order to pick up new routes or new configuration, I would need to restart the Javalin server and, as soon as I stop the Javalin server, the entire application exits! I don't have enough knowledge of Javalin internals to know how to get round this.

I've therefore put together a prototype in the hope it might give you an idea of how to integrate into Javalin. Right now it is VERY rough and ready

https://github.com/tomhillgreyridge/hotswap

@tipsy
Copy link
Member

tipsy commented Jul 14, 2021

https://github.com/tomhillgreyridge/hotswap

Really nice writeup! I'm on vacation currently, so I don't too much time to look into it, but I can try to assist you here.

The problem I have is that, in order to pick up new routes or new configuration, I would need to restart the Javalin server and, as soon as I stop the Javalin server, the entire application exits! I don't have enough knowledge of Javalin internals to know how to get round this.

Javalin just does Server#start (from Jetty), which I guess does something like Thread.join to keep the application running. We could move that into a separate thread, and do Thread.join on the main thread? That should prevent the main thread from exiting when Javalin (and by extension, it's Jetty Server) stops.

@tomhillgreyridge
Copy link
Contributor

That was my guess as to what was going wrong. I'm not massively familiar with threads so, rather than trying to work out how to adjust the thread join, I have put together what has to count as the hackiest prototype I've done in quite some time!

Basically, I have added a class called AppRunner which starts the App and, if the app exits, goes into a loop with a thread sleep. That means my main thread never exits and so the reload method does indeed get executed.

With that change (and I am in no way suggesting that's an actual solution) it shows that hot reload does indeed work and boy is it fast! I get a 20-50ms restart time for Javalin....

[main] INFO io.javalin.Javalin - 
           __                      __ _
          / /____ _ _   __ ____ _ / /(_)____
     __  / // __ `/| | / // __ `// // // __ \
    / /_/ // /_/ / | |/ // /_/ // // // / / /
    \____/ \__,_/  |___/ \__,_//_//_//_/ /_/

        https://javalin.io/documentation

[main] INFO org.eclipse.jetty.util.log - Logging initialized @938ms to org.eclipse.jetty.util.log.Slf4jLog
[main] INFO io.javalin.Javalin - Starting Javalin ...
[main] INFO io.javalin.Javalin - Listening on http://localhost:7777/
[main] INFO io.javalin.Javalin - Javalin started in 599ms \o/
HOTSWAP AGENT: 10:00:27.509 DEBUG (org.hotswap.agent.command.impl.SchedulerImpl) - Executing executeCommand: initClassLoader(jdk.internal.loader.ClassLoaders$AppClassLoader@368239c8)
HOTSWAP AGENT: 10:00:40.730 DEBUG (org.hotswap.agent.plugin.jvm.ClassInitPlugin) - Adding $$ha$clinit to class: io.javalin.hotswap.TestClass
HOTSWAP AGENT: 10:00:40.736 DEBUG (org.hotswap.agent.plugin.jvm.ClassInitPlugin) - Skipping old field INSTANCE
HOTSWAP AGENT: 10:00:40.737 DEBUG (org.hotswap.agent.plugin.jdk.JdkPlugin) - Flushing io.javalin.hotswap.TestClass from com.sun.beans.introspect.ClassInfo cache
HOTSWAP AGENT: 10:00:40.739 DEBUG (org.hotswap.agent.plugin.jdk.JdkPlugin) - Flushing io.javalin.hotswap.TestClass from ObjectStreamClass caches
HOTSWAP AGENT: 10:00:40.740 DEBUG (org.hotswap.agent.plugin.jdk.JdkPlugin) - Flushing io.javalin.hotswap.TestClass from introspector
HOTSWAP AGENT: 10:00:40.744 TRACE (org.hotswap.agent.plugin.jdk.JdkPlugin) - Removing class from declaredMethodCache.
class reloaded so we need to restart the application
HOTSWAP AGENT: 10:00:40.853 DEBUG (org.hotswap.agent.command.impl.SchedulerImpl) - Executing Command{class='io.javalin.hotswap.App', methodName='reload'}
[Thread-13] INFO io.javalin.Javalin - Stopping Javalin ...
[Thread-13] INFO io.javalin.Javalin - Javalin has stopped
[Thread-13] INFO io.javalin.Javalin - 
           __                      __ _
          / /____ _ _   __ ____ _ / /(_)____
     __  / // __ `/| | / // __ `// // // __ \
    / /_/ // /_/ / | |/ // /_/ // // // / / /
    \____/ \__,_/  |___/ \__,_//_//_//_/ /_/

        https://javalin.io/documentation

[Thread-13] INFO io.javalin.Javalin - Starting Javalin ...
[Thread-13] INFO io.javalin.Javalin - Listening on http://localhost:7777/
[Thread-13] INFO io.javalin.Javalin - Javalin started in 40ms \o/

So I guess we can say that using a DCEVM plugin for hot reload looks both possible and performant and it just needs a design as to how to make it work more "generically" (i.e. without hard coded classes) and in a way which doesn't involve a hacky infinite loop.

Enjoy your holiday. Happy to continue discussion when you're back

@tomhillgreyridge
Copy link
Contributor

Hi @tipsy

I've not had too much time to progress with this, but I did manage to work out how to make the reload plugin "generic". Basically, HotswapAgent comes with a properties file (hotswap-agent.properties) and a PluginConfiguration class which gets injected into your own plugin and can then access properties from the properties file.

As such, I was able to add

javalin.main_class=com.blah.MyClass

to the properties file and then inject into my plugin like this

@Init
lateinit var pluginConfiguration: PluginConfiguration

@Init
lateinit var appClassLoader: ClassLoader

private val mainClassName by lazy { pluginConfiguration.getProperty("javalin.main_class") }

and finally, having both the class name and the application classloader, we can get an instance of the class to call

@OnClassLoadEvent(classNameRegexp = ".*", events = [LoadEvent.REDEFINE])
fun onClassReloaded() {
  val cls = Class.forName(mainClassName,true,appClassLoader)
  val instance = cls.getField("INSTANCE").get(cls)

  scheduler.scheduleCommand(ReflectionCommand(instance,"restart"))
}

I still haven't got rid of the hacky infinite loop and my implementation requires your main class to be a kotlin object at the moment (hence the cls.getField("INSTANCE") stuff, but it does at least work if you fancy discussing online at some point

@tomhillgreyridge
Copy link
Contributor

Oh, and I have updated the repo below with a tidier version which

  • No longer has any dependencies on a hard-coded class - it looks it up from hotswap-agent.properties
  • Watches all resources in src/main/resources and automatically restarts the application on change

https://github.com/tomhillgreyridge/hotswap

My gut feeling is that you might not want this to be a core Javalin feature as I don't think it will work 100% of the time and you'll just get people saying "why doesn't this work with my random self-built dependency injection framework". If we could work out how to get rid of the hacky infinite loop part, I'd be happy to write a tutorial about how people can add it to their own Javalin projects though.

I've now got it working with a reasonable sized Javalin project, complete with HikariCP, Guice, javamail etc so I should be able to get a feel for how well it works by using it "for real"

@tipsy
Copy link
Member

tipsy commented Jul 27, 2021

If we could work out how to get rid of the hacky infinite loop part,

I honestly don't see that as an issue at all, it's a dev server thing after all.

My gut feeling is that you might not want this to be a core Javalin feature as I don't think it will work 100% of the time and you'll just get people saying "why doesn't this work with my random self-built dependency injection framework".

It could be nice to have it as a module? As things are now I don't provide much support for the modules, they are mainly managed by the community.

@ManolisPapadospyridakis
Copy link

ManolisPapadospyridakis commented Aug 29, 2021

Hi all, i am struggling to find an easy solution to automatically reaload my application when i change my code. So, i have write a python script that do that.
Here (https://github.com/ManolisPapadospyridakis/ServerReloader) you can find the script. Feel free to use it. When you run the ServerReloaderMain script you pass 3 arguments 1. path to the src folder, 2. build tool command to compile and run the app 3. an interval for the next monitoring (because i use pooling to the folder to check if there is some changes).
The script execute the command that you provide as second argument so if you use maven it must be something like this (mvn compile exec:java -Dexec.mainClass=HelloWorld). So, first compiles the code and after run your main. Just open a terminal and start the ServerReloaderMain with the appropriate arguments and that's it. Have fun!

@yusukezzz
Copy link

FYI

https://github.com/yusukezzz/javalin-restart-demo

I wrote sample code to apply the method used in micronaut to javalin.
./gradlew run -t to automatically restart after editing code.
(This is not a hot deploy, just a restart 😔 )

Hope it helps for javalin users.

@asad-awadia
Copy link

https://github.com/nikku/amvn this is what I am using to get auto restarts after editing code

@tipsy
Copy link
Member

tipsy commented Sep 25, 2022

@asad-awadia are you happy with that solution? Can you set what it should trigger on? Would you be interested in setting up an example?

@asad-awadia
Copy link

are you happy with that solution?

Yes - was the easiest way to get auto reload on edit for any maven project - works with anything that can be rerun via maven [not javalin specific]

Can you set what it should trigger on

Not without editing the code of the repo itself - eg i changed the watched directory from src/main/java to src/main/kotlin - but there is no way to pass that it in - had to change the source code

Would you be interested in setting up an example?

What would that look like?

Currently I call amvn compile exec:java --watch and just make sure that mvn compile exec:java runs my main function - then amvn will rerun it when the code is edited

@tipsy
Copy link
Member

tipsy commented Sep 25, 2022

What would that look like?

I assumed there would be some config, but If this is all there is I don't think a tutorial makes sense 😄

@kDot
Copy link

kDot commented Dec 16, 2022

Maybe add an info tag here to include this information into your "Interesting issues" collection. Helpful would also be to highlight @asad-awadia answer somehow..

@tipsy tipsy added the INFO label Dec 17, 2022
@tipsy
Copy link
Member

tipsy commented Dec 17, 2022

Thanks, added :)

@mavek87
Copy link

mavek87 commented Feb 2, 2023

Anyone knows a easy way to have hot releoad in javalin using java and gradle (groovy)?

@mapperr
Copy link

mapperr commented Jun 4, 2023

Just to add another option using entr (https://github.com/eradman/entr) to do something like this:

fd | entr -r mvn compile exec:java

where fd automatically ignore gitignored files

@saasvantage
Copy link

what is fd here? Is it from fdclone package?, I am using ubuntu

@mapperr
Copy link

mapperr commented Oct 16, 2023

sorry!

it is a modern find command: https://github.com/sharkdp/fd

@halkhalil
Copy link

fd --exclude 'target' --exclude 'build' . | entr -r sh -c 'mvn verify -pl DemoModule && java -jar ./build/MainApp.jar'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests