You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Hosting API to enable hot reload at startup #47274
When running without a debugger attached, we need a way to tell the runtime that certain assemblies will be modified with EnC deltas. For Mono we need to do this early enough that we set the right interpreter optimization flags before we execute any managed code from the affected assemblies.
The information given to the runtime should probably include the set of assemblies that will be modified (as opposed to informing the runtime that any assembly at all could potentially be modified).
The current proposal:
Pass an environment variable DOTNET_MODIFIABLE_ASSEMBLIES with the case-insensitive keyword "debug" to enable all debug built assemblies for hot reload. This can be expanded on with other keywords like "all" or a comma separated list of assemblies when there are clear scenarios for the options.
Alternative approaches:
Pass a new MODIFIABLE_ASSEMBLIES property to coreclr_initialize that includes a list of assemblies that the runtime should expect to be modified. The presence of the property would indicate that hot reload support should be enabled. We could pass the property via the .runtimeconfig.dev.json file without any modifications to the (desktop) host. Mobile and wasm workloads would need to adjust their hosts.
Pass an environment variable DOTNET_MODIFIABLE_ASSEMBLIES with the same information. On mobile and wasm workloads, the hosts wouldn't need to be adjusted, MonoVM would consult the environment variable itself. On other workloads the host could interpret the environment variable or leave it to the runtimes.
Passing additional properties to the runtime through coreclr_initialize is already possible by modifying the .runtimeconfig.json or via .runtimeconfig.dev.json. Using those would not need any host changes. We could also add the env. variable support to the host for this and potentially even command line support if necessary.
In this sense it would be similar to DOTNET_STARTUP_HOOKS which is an env. variable read by the host and passed to the runtime via the runtime properties. For this one we only allow runtimeconfig and env. variable - there's no command line, at least yet.
I guess it depends on the needs in this case:
Do we need to support something like "lazy attach", so that the app starts without the "controller" which "attaches" much later on?
Do we need to support this on "random" app, that is app which was produced using any build configuration without knowing that it will be used this way?
If we only need to support this in cases where we build the app with hot reload in mind (similar to "Debug" configuration), then using the .runtimeconfig.dev.json and the runtime property would probably be a good fit - with no changes necessary to the host.
Do we pass along the .runtimeconfig.json? That one would also work - if we're really doing a special build for this case.
My understanding is that at this time the mobile embedders don't bundle the json files (or otherwise make the content available). And also apparently they're not using monovm_initialize/coreclr_intialize yet.
I'm back to mildly preferring to use an environment variable.
Update: we added work items for XI and XA to make sure they call monovm_initializedotnet/macios#10504 and dotnet/android#5537, although that still leaves the mobile hosts with funnelling the runtimeconfig properties into the runtime.
It assumes that the app in EnC mode is launched by a controller that sets this environment variable for you. Is it a safe assumption to make?
I think so. Hot Reload would most likely need a controller that is responsible for launching the app. (That seems the easiest way to guarantee that the controller has a consistent view of the source code, the managed assemblies and queued up EnC deltas)
Will we use managed APIs to actually do the hot-reload editing?
If true, then how are we going to inject the necessary managed code to the app?
I'm thinking that maybe something like startup hooks would be a better fit - for both reasons. It could setup the necessary communications channels (all in managed) and also tell the runtime which assemblies should be enabled for "Editing".
Will we use managed APIs to actually do the hot-reload editing?
Still TBD - it's what I'm prototyping in Mono, but ultimately we'll have to align on a design with @tommcdon.
I'm thinking that maybe something like startup hooks would be a better fit - for both reasons. It could setup the necessary communications channels (all in managed) and also tell the runtime which assemblies should be enabled for "Editing".
I can think of reasons why that won't work right now for Mono's hot reload, but I have a harder time thinking of why this wouldn't work in principle.
Details
Assuming we choose the right execution engine (interp, not JIT or AOT), the information that hot reload needs is: don't do inlining of any methods that could change. Which in practice means don't inline methods from a modifiable assembly.
So a startup hook out to work to tell the runtime what the modifiable assemblies are.
Currently it's all-or-nothing in mono - we turn off all inlining, because there's no flag for modifiable assemblies yet. But it seems reasonable to add such a flag and it shouldn't slow down the interpreter fast path unreasonably.
Setting up a communication channel through a startup hook, may work, but I'm not certain. The "main assembly" on Xamarin.iOS is the Xamarin Objective C runtime. So setting up a communication channel would have to be done without using any managed iOS functionality. I'm not sure if that's an onerous limitation. Probably it's ok. I think XA works similarly. I suppose worst case the managed startup hook could call into a native library for the bulk of the work. A managed startup hook would work on wasm, I think.
Mono and all three of those hosts don't have startup hook support right now, so that would need to be done. Xamarin and wasm will require tooling work to bundle the startup hook assemblies into the application bundles (and maybe AOT them for ios).
they can be used with module paths separated by
Are we going to actually use this for anything in near future? I sounds like unnecessary complication to me.
We may want it for testing to enable just a test assembly regardless of how it is built. Hopefully @pranavkm can tell us if specifying individual modules will be useful for dotnet-watch.
Are we going to support hot-reload e2e for the release build configuration? If not, we should rather always build the test assemblies for this as debug.
dotnet-watch is aware of the set of projects / assemblies it's building. It would be fairly easy for us to specify the list of assemblies being built as a value to the env variable if that makes a difference. That said, in the ordinary case specifying Debug should suffice. We can provide UX to users telling them hot reload is being disabled because they're building their app in release if that complicates things.
We should also clarify how All works together with inlining. Is All going to prevent inlining by turning off optimizations for the whole process?
I don't know if the JIT_FLAG_DEBUG_EnC (set if the module is editable) disables inlining or not. It does prepare for on stack replacement which isn't needed for hot reload but may be needed on Windows for a smooth transition from hot reload edits to debugging/ENC.
Unless it has noticeable performance impact and since it is opt-in now, I say the "All" option sets the jit ENC flag just like the COMPlus_ForceENC. To keep things simple and consistent I think the "All" option should do this for Windows, Linux and MacOS, etc.
the JIT_FLAG_DEBUG_EnC (set if the module is editable) disables inlining or not.
It does not.
COMPlus_ForceENC is a testing hack that does not always work correctly. If we just want a hack for testing, we can keep using COMPlus_ForceENC. If we want something that actually works well, it is going to need more work.
I think we should just enable the Debug policy for now, and worry about other policies only once we have a clear scenario for them.
Does this env var need to go through an official API review?
Today, no. Though we're getting to a point with our environment variables where I do think we should start looking at them with a view to consistency, and potentially adding a tiny more amount of process around them, not to block them but just to help with that consistency. @terrajobst, thoughts?
Activity
lambdageek commentedon Jan 21, 2021
/cc @tommcdon @mikem8361 @jkotas @vargaz
jkotas commentedon Jan 21, 2021
It assumes that the app in EnC mode is launched by a controller that sets this environment variable for you. Is it a safe assumption to make?
DOTNET_MODIFIABLE_ASSEMBLIESwould be a better name.vitek-karas commentedon Jan 21, 2021
Passing additional properties to the runtime through
coreclr_initializeis already possible by modifying the.runtimeconfig.jsonor via.runtimeconfig.dev.json. Using those would not need any host changes. We could also add the env. variable support to the host for this and potentially even command line support if necessary.In this sense it would be similar to
DOTNET_STARTUP_HOOKSwhich is an env. variable read by the host and passed to the runtime via the runtime properties. For this one we only allow runtimeconfig and env. variable - there's no command line, at least yet.I guess it depends on the needs in this case:
If we only need to support this in cases where we build the app with hot reload in mind (similar to "Debug" configuration), then using the
.runtimeconfig.dev.jsonand the runtime property would probably be a good fit - with no changes necessary to the host.lambdageek commentedon Jan 21, 2021
Hmm.. using
.runtimeconfig.dev.jsonis appealing, but I don't think the content of that file is passed along to mobile (or wasm) workloads.vitek-karas commentedon Jan 21, 2021
Do we pass along the
.runtimeconfig.json? That one would also work - if we're really doing a special build for this case.lambdageek commentedon Jan 22, 2021
My understanding is that at this time the mobile embedders don't bundle the json files (or otherwise make the content available). And also apparently they're not using
monovm_initialize/coreclr_intializeyet.I'm back to mildly preferring to use an environment variable.
Update: we added work items for XI and XA to make sure they call
monovm_initializedotnet/macios#10504 and dotnet/android#5537, although that still leaves the mobile hosts with funnelling the runtimeconfig properties into the runtime.lambdageek commentedon Jan 22, 2021
I think so. Hot Reload would most likely need a controller that is responsible for launching the app. (That seems the easiest way to guarantee that the controller has a consistent view of the source code, the managed assemblies and queued up EnC deltas)
vitek-karas commentedon Jan 22, 2021
Will we use managed APIs to actually do the hot-reload editing?
If true, then how are we going to inject the necessary managed code to the app?
I'm thinking that maybe something like startup hooks would be a better fit - for both reasons. It could setup the necessary communications channels (all in managed) and also tell the runtime which assemblies should be enabled for "Editing".
lambdageek commentedon Jan 22, 2021
Still TBD - it's what I'm prototyping in Mono, but ultimately we'll have to align on a design with @tommcdon.
I can think of reasons why that won't work right now for Mono's hot reload, but I have a harder time thinking of why this wouldn't work in principle.
Details
Assuming we choose the right execution engine (interp, not JIT or AOT), the information that hot reload needs is: don't do inlining of any methods that could change. Which in practice means don't inline methods from a modifiable assembly. So a startup hook out to work to tell the runtime what the modifiable assemblies are. Currently it's all-or-nothing in mono - we turn off all inlining, because there's no flag for modifiable assemblies yet. But it seems reasonable to add such a flag and it shouldn't slow down the interpreter fast path unreasonably.Setting up a communication channel through a startup hook, may work, but I'm not certain. The "main assembly" on Xamarin.iOS is the Xamarin Objective C runtime. So setting up a communication channel would have to be done without using any managed iOS functionality. I'm not sure if that's an onerous limitation. Probably it's ok. I think XA works similarly. I suppose worst case the managed startup hook could call into a native library for the bulk of the work. A managed startup hook would work on wasm, I think.
Mono and all three of those hosts don't have startup hook support right now, so that would need to be done. Xamarin and wasm will require tooling work to bundle the startup hook assemblies into the application bundles (and maybe AOT them for ios).
21 remaining items
jkotas commentedon Feb 18, 2021
We should also clarify how
Allworks together with inlining. IsAllgoing to prevent inlining by turning off optimizations for the whole process?mikem8361 commentedon Feb 18, 2021
We may want it for testing to enable just a test assembly regardless of how it is built. Hopefully @pranavkm can tell us if specifying individual modules will be useful for dotnet-watch.
jkotas commentedon Feb 18, 2021
Are we going to support hot-reload e2e for the release build configuration? If not, we should rather always build the test assemblies for this as debug.
pranavkm commentedon Feb 18, 2021
dotnet-watch is aware of the set of projects / assemblies it's building. It would be fairly easy for us to specify the list of assemblies being built as a value to the env variable if that makes a difference. That said, in the ordinary case specifying
Debugshould suffice. We can provide UX to users telling them hot reload is being disabled because they're building their app in release if that complicates things.mikem8361 commentedon Feb 19, 2021
I don't know if the JIT_FLAG_DEBUG_EnC (set if the module is editable) disables inlining or not. It does prepare for on stack replacement which isn't needed for hot reload but may be needed on Windows for a smooth transition from hot reload edits to debugging/ENC.
Unless it has noticeable performance impact and since it is opt-in now, I say the "All" option sets the jit ENC flag just like the COMPlus_ForceENC. To keep things simple and consistent I think the "All" option should do this for Windows, Linux and MacOS, etc.
jkotas commentedon Feb 19, 2021
It does not.
COMPlus_ForceENCis a testing hack that does not always work correctly. If we just want a hack for testing, we can keep usingCOMPlus_ForceENC. If we want something that actually works well, it is going to need more work.I think we should just enable the
Debugpolicy for now, and worry about other policies only once we have a clear scenario for them.mikem8361 commentedon Feb 19, 2021
Does this env var need to go through an official API review? Should I mark it api-ready-for-review?
/cc: @stephentoub
stephentoub commentedon Feb 20, 2021
Today, no. Though we're getting to a point with our environment variables where I do think we should start looking at them with a view to consistency, and potentially adding a tiny more amount of process around them, not to block them but just to help with that consistency. @terrajobst, thoughts?
Related: #47283
mikem8361 commentedon Mar 8, 2021
@lambdageek, have you added this env var to mono yet?
lambdageek commentedon Mar 9, 2021
@mikem8361 Nope, got pulled away to work on a couple other things. Hope to return to it later this week.
[mono][mbr] Implement DOTNET_MODIFIABLE_ASSEMBLIES support (#49507)
lambdageek commentedon Mar 13, 2021
This is now done for MonoVM and CoreCLR