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

Hot Reload #11636

Open
Happypig375 opened this issue Jun 5, 2021 · 46 comments
Open

Hot Reload #11636

Happypig375 opened this issue Jun 5, 2021 · 46 comments
Labels
Area-Debug stepping, debug points, stacks and more Feature Request
Milestone

Comments

@Happypig375
Copy link
Member

Is your feature request related to a problem? Please describe.

C# is getting Hot Reload for .NET 6. Will F# receive the same experience? Roslyn has had work done to support this. The F# compiler doesn't seem to have any equivalent work on this.

Describe the solution you'd like

F# can use hot reload just like C#.

Describe alternatives you've considered

Staying behind in terms of tooling again.

Additional context

If the issue is about:

  • improving a compiler error message: No.
  • improving/adjusting the parser: No.

Add any other context or screenshots about the feature request here.
dotnet/core#5510

@Happypig375
Copy link
Member Author

https://devblogs.microsoft.com/dotnet/introducing-net-hot-reload/

F# is currently not supported in .NET 6 but we are planning to support in a future release based on customer feedback.

Of course F# is staying behind in terms of tooling again. 😞

@charlesroddie
Copy link
Contributor

charlesroddie commented Jun 5, 2021

What's the current status if:

  • You run a C# project that supports edit and continue/hot reload
  • This depends on an F# project
  • After making a change in the F# project, you build it.

Is hot reload able to support that currently? I.e. loading the updated assembly.

@cartermp
Copy link
Contributor

cartermp commented Jun 5, 2021

It'll happen eventually here, but hot reload is a very new technology that is likely to undergo several changes well after .NET 6. We'll plug into it when it's more stable.

@KevinRansom
Copy link
Member

@happypig, @charlesroddie --
what do you want hot reload to do for you? Just asking for a feature name is probably not going to get anything moving. We don't have plenty of other C# features also, and we don't plan on going down the VB route of moaning whenever we don't get some new C# feature.

@forki
Copy link
Contributor

forki commented Jun 9, 2021 via email

@charlesroddie
Copy link
Contributor

From the linked thread:

dotnet/core#5510 (comment) How fast developers can make code changes and see the resulting impact in their apps is directly proportional to how productive they can be.

Most important is a quick development loop when changing bits of UI (likely to be MAUI with WinUI as primary development platform). For non-UI code I don't need to run code very often, sometimes only once after finishing a work item to check that it works. But for UI code a lot of tweaks and adjustments are important and need to be visualized by running the code.

Hot reload would be perfect for this but does look like a very large task for F# to support Edit and Continue which it is based on.

A typical UI tweak doesn't affect the API of an assembly so reference assemblies will speed up the loop.

Hypothetically if it were possible, while running an application, to edit a dependent project, build it, and switch in the dll, it might capture most of the benefit of hot restart. But this may not be possible in dotnet?

@heronbpv
Copy link

heronbpv commented Jun 9, 2021

Would support for generating files for FSI scripting, like the one provided by the F# Power Pack back in the day, be an alternative in this case? Together with script debugging, of course.
I can do it in VS 2015 with the plugin for something like winforms, for example. It's more clunky than have it done automatically, for sure, but also closer to home.

@albertwoo
Copy link

I am using blazor with fsharp. Hot reload will speed up the poductivity a lot.

@farlee2121
Copy link

farlee2121 commented Jul 20, 2021

This is a critical tooling enhancement for my usage of F#.

Is there some way I can contribute to move up the timeline?
(I don't currently have a great feel for the scope of work needed)

@vzarytovskii
Copy link
Member

This is a critical tooling enhancement for my usage of F#.

Is there some way I can contribute to move up the timeline?
(I don't currently have a great feel for the scope of work needed)

I don't think we have a full understanding of what has to be done yet.

@farlee2121
Copy link

I decided to spelunk and get a better idea of what it takes to implement hot reload.

Full notes are in this gist https://gist.github.com/farlee2121/24915fb0518ad4ca6631bc33d0ff401c

Finds so far

Oustanding investigations

  • TODO: Looking into F# partial compilation capabilities
  • TODO: Look into support for solutions with projects in multiple languages
  • TODO: understand where application state is preserved

@farlee2121
Copy link

farlee2121 commented Sep 1, 2021

A quick update. I made an experimental fork.

It's pretty basic so far

  • Handles project changes
  • Handles new files
    • There is a gotcha with VisualStudio. The build fails because the VS implicitly modifies the project file, but doesn't save it right away
  • Rebuilds on any change to any .fs file

The running program will show changes, and much faster than the existing dotnet watch. The main issue is that debug artifacts are not updated with this strategy.

A more accurate description is that rebuilding tries to output both dlls and pdbs, but those resources are already in use by the debugger process.

@NatElkins
Copy link
Contributor

@farlee2121 Don't be shy, post a GIF!

@farlee2121
Copy link

Here's a demonstration of the most basic scenario, changing a file

fsharp-watch

@kerams
Copy link
Contributor

kerams commented Sep 6, 2021

Sorry for my ignorance, but what exactly is that demonstrating? Watch will recompile and relaunch an application today without hot reload.

@farlee2121
Copy link

farlee2121 commented Sep 6, 2021

Correct.

Here is the described state of my experiment I was asked to gif.

It's pretty basic so far

  • Handles project changes
  • Handles new files
    • There is a gotcha with VisualStudio. The build fails because the VS implicitly modifies the project file, but doesn't save it right away
  • Rebuilds on any change to any .fs file

I also believe it to be faster, but my sample is only one project.

While the above requirements are met with the DotNetWatcher, they also have to be implemented for F# in HotReloadDotNetWatcher.
You could interpret this gif as demonstrating the most basic F# reloading, but working in the Hot Reload implementation of dotnet watch.

I'm still working on demonstrating updates to F# code using existing delta appliers (true hot reload).

@kerams
Copy link
Contributor

kerams commented Sep 6, 2021

I see, thank you.

@farlee2121
Copy link

farlee2121 commented Sep 23, 2021

An update on my efforts.

  • I started some new consulting projects, so my time for this has declined and progress has slowed.
  • I've had to back-fill a lot of knowledge about compiled artifacts (dlls, pdbs)
  • I thought I had figured out the data format needed for live updates applied by IDeltaApplier by digging through the roslyn implementation. However, my test implementation didn't work.

A few other conclusions likely obvious to the FSharp team, but I had to dig for

  • It appears FSharp Compiler Services has the underlying tooling we need to generate incremental updates
  • Incremental updates can be created using FSharpChecker then diffing the compiled outputs, but it seems we could make smarter updates with access to internal-only API methods. Thus, we'll likely want to add hot reload binary patch /delta generation to the compiler services and simply invoke it from the appropriate place in dotnet-watch

Things I'll probably need help on if I can demonstrate a successful reload

  • Project cracking is very confusing, I traced a lineage of several deprecated project crackers, we probably don't want core tooling to depend on the current ionide cracker. How are we supposed to build a project graph?
  • Feedback on where the hot reload delta generation should live in compiler services
  • Understanding live update limitations (rude edits)

@xperiandri
Copy link
Contributor

I suppose @farlee2121 needs someone from Roslyn/HotReload team to join this thread

@farlee2121
Copy link

That would certainly help

@farlee2121
Copy link

I got a bit more work in tonight.
Specifically, I traced how the tests were generating deltas, and it comes back to a utility based on the same Roslyn method I was looking at before . The issue is that it passes around a bunch of mutable byte streams and I'm having a hard time tracking with what all happens to those mutable streams.

I also set up more experiments to understand what behaviors hot reload handles for C# now. I found the results more fickle than I expected. Asp.net works fine, but I've yet to get blazor wasm working, and the console app only updates if I stop at a breakpoint.

Issue Scope

My updates have been a bit hodge-podge, so here's a summary in light of the original question: what would it take to implement hot reload for F#.

I'm more confident than ever that the DeltaAppliers should be reusable for F#. "Starting in 6.0, the runtime (.NET Core and Mono) expose APIs to patch a running app". This should be language-agnostic, and addresses the complexities of different runtime conditions.

My sdk fork demonstrates how to plug F# into the hot reload hierarchy. I can provide more details if needed. It's basically the single FSharpCompilationHandler class.

The main work to be done is an F# compiler api method that takes changed files in a project and outputs assembly patches in a format mappable to System.Reflection.Metadata.MetadataUpdater. We still need to understand that format properly. This api method should also notify when a "rude edit" that can't be handled at runtime is made so hot reload can prompt for a restart.

When it's all done, we add <ProjectCapability Include="SupportsHotReload" /> to the Fsharp targets file

@albertwoo
Copy link

Any progress so far?

@farlee2121
Copy link

I started a new job and haven't had time for this lately.
I'll probably need to reach out to the roslyn team to better understand the delta formats

@albertwoo
Copy link

@dsyme can you please help on this cool feature? Do you know any Roslyn team members who can help here?

@PiotrJustyna
Copy link

F# support for this would be so much appreciated. I created a small repository to illustrate the problem for the new readers: https://github.com/PiotrJustyna/binoculars What I'm after is for the PID not to change as the code hot-reloads. It does work perfectly for C# 👌

I plan to use it for work where, in order to speed up deployments, we could hot swap certain F# components in our C# and F# hosts. Eagerly observing this issue 👀

@askpt
Copy link

askpt commented Jan 21, 2022

This feature would improve our team daily workflow. I don't understand why is not being planned to introduce for F# language. It will leave the language as a second class citizen when compared with C# and even VB.NET.

@albertwoo
Copy link

It can improve bolero developments. I am also making a library Fun.Blazor, looking forward to hot reload for a long time. I hope F# team can take this as soon as possible, they and Roslyn team are all in Microsoft right? They can discuss the C# implementation detail.
Hope it will happen soon...

@AlexBoehm
Copy link

The lack of hot reload is also holding me back from using Bolero with F# instead of Blazor with C#.

@coldwarrl
Copy link

This missing feature is really a productive killer and will not help to spread the usage of F#. If you gain initial benefits from using F# and then later your productivity falls back behind using C# what is the rationale to use F# outside of domains like Data Science ?

Even if you have F# type references in your C# assembly, hot reload seems not to work even for C# most of the time. This is even more frustrating.

I understand that rolling out new features cannot be done always in a holistic way through the .NET framework. But if evertyime of such a feature, F# user has to wait a couple of years until they might be able to benefit also, F# will never get more momentum, the IDEs will always be considerable less productive for F# devs in general. So if F# should be a first citizen in .NET please think about improving this situation in the future.

@badgh
Copy link

badgh commented Mar 16, 2022

F# can be transpiled to JavaScript (now also Python) via Fable and thus it uses hot reload feature from JavaScript. If F# could also be transpiled to C# via Fable, does it mean that F# would get C#'s hot reload feature for free?

@xperiandri
Copy link
Contributor

It does not make sense as it is already the .NET and needs just a piece of logic to be plugged in

@farlee2121
Copy link

farlee2121 commented Mar 17, 2022

Converting F# to C# is doable with existing tools. For example, you could use something like ILSpy to decompile from IL.

However, this isn't likely to enable a practical hot reload. We'd end up compiling twice and decompiling once. Much of it would likely be hard to do incrementally. It's unlikely such a pipeline would be efficient enough to beat restarting the app. It could be worth an experiment though.

F# already has incremental compile capabilities. The problem is translating them into the delta format expected by the HotReload. That the delta format is not well documented right now, and not all changes are supported. We'd have to analyze the compiled code for unsupported edits.

EDIT

I was thinking in existing tools, which probably wouldn't work. But A bit more thought, and direct transpilation to C# might not be such a bad idea. We'd have to take a look at Fabel to see how much work it is to map the AST. If we can preserve incremental compiles and feed those to a MSBuildWorkspace, which could also detect incremental changes.... it might be fast enough and offload concern for rude edit detection

@badgh
Copy link

badgh commented Mar 17, 2022

I also mean not using decompile from IL, but using the same logic as they use in incremental F# to JavaScript Fable compilation.

Giraffe is built on top of ASP.NET Core, and so it receives all updates made in ASP.NET Core for free (like 50-100% speed increase in .NET 5). In the same way, if F# was also built on top of C#, so to speak (i.e. compiled to C#), as a side experiment, it сould receive hot reload and any other potential benefits which C# will definitely receive in future. These are just thoughts, they may or may not be feasible...

@dsyme dsyme added the Area-Debug stepping, debug points, stacks and more label Apr 20, 2022
@vzarytovskii vzarytovskii added this to the Backlog milestone Oct 19, 2022
@En3Tho
Copy link
Contributor

En3Tho commented Nov 21, 2022

@farlee2121 Hey! I know it's a lot of time since you've touched this topic but I'd like to ask you anyway. In net6 dotnet watch was working for C# projects that reference F# projects.

For examlpe: new blazor server project -> reference to new F# class library project. Run dotnet watch, change somethig in C#/razor markup -> hot reload applies. Change something in F# -> rebuild. So it's okay'ish.

In net7 they somehow changed workspace thingy so in the same situation any change to cs/razor files now trigger full rebuild.

Did you have to implement specific F# project loader and friends? I think I will have to check out your fork because it became quite sad to use C# + F# combos.

@En3Tho
Copy link
Contributor

En3Tho commented Nov 21, 2022

@dsyme @vzarytovskii Any plans doing this in Net8? Would be cool to have an option to at least change methods/let functions compiled to methods. UI frameworks are usually based on methods.

@vzarytovskii
Copy link
Member

vzarytovskii commented Nov 21, 2022

@dsyme @vzarytovskii Any plans doing this in Net8? Would be cool to have an option to at least change methods/let functions compiled to methods. UI frameworks are usually based on methods.

No, no concrete plans as for now, we would certainly like to get this working, but it will need to get some proper planning and framing.

As for now it's unknown what will be involved.

I suspect a bunch of work will intersect with expression evaluator.

@farlee2121
Copy link

@En3Tho I didn't get so far as a special project loader. The biggest outstanding risk was generating the deltas. Delta generation ended up a much larger and more opaque problem than expected, so I dropped the project.

The Ionide proj-info project is probably your best starting place. The F# team explicitly chose not to own a project system. There are some Roslyn workspace integrations as part of the VS tooling, but I think those were getting project trees handed in from an external source I couldn't trace. It's been a while though.

That's as best as I remember. There may be more project system research info in my notes if you're willing to dig

@En3Tho
Copy link
Contributor

En3Tho commented Nov 21, 2022

@vzarytovskii @farlee2121 Thanks.

@sksallaj82
Copy link

I'm using Blazor C# with F# driving the business logic, in fact, F# accounts for roughly 75% of the product. Because of the existence of fsproj, it puts the hot reload into rude edit mode, which I pick 'always' to restart the application whenever I get a rude edit. At that point, it'll just refresh the page whenever I change my code. When editing the F# code, I have to stop everything and restart the application. To move on and get the benefits of hot-reload, I made a Vue project using Vue CLI, where I created a component library. Each time I approve a component in Vue, I convert it to the Blazor application. The components are nearly identical, except for the programming nuance. I was going to get on the Bolero train, but don't ask me why I'm not using it... it's a good project, though.

@vzarytovskii
Copy link
Member

I'm using Blazor C# with F# driving the business logic, in fact, F# accounts for roughly 75% of the product. Because of the existence of fsproj, it puts the hot reload into rude edit mode, which I pick 'always' to restart the application whenever I get a rude edit. At that point, it'll just refresh the page whenever I change my code. When editing the F# code, I have to stop everything and restart the application. To move on and get the benefits of hot-reload, I made a Vue project using Vue CLI, where I created a component library. Each time I approve a component in Vue, I convert it to the Blazor application. The components are nearly identical, except for the programming nuance. I was going to get on the Bolero train, but don't ask me why I'm not using it... it's a good project, though.

Hot reload is still on the radar, however it showed to be much harder than anticipated.

@En3Tho
Copy link
Contributor

En3Tho commented Feb 12, 2023

@sksallaj82 presence of F# forcing rude edits is partly a .Net 7 regression. Before this C# code could be easily edited w/o rude edits even while having a dependency on F# project. dotnet/sdk#28998

One of ways to work around this thing is to fork sdk repo and do some tweaks as I suggested in this issue

Other is obviously wait for F# team to implement workspaces properly as first step to hot reload

Please leave a message in that issue so it won't be a one man problem

@NatElkins NatElkins mentioned this issue Sep 1, 2023
@NatElkins
Copy link
Contributor

@vzarytovskii I know that hot reload is considered to be a huge effort, but is there anything the community can do to move things forward? @farlee2121 was able to make (what appears to be) at least a bit of progress. I'm not sure if there have been many conversations on what is required to complete this feature, or if new tasks have come to light. Some questions I have as a (relative) layperson:

  1. How is project loading related to the hot reload effort?
  2. How is the F# EE related to hot reload? In a previous comment you had said you suspected there would be overlap with the EE and hot reload, has that been confirmed?
  3. Hot reload is still on the radar, however it showed to be much harder than anticipated. What are the major blockers that you are aware of?
  4. Does this work intersect with the transparent compiler initiative at all?

I'm really eager to be able to use this feature, especially as it would massively improve some workflows related UI programming (a focus of mine at the moment 😅 ).

Thank you!

@vzarytovskii
Copy link
Member

@vzarytovskii I know that hot reload is considered to be a huge effort, but is there anything the community can do to move things forward? @farlee2121 was able to make (what appears to be) at least a bit of progress. I'm not sure if there have been many conversations on what is required to complete this feature, or if new tasks have come to light.

I guess any help with figuring out all the components involved in C#'s hot reload will be helpful. Will allow us to frame things better.

Some questions I have as a (relative) layperson:

  1. How is project loading related to the hot reload effort?

Hot reload is coupled with MSBuild, workspaces and project evaluation FWIU. Maybe @baronfel has more context here?

  1. How is the F# EE related to hot reload? In a previous comment you had said you suspected there would be overlap with the EE and hot reload, has that been confirmed?

To be able to properly reload running project, we need to either:

a. Know how to compile changed piece of code and inject it into running process/patch IL (kind of like what C# does when we evaluating expressions in debugger right now).
b. Fully reload and restart everything from scratch (in this case, I wouldn't consider it a hot reload, but just restart).

  1. Hot reload is still on the radar, however it showed to be much harder than anticipated. What are the major blockers that you are aware of?

I guess the main blocker is that we don't really know/understand the full picture of what's involved in this work. We have bits and pieces.

  1. Does this work intersect with the transparent compiler initiative at all?

No, they are not related at all. Transparent compiler is mostly about new compiler/checker pipeline, not really related to codegen.

@aligneddev
Copy link

aligneddev commented Mar 5, 2024

I'm using Blazor C# with F# driving the business logic, in fact, F# accounts for roughly 75% of the product. Because of the existence of fsproj, it puts the hot reload into rude edit mode, which I pick 'always' to restart the application whenever I get a rude edit. At that point, it'll just refresh the page whenever I change my code. When editing the F# code, I have to stop everything and restart the application. To move on and get the benefits of hot-reload, I made a Vue project using Vue CLI, where I created a component library. Each time I approve a component in Vue, I convert it to the Blazor application. The components are nearly identical, except for the programming nuance. I was going to get on the Bolero train, but don't ask me why I'm not using it... it's a good project, though.

Hot reload is still on the radar, however it showed to be much harder than anticipated.

I am creating some HTML reports with Giraffe in Visual Studio and just realized hot module reloading wasn't working, which led me here.
Are there any updates on F# getting this goodness?

@NatElkins
Copy link
Contributor

@tmat , Do you happen to have any additional insight on what steps we can take to add hot reload support to F#?

@tmat
Copy link
Member

tmat commented Mar 29, 2024

@NatElkins I think it'd be best to follow the code paths in Roslyn.

You can start with the entry-point, which is EditAndContinueLanguageService. This is called by the debugger via IManagedHotReloadLanguageService interface. We are actively working on these interfaces to simplify and unify code paths between VS and C# DevKit, so expect some changes. Placing breakpoints and debugging through it in VS should give you some idea what's going on though.

From there we call EditAndContinueService in our Roslyn server ("out-of-proc") when running in VS.
This service tracks active DebuggingSession which in turn tracks the current EditSession. EditSession handles a lot of heavy lifting around finding out which documents in the solution changed when compared to the source code the baseline was built from. A lot of complexity here due to #line directives and handling project system quirks.

Once we know what the changed documents are we diff them and analyze the textual changes to find out which symbols are affected by the change and how. This is rather sophisticated analysis and entirely language specific. See AbstractEditAndContinueAnalyzer for the implementation. This analysis also reports rude edits. We have another entry point here that LSP pull diagnostic system calls to report rude edits while the user is typing in the editor.

Once the set of semantic changes is determined and if there are no rude edits or other issues EditSession.EmitSolutionUpdateAsync calls into the compiler to emit IL+metadata+PDB deltas using Compilation.EmitDifference API.

The compiler finds the symbols that need to be recompiled and emits them. There is a lot of sophistication here as well that's specific to language syntax, semantics and how each language construct maps to the IL and metadata. When emitting IL and metadata updates it is often desirable (more so for EnC then for Hot Reload) to emit matching metadata names/signatures for compiler-generated metadata entities or emit local variables of updated methods to matching slots. For example, if the code registers a lambda in an event handler and the user updates the body of the lambda we need to update the existing IL method body generated for the lambda, not generate a new one. This is because the delegate created for the original lambda wouldn't know about the new method, so it would keep invoking the original one.

Once the compiler produces lowered representation of the changes that have the proper matching signatures, slots, etc. it needs to emit it to the EnC delta metadata format. Here we need to assign tokens to the entities that match the previous metadata entity of the same signature. We also need to keep track of deleted entities (a method can be deleted, added back and deleted again).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Debug stepping, debug points, stacks and more Feature Request
Projects
Status: New
Development

No branches or pull requests