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

Implement Startup hooks support in Mono #80391

Merged
merged 17 commits into from
Jan 18, 2023

Conversation

lambdageek
Copy link
Member

@lambdageek lambdageek commented Jan 9, 2023

Fixes #47462

CoreCLR This also makes some changes to CoreCLR to decouple EventPipe and startup hooks. Presently if startup hooks are disabled, RuntimeEventSource.Initialize is never called. The PR makes the two features independent by moving runtime event source initialization out of the startup hook feature check.
TODO:

  • Make ios functional test
  • Make android functional test
  • Make wasm functional test
  • Make an Issue to add mobile and wasm workload samples (once we have WBT for mobile) ==> [test] Add mobile workload test for startup hooks for Mono #80742
  • Fix error handling - the startup hooks design doc says the app should terminate if a hook has an unhandled exception. Right now mono cleans up the error and ignores it.

@lambdageek lambdageek marked this pull request as ready for review January 10, 2023 15:43
@lambdageek lambdageek closed this Jan 10, 2023
@lambdageek lambdageek reopened this Jan 10, 2023
@lambdageek lambdageek force-pushed the feature-startup-hooks branch 2 times, most recently from 6acc15b to 43a46d6 Compare January 10, 2023 21:23
@vargaz
Copy link
Contributor

vargaz commented Jan 11, 2023

The mono changes look ok to me.
Would this have any impact on linked app size and startup time ?

@lambdageek
Copy link
Member Author

The mono changes look ok to me. Would this have any impact on linked app size and startup time ?

Linked size shouldn't be affected. Unless you set <StartupHookSupport>true</StartupHookSupport> the .NET SDK will set IsSupported to false, and the managed code should drop out.

Startup time might be affected a little bit because of the StartupHookProvider lookup.

Copy link
Member

@vitek-karas vitek-karas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CoreLib changes look good.

@vitek-karas
Copy link
Member

Curious about the trimming story - in CoreCLR we simply don't support startup hooks with trimming. But this seems to make some effort to do that in mono. The problem is that startup hooks are effectively plugins, and so with trimmed framework the plugins may break.

@lambdageek
Copy link
Member Author

lambdageek commented Jan 12, 2023

Curious about the trimming story - in CoreCLR we simply don't support startup hooks with trimming. But this seems to make some effort to do that in mono. The problem is that startup hooks are effectively plugins, and so with trimmed framework the plugins may break.

The primary use case for mobile right now is to allow hot reload to inject an agent into the app. Injecting the hook is actually a pretty static operation - the agent assembly has to make it into the app bundle that gets uploaded to the device or emulator - so there is a PackageReference, the StartupHookSupport SDK property is toggled (which toggles the feature flag), etc. So everything should be visible to the trimmer if it even runs (in Debug configurations the trimmer won't be trimming anything).

The only other trimming that is happening is the initial trimming of System.Private.CoreLib when we create the runtime packs. At that stage we say that runtime hook switch defaults to true and we add the process hooks function to the linker descriptor so it's kept around. It's possible that we drop some private CoreLib APIs that a startup hook may want to use that are otherwise completely unused in the runtime and aren't in the linker descriptor. That's always a risk with private reflection.


In general, I think any sort of more dynamic startup hook injection is unlikely to work on mobile. There's really no good way to get code onto a device without it being part of the app bundle. But running code before the user (or platform) code starts running is still useful.

@lambdageek
Copy link
Member Author

The event tracing failures on osx-x64 repro locally. Not sure how they're related but I'm sure it'll be educational...

@lambdageek
Copy link
Member Author

lambdageek commented Jan 12, 2023

@lateralusX @dotnet/dotnet-diag

So believe it or not the failure I'm seeing is due to the call in StartupHookProvider to RuntimeEventSource.Initialize(). Turns out mono already invokes that method from ep_finish_init. So when we run it a second time, we instantiate a second RuntimeEventSource instance and then things go wrong.

Questions:

  1. As far as I can tell, coreclr only calls RuntimeEventSource.Initialize from StartupHookProvider. Which means if !StartupHookProvider.IsSupported, the runtime event source is not initialized? Is that intentional? I could make mono do the same thing, but it seems weird to couple these two features. Or is there a second call site in coreclr somewhere?
  2. Alternately, I could make RuntimeEventSource.Initialize safe to call multiple times by only creating a new instance if there is not one already (probably using a lock-object variant of LazyInitializer.EnsureInitialized)
  3. Alternately, I could add #if !MONO to StartupHookProvider and leave both runtimes with the same behavior we have today

Update I added option 2 to this PR, but we can go with another approach
Update 2 actually trying another option:
Make ep_rt_init_finish for CoreCLR invoke RuntimeEventSource.Initialize same as in mono, and remove the init call from StartupHookProvider

@noahfalk
Copy link
Member

As far as I can tell, coreclr only calls RuntimeEventSource.Initialize from StartupHookProvider. Which means if !StartupHookProvider.IsSupported, the runtime event source is not initialized? Is that intentional?

No, that doesn't sound like a desired behavior to me. I don't see any reason that RuntimeEventSource should be conditional on having startup hooks enabled. I'm guessing what happened is that someone noticed it was important to initialize RuntimeEventSource prior to running startup hooks, and then separately someone else added an option to disable startup hooks and didn't recognize that the RuntimeEventSource.Initialize() should not be included in the scope of code that gets disabled. I'd propose we hoist the call to RuntimeEventSource.Initialize() outside the ProcessStartupHooks call.

@lambdageek
Copy link
Member Author

I'd propose we hoist the call to RuntimeEventSource.Initialize() outside the ProcessStartupHooks call.

So change this to something more generic and call both the RuntimeEventSource initialization and the startup hook initialization from there?

static void RunStartupHooks()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END;
MethodDescCallSite processStartupHooks(METHOD__STARTUP_HOOK_PROVIDER__PROCESS_STARTUP_HOOKS);
processStartupHooks.Call(NULL);
}

@noahfalk
Copy link
Member

So change this to something more generic and call both the RuntimeEventSource initialization and the startup hook initialization from there?

Yeah, that sounds good to me. Perhaps something like:

void ManagedStartup() // call this from native runtime startup code
{
    InitBeforeUserCode(); // run EventSource initialize in here
    ProcessStartupHooks(); // do the rest of the pre-existing startup hook logic here
}

@lambdageek
Copy link
Member Author

lambdageek commented Jan 13, 2023

So change this to something more generic and call both the RuntimeEventSource initialization and the startup hook initialization from there?

Yeah, that sounds good to me. Perhaps something like:

void ManagedStartup() // call this from native runtime startup code
{
    InitBeforeUserCode(); // run EventSource initialize in here
    ProcessStartupHooks(); // do the rest of the pre-existing startup hook logic here
}

Hmm... that's actually not very linker friendly. Ideally I'd like to trim ManagedStartup if its body is entirely empty because both eventpipe and startup hooks are disabled. Otherwise this could be a startup perf regression for Mono in published mobile/wasm apps.

I guess the other way to go here is to make coreclr more like mono: call the RuntimeEventSource.Initialize from ep_rt_init_finish (ultimately called by EnsureEEStarted from CorHost2::Start()) and remove it from StartupHookProvider.ProcessStartupHooks (called from ExecuteMainMethod from Corhost2::ExecuteAssembly).

@lambdageek
Copy link
Member Author

Okay, CoreCLR now follows Mono's model: RuntimeEventSource.Initialize is invoked from EventPipeAdapter::FinishInitialization() and is not related to startup hooks.

Copy link
Member

@lateralusX lateralusX left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mono bits LGTM!

On Mono the method is called from ep_rt_mono_init_finish and also from
StartupHookProvider.ProcessStartupHooks.

CoreCLR only calls it from StartupHookProvider.  So if startup hooks
are disabled, the runtime event source is also disabled.
This reverts commit 07219db97e736bd49111dff8a605550909386b12.
…nishInitialize

Decouple eventpipe initialization from startup hooks.

Previously if startup hooks were disabled, the runtime event source
would not be initialized.
Also move the call out of EventPipeAdapter, back to ExecuteMainMethod.
EventSource is independent of EventPipe (for example if ETL is used,
or lttng)
A common configuration for coreclr is event source enabled, startup
hooks disabled, so at least one managed call is inevitable.  Since we
have to call into managed no matter what, let the trimmer determine
what happens once we get there.

This is different from mono where published trimmed apps may have both
startup hooks and event source disabled.  In that case we would rather
avoid a managed call to an empty method early in startup.
@lambdageek
Copy link
Member Author

Verified manually (by overwriting files in runtime packs) that the Microsoft.Android SDK works with this implementation of startup hooks
Screenshot 2023-01-18 at 12 13 01

@lambdageek
Copy link
Member Author

CoreCLR eventsourceerror failure is #80666

I think this is ready to merge

@lambdageek
Copy link
Member Author

lambdageek commented Jan 18, 2023

linux-arm64 llvmfullaot timeout is known #80805

@lambdageek lambdageek merged commit 3c3cc44 into dotnet:main Jan 18, 2023
mdh1418 pushed a commit to mdh1418/runtime that referenced this pull request Jan 24, 2023
…dotnet#80391)

Fixes dotnet#47462 

**CoreCLR** This also makes some changes to CoreCLR to decouple EventPipe and startup hooks. Presently if startup hooks are disabled, `RuntimeEventSource.Initialize` is never called.  The PR makes the two features independent by moving runtime event source initialization out of the startup hook feature check.

* Implement startup hooks support in Mono

* Keep StartupHookProvider.ProcessStartupHooks under feature flag

* Don't catch/cleanup the exceptions from startup hooks.

* Add an ios simulator startup hook functional test

* Implement Android functional test

* Add WASM functional test

* Make a single managed startup method for CoreCLR

A common configuration for coreclr is event source enabled, startup
hooks disabled, so at least one managed call is inevitable.  Since we
have to call into managed no matter what, let the trimmer determine
what happens once we get there.

This is different from mono where published trimmed apps may have both
startup hooks and event source disabled.  In that case we would rather
avoid a managed call to an empty method early in startup.

* fix build and line damage
@ghost ghost locked as resolved and limited conversation to collaborators Feb 18, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for host startup hooks to Mono
6 participants