WARNING: This repository is a work-in-progress and primarily serves as a demonstration that patching a running F# assembly using F# code is possible. The code might be messy and is not guaranteed to be production-ready.
To run the main demonstration project:
DOTNET_MODIFIABLE_ASSEMBLIES=debug dotnet run --project hot_reload_poc/src/TestApp/TestApp.fsprojThis project demonstrates a minimal example of applying a hot reload patch to an F# assembly without restarting the application. Here's a high-level overview of the process implemented in hot_reload_poc/src/TestApp/:
- Compile Baseline: A simple F# library (
SimpleLib) is compiled to a DLL on disk usingFSharp.Compiler.Services. - Load Assembly: The baseline DLL is loaded into a collectible
AssemblyLoadContext. - Generate Delta Patch: A patch (delta) is generated for the loaded assembly. This involves:
- Manually constructing metadata delta tables using
System.Reflection.Metadata.MetadataBuilder. - Generating the corresponding IL delta for the changed method body.
- (Optionally) Generating a PDB delta for debugging information.
- Manually constructing metadata delta tables using
- Apply Update: The runtime's
System.Reflection.Metadata.MetadataUpdater.ApplyUpdatemethod is called with the generated metadata, IL, and PDB deltas to patch the assembly in memory. - Verify: The updated method in the running assembly is invoked using reflection to confirm it now executes the patched code (e.g., returns a new value).
A hot reload delta typically consists of three byte arrays:
- Metadata Delta (
.dmeta): Contains changes to the assembly's metadata tables. - IL Delta (
.dil): Contains the updated Intermediate Language code for modified method bodies. - PDB Delta (
.dpdb): Contains updated debugging information (optional but recommended).
You can inspect the baseline assembly and the generated deltas using the mdv (Metadata Visualizer) tool from the dotnet/metadata-tools repository.
Example mdv Usage:
# Navigate to the directory containing the baseline DLL and delta files
cd path/to/output
# Inspect baseline (Gen 0) and apply deltas for Gen 1 and Gen 2
mdv baseline.dll '/g:1.meta;1.il' '/g:2.meta;2.il' | cat(Note: Ensure arguments with semicolons are quoted correctly for your shell.)
A successful multi-generation patch applied and viewed with mdv will show output similar to this, illustrating the EncId and EncBaseId linkage between generations:
MetadataVersion: v4.0.30319
>>> Generation 0: >>>
Module (0x00):
Gen Name Mvid EncId EncBaseId
=======================================================================================
1: 0 'baseline.dll' {89c195e9-...} nil nil
>>> Generation 1: >>>
Module (0x00):
Gen Name Mvid EncId EncBaseId
===============================================================================================================================
1: 1 'baseline.dll' {89c195e9-...} {0f02ed24-...} nil <-- Gen 1 links to baseline implicitly
>>> Generation 2: >>>
Module (0x00):
Gen Name Mvid EncId EncBaseId
===============================================================================================================================
1: 2 'baseline.dll' {89c195e9-...} {869f292c-...} {0f02ed24-...} <-- Gen 2 links to Gen 1 via EncBaseId=Gen1.EncId
... (rest of mdv output) ...
See notes/fsharp_hot_reload_overview.md for detailed specifications on the delta format required for F#.
While this repository focuses on generating deltas using F# and System.Reflection.Metadata, comparing this with C# delta generation using official tools can be insightful. The dotnet/hotreload-utils repository provides tools for this.
The hot_reload_poc/src/csharp_delta_test/ directory contains an example C# project configured to use hotreload-utils.
Setup:
- Clone
hotreload-utils: This tool is not available on NuGet and must be cloned locally. Place it adjacent to this repository (e.g.,../hotreload-utils).git clone https://github.com/dotnet/hotreload-utils ../hotreload-utils
- Build
hotreload-utils(Optional): Building the tool first might be necessary. Follow instructions in its README (build.shor similar). - Target Project (
csharp_delta_test) Setup:- Contains a
diffscript.jsondefining sequential code updates. - Includes source files for each version (
SimpleLib_v1.cs,SimpleLib_v2.cs). - Excludes versioned files from the main build via
.csproj(<Compile Remove=... />).
- Contains a
Generating C# Deltas:
- Build Baseline:
cd hot_reload_poc/src/csharp_delta_test dotnet build csharp_delta_test.csproj -p:Configuration=Debug cd ../../../.. # Return to workspace root
- Run Delta Generator Tool: Execute
hotreload-delta-genviadotnet run:This generates delta files (# Adjust path to hotreload-utils if needed dotnet run --project ../hotreload-utils/src/hotreload-delta-gen/src/hotreload-delta-gen.csproj -- \ -msbuild:hot_reload_poc/src/csharp_delta_test/csharp_delta_test.csproj \ -p:Configuration=Debug \ -script:hot_reload_poc/src/csharp_delta_test/diffscript.json.1.dmeta,.1.dil,.2.dmeta, etc.) in the C# project's output directory (bin/Debug/net10.0/).
- .NET SDK: Tested with .NET 10 SDK. Compatibility with .NET 9 might be possible but requires further testing, especially when using
hotreload-utils. - dotnet/metadata-tools: Provides
mdv(Metadata Visualizer). See their repo for instructions on installation. - (Optional) Cloned
dotnet/hotreload-utilsRepository: Needed for the C# delta generation comparison described above.
Come find me on the F# Discord, or open an issue.