Interactive / hot reload / live reload workflows

Are there any interactive / live tools for Scala that allow redefining methods without restarting a running program?

For a bit more context, I am thinking of long running programs with lots of state that you don’t want to lose by restarting, like a game engine or data analysis pipeline. You want to make a change to behaviour while preserving current state.

Some language ecosystems have developed interactive / live reload / hot reload techniques that make this possible, so I am curious if this exists for Scala.

(I recently asked this on Discord as well, but I thought posting here may solicit additional responses.)

2 Likes

I’m not aware of any tooling to do this easily – others might come up with options, but I haven’t noticed any. It’s a just-plain-challenging problem in the JVM and similar environments.

I’m pretty sure it can be done, by using class loaders and encapsulating the hot-swappable code, but there are inevitably limits to how much you can do there without compromising type safety and the like, since it means that you’re making runtime changes outside the scope of the compiler. I wouldn’t recommend doing it casually: it wants really careful upfront design of what you’re trying to accomplish and where the boundaries are.

I’ve done stuff like this in C#/.NET, a million years ago (when I was building what amounted to a container-ish middleware engine), and I’m reasonably confident it can be done on the JVM. But it’s not a common use case, so I’m not sure there are any libraries to make it straightforward.

I should also note: the same goals can often be accomplished other ways. For example, this sort of warm-update isn’t rare in the Akka world, where the application and its state is distributed across multiple nodes. You don’t swap out methods per se, but you can bring up a node on a newer version of the application, and then migrate actors (which contain the state) over to the new nodes. The system as a whole keeps running, even while individual nodes are gradually updating under the hood. That’s still not simple to do reliably (you need to be careful about protocol evolution), but it’s not a bad approach when always-up is a priority.

5 Likes

When using Metals language server with Bloop there is an option to use hot reload, though I haven’t really looked how well it works recently.

It’s done via Scala Debug Adapter library and introduced in 4.0.0 version: Release v4.0.0 · scalacenter/scala-debug-adapter · GitHub

I haven’t seen any issues related to it, but it might have been because people didn’t actually use it :sweat_smile: If you have any issues let us know! It should just be a case of starting the program within metals and using the reload button (thunderbolt)

5 Likes

I thought the play framework supported hot reloading, and there’s sbt-revolver though the hot reloading part doesn’t seem to be actively supported anymore IIUC. But AFAIK that functionality is usually only meant to be used during development for quick turn-around, not for rolling out new versions in production.

3 Likes

Aha, very cool! I’ve done a quick test just now, and it does indeed appear to work as intended. I can change a method while debugging, click hot code replace (bolt), and the new behaviour is used on the next call to the method. State seems to be preserved as well. :tada:

One small issue I noticed: When I click the hot code replace (bolt) button, I get a warning from Metals that says “No classes were reloaded”, but in fact they were since the new behaviour is applied correctly… :thinking: I filed an issue for this. I’d like to start contributing to Scala tooling, so I’ll attempt to fix it.

1 Like

Yeah, as far as I can tell from searching around, low-level tooling (agents, class loaders, etc.) would need to be created to do anything more complex than method redefinition, as I believe that’s the only change directly supported by most JVMs.

Some customised JVMs (JetBrains runtime, GraalVM Espresso) appear to support additional code changes at runtime, though I haven’t tested those myself (and there may be Scala-specific hurdles to overcome).

I’ve also noticed a few mentions of JRebel (in the Java ecosystem) which claims to also support advanced code changes, but it’s a commercial offering.

I’m happy to create any tooling (open source, of course!) that might be needed to enable this kind of workflow (in case I find a need to go beyond the method redefinition that Metals + JVM already supports). As you say, it’s critical to keep the compiler involved to ensure type safety and such.

If other people are also interested in interactive / hot code replace features, please do let me know. :smile:

2 Likes

That seems like a very interesting challenge: Typechecking the changes

I see it as ensuring there exists a “bubble” around the changes on the surface of which the types (and binary representation) remain the same before and after the change

A few examples:

val x: Int = 4
// can become
val x: Int = 5

// x in scope
val y = 5
foo(x, y)
def foo(x: Int, y: Int)
// can become
val y = "Hi"
foo(x, y)
def foo(x: Int, y: String)

class A(x: Int)
val z = A(1)
// cannot become
class A(val x: Int)
val z = A(1)
1 Like

Hello! Recently I’ve published jvm-live-reload. It allows you to make use of Play-like live-reloading regardless of used web-framework. In Scala-ecosystem it supports at least such frameworks as cask, http4s, zio-http, the others probably work too, but they’re yet untested and unverified.

To compare with previously mentioned solutions:

  • sbt-revolver - it’s neither hot nor live reloading at all, it just continuously rebuilds and restarts your JVM process in a watch-mode.
  • scala-debug-adapter - works only in debug mode and allows only changes which doesn’t change class schema.
  • Custom JVMs - require specific JVM and lacks of tooling. Every team member have to setup it manually and there is no option to include something in build.sbt that would allow everyone to use reloading out-of-box. And I’m unsure whether it plays with Scala-ecosystem or not.

jvm-live-reload allows you to setup it once in build.sbt or build.mill and then use it without any additional actions, hassle-free.

Are there any interactive / live tools for Scala that allow redefining methods without restarting a running program?

For a bit more context, I am thinking of long running programs with lots of state that you don’t want to lose by restarting, like a game engine or data analysis pipeline. You want to make a change to behaviour while preserving current state.

But still jvm-live-reloaddoes restart your application (partially), so state would probably be lost. Reload without losing a state is only available as part of tools which provide the true hot-reloading, which exactly patch only changed code in runtime, which are either custom JVMs or JRebel. jvm-live-reload implements a live-reloading approach using ClassLoaders, which still restarts your application, but preserving a running JVM process. This approach also implemented by such web-frameworks as Play, Spring, Quarkus, Tapestry and many others.

4 Likes

That looks like an amazing project.

I have been using the previously JBR runtime that allows for just hot class reloading (schema changes are allowed bu limited support for class hierarchy changes) by merging in an offshoot of the old DCEVM project into the openjdk. Its not a classpath based system so state doesnt re initialize (ie no new endpoints or stuff like that, unless you write some additional code to do reload those on top of that), but is enough to do some live development.

Havent tried to use it with SBT, but the intellij build system (unsurprisingly) has no problem with it.

A hybrid system that could optionally allow for classpath based restarts would be the holy grail for my workflows.

Any particular reason for this to only work with web servers?

I understand that it doesn’t store state, so web servers with an external storage are the most common use case, but I don’t see why other applications couldn’t work (it would still be faster than sbt- revolver).

I see that the library needs an open health check endpoint, but I don’t really understand why (other than maybe some rollback logic). Wouldn’t this work without an health check?

From the very beginning it was supposed to provide exactly “Play-like” live-reloading. Basically, reloading flow in playframework looks like that:

  • Starting an application.
  • Doing as much code changes as you want.
  • (nothing reloads yet)
  • Do curl request to your application.
  • (here it complies all your changes and restarts an application while the request just idle)
  • When an application started on a new code, your request proceeds.

With such approach your request is a trigger to start a reloading and the whole thing (and of course, if nothing were changed, the request proceeds as always).

We need a health-check endpoint to know when an application is actually started so we can safely proceed a request after reloading. Without such endpoint it won’t work, because we have no other ways to retrieve such information. When we start an application after reloading, everything we do is basically calling a static void main method.

playframework has full control over application lifecycle, knows its’ structure etc, but our plugin is an universal solution and we know literally nothing about an underlying application, so that’s why this health-check is mandatory.

Well, it can be easily implemented I guess. Yes, in this case it would be the same sbt-revolver, but a little bit faster as a result of running in a non-forked JVM.

Thank your for your interest!

I heard that HotSwapAgent has an ability to extend reloading behaviour using custom plugins. That’s how they workaround cases like you described for various frameworks from Java world.

But I believe you need to give jvm-live-reload a try :grin:Maybe it will work for you, if not, feel free to open an issue! Thank you for your interest!