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

internal runtime API to return hot reload capabilities #50111

Closed
lambdageek opened this issue Mar 23, 2021 · 31 comments · Fixed by #51954
Closed

internal runtime API to return hot reload capabilities #50111

lambdageek opened this issue Mar 23, 2021 · 31 comments · Fixed by #51954

Comments

@lambdageek
Copy link
Member

lambdageek commented Mar 23, 2021

MonoVM and CoreCLR will both support hot reload in .NET 6. However the actual set of supported (non-"rude") edits are different between the runtimes. In future releases the capabilities of both runtimes will evolve. Part of #44806 and #45629

Related roslyn issue: dotnet/roslyn#49010

In a hot reload editing session, the runtime and the Roslyn compiler (mediated by a communication mechanism such as dotnet watch or the Visual Studio debugger) will need to agree on the supported edits.

One approach would be to build into Roslyn the knowledge of the capabilities of each runtime based on the reported runtime version. However in this case it would be difficult to mix compiler and runtime versions. Also if the runtime wants to disable an edit in a servicing release, it would require closely coupling servicing releases of (potentially several) Roslyn versions and the runtime.

Another approach is to expose a new API implemented by the runtime that returns a list of the supported capabilities:

 namespace System.Reflection.Metadata
{
    partial class AssemblyExtensions
    {
        /// Returns a description of the supported System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate edits.
        /// Returns string.Empty if applying updates is not supported by this runtime.
        internal static string GetApplyUpdateCapabilities ();
    }
}

The specific format of the return value would be implementation defined and in practice it would be a contract between the runtimes and Roslyn.

Example of capabilities description

(This is for illustration only and not part of the API definition)

For example the string might be a space separated sequence of tokens from the following set: Baseline, RuntimeEdits, AddDefinitionToExistingType, NewTypeDefinition. The proposal is to represent .NET 5 CoreCLR capabilities as Baseline plus everything that .NET 5 CoreCLR can do minus what .NET 6 MonoVM can do. That is, .NET 5 MonoVM would be represented by the empty set of capabilities, .NET 5 CoreCLR as Baseline plus everything that CoreCLR can do that .NET6 Mono cannot. In future releases, as MonoVM catches up it will start returning AddDefinitionToExistingType (for example). In future releases when one or both runtimes add a new capability, it will be added to the enumeration. In summary:

  • .NET 5 CoreCLR, .NET Framework: "Baseline AddDefinitionToExistingType NewTypeDefinition " (and ... TBD based on Mono capabilities )
  • .NET 6 CoreCLR: .NET 5 + "RuntimeEdits"
  • .NET 5 MonoVM: ""
  • .NET 6 MonoVM: "Baseline RuntimeEdits"
@lambdageek lambdageek added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Mar 23, 2021
@lambdageek lambdageek added this to the 6.0.0 milestone Mar 23, 2021
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Mar 23, 2021
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@lambdageek
Copy link
Member Author

/cc @tmat @mikem8361 @tommcdon

@jkotas
Copy link
Member

jkotas commented Mar 23, 2021

We would define negative capabilities for Mono,

That looks weird. I think we should define this just in term of the possitive capabilities.

Is there a reason why not just use a flags enum for this?

@jkotas
Copy link
Member

jkotas commented Mar 23, 2021

Potential alternative design is to avoid introducing a new API for this and just use the existing RuntimeFeature.IsSupported API. e.g. RuntimeFeature.IsSupported("AddFieldDefinition").

@tmat
Copy link
Member

tmat commented Mar 23, 2021

That looks weird. I think we should define this just in term of the possitive capabilities.

The assumption was that there wouldn't be that many negative and the set would be empty for .NET Framework/.NET 5.
Otherwise we would need to hard-code list of everything that .NET Framework, .NET 5 can do into the code that calls this API (since the runtimes do not have the API, the call would fail and the fall back would assume it's an older runtime).

Is there a reason why not just use a flags enum for this?

64 might not be enough bits.

@jkotas
Copy link
Member

jkotas commented Mar 23, 2021

It would be useful to see the full set we expect to add for .NET 5.

HotReloadCapability

This should not be called HotReload. We have dropped "HotReload" from the apply delta API as well.

@tmat
Copy link
Member

tmat commented Mar 23, 2021

It would be useful to see the full set we expect to add for .NET 5.

For .NET 5, it would be an empty set - the same as .NET Framework.
For .NET 6 it would probably also be one item: RuntimeEditsSupported - which would indicate that we can apply changes while the app is running (as opposed to when suspended at breakpoint).

For Mono we are discussing the set here: dotnet/roslyn#49010 (comment).

Something like: RuntimeEditsSupported, AddDefinitionToExistingTypeNotSupported, AddNewTypeDefinitionNotSupported, etc. TBD

@tmat
Copy link
Member

tmat commented Mar 23, 2021

Good question. I think that would need to be special-cased as "nothing is supported".

@tmat
Copy link
Member

tmat commented Mar 23, 2021

For reference, here are all the current rude edits. They do not map to metadata edits directly, but you can get sense of what is reported.

@jkotas
Copy link
Member

jkotas commented Mar 23, 2021

For Mono we are discussing the set here: dotnet/roslyn#49010 (comment).

Can you paste it here once the full set of capabilities is determined?

Otherwise we would need to hard-code list of everything that .NET Framework,

It does not sound that bad to me (vs. having to keep explaining till forwever the reason behind the possitive/negative split).

@tmat
Copy link
Member

tmat commented Mar 23, 2021

Can you paste it here once the full set of capabilities is determined?

Sure.

In time the negative ones become obsolete as Mono implements more features. The same happens with Roslyn's Rude Edits. We obsolete them as we allow more edits.

The problem with listing everything that should be enabled is also that we'd need to implement each of the positive capability in Roslyn - we can't assume that certain capabilities are always present. With negative ones it's much easier - we only need to revisit reporting rude edits that are affected by something that Mono does not have. We don't need to revisit every single rude edit and condition it based on a set of positive capabilities.

@jkotas
Copy link
Member

jkotas commented Mar 23, 2021

The problem with listing everything that should be enabled is also that we'd need to implement each of the positive capability in Roslyn - we can't assume that certain capabilities are always present

You do not have to. You can check whether the baseline is available, and disable all edits if it is not.

@tmat
Copy link
Member

tmat commented Mar 23, 2021

By "baseline" you mean .NET 5 - Mono 6? We could do that. Seems useless then to list capabilities in two places and subtract them, essentially ignoring them. I guess another way of doing this would be to have a single capability Baseline which would represent this difference (.NET 5 - Mono 6).

@tmat
Copy link
Member

tmat commented Mar 23, 2021

So, how about this?

  • .NET 5, .NET Framework: { Baseline, AddDefinitionToExistingType, NewTypeDefinition, ... TBD based on Mono capabilities }
  • .NET 6: .NET 5 + { RuntimeEdits }
  • Mono 5: { }
  • Mono 6: { Baseline, RuntimeEdits }

(exact names TBD)

@jkotas
Copy link
Member

jkotas commented Mar 23, 2021

I assume that .NET 6 would also include everything in .NET 5 bucket. Also, is the list really exactly the same between .NET 5 and .NET Framework? I thought that made some EnC improvements in .NET Core.

Sounds reasonable (exact names TBD).

@tmat
Copy link
Member

tmat commented Mar 23, 2021

I assume that .NET 6 would also include everything in .NET 5 bucket.

Yes, fixed.

@tmat
Copy link
Member

tmat commented Mar 23, 2021

I thought that made some EnC improvements in .NET Core.

I am not aware of any (so far).

@lambdageek
Copy link
Member Author

Updated the proposal with summary of the above discussion: positive flags; .NET5 CoreCLR - .NET 6 MonoVM as Baseline; changed the API name not to use HotReload.

@jkotas
Copy link
Member

jkotas commented Mar 24, 2021

How is the hotreload agent going to use this API? I assume that it will just enumerate everything, serializes it and sends it over to the manager to inform the compiler. That makes me think - would it be better just have an API that returns string?

public class RuntimeFeature
{
    public string? GetAssemblyUpdateCapabilities();
}

The string can be space separated list. The exact names are not visible in the API surface so making them pretty is less important. I think the negatives that were in the original proposal would be acceptable in this case (e.g. prefix the name with minus). And you do not have to go through API review and documentation for every EnC improvement.

@tmat
Copy link
Member

tmat commented Mar 24, 2021

A string would be fine with me.

@jkotas
Copy link
Member

jkotas commented Apr 13, 2021

linker can trim out hot reload support from managed libraries.

I do not think we want to attach trimming of the hot reload support to the capabilities string. It should be separate - already tracked by #51159.

@lambdageek lambdageek added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-needs-work API needs work before it is approved, it is NOT ready for implementation labels Apr 13, 2021
@stephentoub stephentoub added the blocking Marks issues that we want to fast track in order to unblock other important work label Apr 23, 2021
@bartonjs
Copy link
Member

We feel that rather than string tokenization this should just be a flags enum.

partial class RuntimeFeatures
{
    [Flags]
    public enum AssemblyUpdateFeatures : long
    {
        None = 0,
        RuntimeEdits = 1 << 0,
        AddDefinitionToExistingType = 1 << 1,
        ...
    }

    public static AssemblyUpdateFeatures AssemblyApplyUpdateCapabilities { get; }
}

is what got doodled during the discussion, those names weren't really discussed.

@bartonjs bartonjs added api-needs-work API needs work before it is approved, it is NOT ready for implementation and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Apr 27, 2021
@tmat
Copy link
Member

tmat commented Apr 27, 2021

As @jkotas pointed out a string has benefits over enum.

Beside those that Jan mentioned the components consuming this API do not target .NET 6, so there is no real value in making the values strongly typed. We will need to parse the values from strings/int anyways.

@jkotas
Copy link
Member

jkotas commented Apr 27, 2021

Does this actually need to be a public API? Can this be just an internal API, called via private reflection, just like the hot reload update handlers?

@lambdageek
Copy link
Member Author

In the API review, the feedback I heard was:

  1. A string API doesn't absolve us of having to come to agreement about the structure of the capabilities - roslyn and the runtimes still have to use the same representation.
  2. Adding a public API even if it's intended for the hot reload agent and roslyn doesn't preclude others from using the API - therefore rather than forcing those folks to use an ad-hoc string parser, we should just give them a bitmask of flags.

Does this actually need to be a public API? Can this be just an internal API, called via private reflection, just like the hot reload update handlers?

I don't think there's a technical need for it to be public.

@jkotas
Copy link
Member

jkotas commented Apr 27, 2021

Then let's just keep it internal and return string. We have a ton of internal surface to support debuggers, so an internal method like this would not be an outlier.

@lambdageek lambdageek removed api-needs-work API needs work before it is approved, it is NOT ready for implementation blocking Marks issues that we want to fast track in order to unblock other important work labels Apr 27, 2021
@lambdageek lambdageek changed the title Runtime API to return hot reload capabilities internal runtime API to return hot reload capabilities Apr 27, 2021
@lambdageek
Copy link
Member Author

If it's going to be internal, I prefer to make it a method, rather than a property (easier to call from reflection), and also move it to System.Reflection.Metadata.AssemblyExtensions so that it's near ApplyUpdate(). Updated the issue description.

@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Apr 27, 2021
@ghost ghost closed this as completed in #51954 Apr 28, 2021
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Apr 28, 2021
lambdageek added a commit to lambdageek/runtime that referenced this issue May 10, 2021
Adding infrastructure for hot reload testing.

For each test we define a new library assembly project.  The `.csproj` has a
`DeltaScript` property that specifies a JSON file that lists the name of an
initial source file, and a list of updated versions of that file.  The
`hotreload-delta-gen` tool runs during the build to read the delta script and
create deltas that incorporate the updates.

The main testsuite references all the test assemblies, and when a test runs, it
calls `ApplyUpdateUtil.ApplyUpdate` to load subsequent deltas
and then compares the results before and after an update.

Dependencies:

- https://github.com/dotnet/hotreload-utils  the `hotreload-delta-gen` binary
must be installed and on the PATH  (TODO: package it as a dotnet tool and
publish to a transport nuget package)

Needs work:

- Mono test runs need to pass the `FEATURE_MONO_METADATA_UPDATE` property to
  msbuild to the test project.  This is because not every mono configuration
  includes hot reload support.  Eventually this should be subsumed by the
  runtime API to querty hot reload capabilities dotnet#50111
- All runs need to pass `DOTNET_MODIFIABLE_ASSEMBLIES=debug` to the testsuite.
  Hot reload only works when:
  - the assemblies are compiled with the Debug compiler setting,
  - and, if the runtime is tarted with the above environment variable set.
- The GenerateHotReloadDeltas.targets needs some output file work to run `hotreload-delta-gen` less
  frequently.  Right now it runs every time even if everything is up to date.
  For CI testing we should ideally compile the deltas ahead of time once.

To try it out locally:

1. checkout and build `hotreload-utils` and do `dotnet publish --self-contained -r <RID>` to
put `hotreload-delta-gen` into that projects' `artifacts/...` forlder.  Add the
publish folder to your `$PATH`

2. In dotnet/runtime run
```
DOTNET_MODIFIABLE_ASSEMBLIES=debug MONO_ENV_OPTIONS=--interp ./dotnet.sh build src/libraries/System.Runtime.Loader/tests /p:MonoMetadataUpdate=true /t:Test
```

(CoreCLR doesn't need `MONO_ENV_OPTIONS` or `MonoMetadataUpdate`)
@ghost ghost locked as resolved and limited conversation to collaborators May 28, 2021
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
No open projects
Development

Successfully merging a pull request may close this issue.

5 participants