-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Support custom hosting a hybrid between app and component #35465
Comments
Tagging subscribers to this area: @vitek-karas, @swaroop-sridhar |
We've been discussing something like this recently, thanks for bringing this up again and actually starting the technical solution. I do like the description of "it's like app and it's like component at the same time" 👍 . Although these two are very related I think it might be beneficial to handle them separately: Allow self-contained runtime in more scenariosThis can be achieved in two ways:
Both can be done, the main differences are:
The one which I've seen recent requests for is much more in line allowing SCD with The obvious limitation would be that SCD Note: Technically it might be possible to allow SCD to be loaded first and then any number of FDDs if they match as that is basically the same scenario as loading plugins to SCD app - which is supported. But for now we can disallow that for simplicity and for lack of scenarios. Scenario where this ability is useful without the second (loading into default load context): Native app which needs to load multiple managed components which are all implemented together in one "large" SCD project but still want ALC isolation for some reason (maybe in the future combined with unloadability). I don't have a specific example in mind right now - but it doesn't sound too crazy. Allow loading "components" into the default load contextThis has been actually discussed during the initial proposal for the new hosting APIs. In the end we decided not to do this as we didn't have enough scenarios to justify the added complexity. But it is a natural extension. To make this possible we actually designed the API to allow it in the future. The
Please note that while I would probably do this by defining a new struct to pass in the struct load_assembly_and_get_function_pointer_parameters
{
size_t size; // Acts as a version number effectively
bool load_into_default_load_context;
} And then redefine the typedef int (CORECLR_DELEGATE_CALLTYPE *load_assembly_and_get_function_pointer_fn)(
const char_t *assembly_path /* Fully qualified path to assembly */,
const char_t *type_name /* Assembly qualified type name */,
const char_t *method_name /* Public static method name compatible with delegateType */,
const char_t *delegate_type_name /* Assembly qualified delegate type name or null */,
const load_assembly_and_get_function_pointer_parameters *parameters /* additional parameters */,
/*out*/ void **delegate /* Pointer where to store the function pointer result */); Existing code would keep working as-is, and new code can pass in the parameters and alter the behavior. This behavior has actually been asked for already without the SCD support - so we know there are scenario which want this regardless of the first change. Given that this is a non-trivial change to the native hosting design, could you please start a separate PR (separate from the implementation PR) to update the design doc. That would be a good place to have the detailed design discussion. The doc also has some more details on interactions of the various APIs and hosting modes and it should help us make sure that the proposed changes play nice with the existing functionality. |
Can you give me an idea of whether this can make it in .NET 5? Will the added complexity of getting this working in the existing API's affect if it does? To be honest, using your proposal would require a lot of additional work and I don't want to spend time on this right now if it has to wait for .NET 6. |
This can definitely make 5.0 (there's still probably around 2 months before any kind of lock down for the release). I'll have to think about this some more:
Honestly the cleanest way to fix this would be to fix the SDK issue described above, but that's not easy to coordinate. I assume you have a specific scenario in mind when proposing this - what are the aspects of this which you need:
I understand that adding new APIs to solve the problem is relatively clean in this case, but it would mean to maintain those going forward (basically forever). So we need to make sure that whatever new concepts we introduce make good sense and play well with everything else. |
The specific scenario is the one in the issue I linked to - the Wix Toolset's bootstrapper functionality. It must start native because it's an installer, so it can't rely on anything being on the target machine. SCD support is a must - this allows users to write the BA in managed code without any dependencies being on the target machine. Loading into the default ALC is required to be able to workaround any bugs, limitations, etc. around running in a non-default ALC that are lurking in .NET Core. I also feel like I need to point out that you can't currently shim around this in the SCD scenario because you can't even call Do we need to decide on using the existing APIs vs new ones here or should I figure out how to write both of them up in a design PR? |
I'll discuss this with couple of people on our end (calls - as that's faster and I will update the issue with any notes I'll have). Over night I realized two additional things we need to consider: SDK support for SCD components.NET Core SDK currently doesn't really support building self-contained "components" - that is classlib projects. Unfortunately this has been "broken" for a while: dotnet/sdk#2549. It's possible to force it on the command line, but the result is "unknown" - it might work, but it might not - we simply don't test this scenario. So in this sense the only "good" way to produce an SCD is building it as an application. Allow usage of
|
I can't help but notice that you seem to keep on trying to frame this hybrid scenario as a component, but it really doesn't fit into any of the design decisions that have been made around them. It is essentially an application. I don't mind reusing the |
I chatted with @elinor-fung and we agreed that the app approach is something which makes sense and should work:
To react to the I do agree that the necessity of a delegate is unfortunate. The underlying reason is that it's implemented fully in C# and currently C# doesn't have a way to express the "random" delegate. This should be possible to "fix" when C# supports function pointers. Exposing the I will chat with couple more people tomorrow, but right now I don't think the main ideas will change that much. Would the above proposal work for you? |
That proposal works for me. But is the team really willing to let the native host potentially call |
Isolated ALC on SCD is almost fully supported as of 3.1. The only missing bit is the lack of framework/non-framework assembly information, but that is only important if the loaded component overrides framework assemblies - which is very rare (we know some apps do it in ASP.NET, but not many and the need for this is decreasing over time). This is basically the same as loading managed plugin into SCD app - should work just fine in 3.1 and 5. The reason I'm hesitant adding |
The lack of framework/non-framework assembly information also means that framework assemblies can get loaded into the isolated ALC, when they should only ever be loaded into the default ALC. |
That should really only happen if the component is built wrong (in 99% of cases).
So in short - if the component contains a framework assembly in it - it's a problem. For FDD it's not such a big problem, it should mostly work well. For SCD it may work a little less due to the above mentioned issue in SDK. With current SDK on 3.0 and above there are only a few assemblies which can be included in the app and the framework at the same time - there are only a few assemblies which are part of the framework but we also publish them as separate NuGet packages which is the only normal way to get such assembly into the component. That is obviously ignoring intentional manipulation of files and such - but I discount that as a reasonable scenario. |
I'm a little confused because in this scenario we are loading the app not a component. If it's SCD, then of course it's going to have framework assemblies in its deps.json. I thought framework assemblies are only supposed to be loaded once and that problems could occur if they're loaded into multiple ALCs. Also, I thought that framework assemblies are not supposed to be unloaded, which might not be possible in this scenario today but could be if the option to make the isolated ALC collectible is added later. |
You're right - I didn't realize this. For the SCD scenario loading into secondary ALC is probably a bad idea. Loading into default should work just fine. But it's not as clear cut - I could Right now I don't know if there's something we can do in the code to make this easier... it's "Easy" to document, but that tends to not work very well. |
It took me a while to formulate my response to this. I believe that
I called this third concept "managed host" in my initial solution. In my scenario there are basically three parts of the application - native, managed, and .NET Core host. The .NET Core host has a native dll (native host) and a managed dll (managed host). The native host has (what was supposed to be) a simple job - start the runtime and call into the managed host. The managed host is responsible for getting the managed part of the application up and running. Originally I didn't think it really mattered whether to use |
@rseanhall I think I may have missed it, why is adding this functionality to |
Here are my initial comments on using |
What is the next step for this? It seems like a decision needs to be made about whether to add a new api or use the existing one. |
@vitek-karas I would like to help with this proposal. I have some ideas that we could employ that might make future extending simpler for external contributors like @rseanhall. |
I hate to be that guy that constantly asks for updates, but I don't understand the process here. Should I just keep asking for updates every week until this is put into a milestone? |
Sorry for the delay. I finally got to this and spent some time playing with the hosting layers. The result is #36990 which contains basically a proposal for the new scenario. @rseanhall can you please read through that and make sure it covers your requirements? Any feedback would be more than welcome. Please note that I can't commit to implementing this in .NET 5 (for resourcing reasons). That said, assuming we get a consensus on the proposed design changes, anybody can go and do the actual implementation. It's also possible that we will find time to work on this ourselves, but I can't promise that. |
I stumbled across this issue while fixing a bug in the hosting code from nodeclrhost. The bug was that the hosted MVC app failed to load its main assembly, which was already loaded to execute the entry point. I solved it by setting the following runtime properties (which are explicitly not set for
As I do not fully understand why & how this works, i`m researching whether this is the right solution or if this is a misuse of the hosting API. From my understanding my scenario is also a hybrid between a component & an application, so is this related? Is the source of my Bug that I don`t want to hijack this Issue, but I do not have enough expertise at the moment to create a new Issue. Also I think that my scenario could help to provide some insight into a similiar use case of the hosting APIs. |
@sanosdole Feel free to create a new issue, the info you provided above is definitely enough to start a discussion. It's hard to tell exactly what's wrong without knowing the details of your setup. I only quickly looked at your code and it definitely could be the secondary ACL problem.
Since it seems you're trying to run an arbitrary application this is likely to break as that application probably lives in a separate directory. One way to solve this would be to create another This would also give you nice isolation of your app from the hosting code. It's possible that if the scenario discussed in this issue was enabled, then you could use Just curious: Will your code ever try to load/run more than one application in a given process? |
Short Answer: This is not an epected use case. It is not prevented but would be unusual. The main intended use cases are writing a node/electron application using dotnet. The dotnet application is run by a js method I used Edit: This is just right now. I could imagine that the project one day will support creating node packages using dotnet. Then we would potentially load multiple assemblies in one process, and use an API like |
@sanosdole If I understand your app correctly, this is your workflow:
If so, the isolated However, I think that's the wrong approach because this issue is for your use case. Both of us has a native host that wants to call into our own managed code, which in turn calls into a user provided application. The difference is that I don't support multiple managed applications. My recommendation is that you don't either. The reason for this is that .NET Core has poor support for dynamically loading code (if you want an example look into creating a custom MSBuild task with complex dependencies), let alone the issue of each application needing to be able to run in the first loaded runtime. From a compatibility perspective, you're going to run into far fewer issues by letting the framework initialize the runtime based on the user's app through apphost mode ( If you're interested in how I'm accomplishing this in 3.x, my native host is here and my managed host is here. |
I think you meant to say These behaviors were kept to maintain at least some level of compatibility with .NET Framework. If you're writing ALC aware code, it's much better to call |
His project is calling |
The "current" ALC should be determine as:
If you see different behavior please let us know. Note: There's a way to change that behavior via |
Oh, that's what it means in that table by "Inferred from caller". Good to know. After thinking about it some more, I think this is what's happening. Originally, we weren't setting the To try to fix this, we started setting the While typing this out, I've figured out what my issue was. I originally wasn't calling |
Correct.
class AppDomain
{
public Assembly Load(string assemblyString) => Assembly.Load(assemblyString);
} Unfortunately also correct (I which we could just delete most of the assembly loading functions available on various classes as most of them are broken in subtle ways). I'm not even sure we can actually fix this on the |
For Search for "Inferred from caller" in https://docs.microsoft.com/en-us/dotnet/core/dependency-loading/loading-managed. |
To be honest even if we fixed the |
Just to clarify the
This works usually fine. But when testing with a MVC app, it failed when attempting to register types from the app assembly itself. This was due to It was all OK once I`m still not sure if this is an issue. We are basically a hybrid between an application and a plugin, so maybe it is totally fine that we have to set those runtime properties ourselves. I'm just pouring my scenario as input for further development of the hosting APIs, as well as trying to achieve a better understanding of the hosting model. |
It's all fine up to the point where something calls I think that for these kind of scenarios the managed code should be loaded "like an app", the only difference being that it won't be executed like an app. |
@vitek-karas Can you set the labels, milestone, etc? |
In .NET Core 3.x, work was done to make custom hosting easier. Support was added for an "application" and a "component". I need support for a hybrid between these two.
The general use case is for an application where its features are spread across native and managed code, with the entry point being in native code. The details of my specific use case are here: #34946.
It's like an "application" but not "component":
-Missing dependencies are an error.
-Loading into default ALC is expected.
-Any isolation must be implemented by the app itself.
It's like a "component" but not an "application":
static void Main(string[] args)
, no concept of argsUnique aspects for this hybrid scenario:
Reasons to be implemented in .NET Core instead of by the custom host using coreclr directly:
Proposal
Original proposal
Add two new exported functions to `hostfxr` - `hostfxr_initialize_for_managed_host` and `hostfxr_create_delegate`. These two functions are similar to the existing `hostfxr_initialize_for_dotnet_command_line`/`hostfxr_run_app` and `hostfxr_initialize_for_runtime_config`/`hostfxr_get_runtime_delegate` function pairs in that the handle created from `hostfxr_initialize_for_managed_host` can only be used with `hostfxr_create_delegate`.hostfxr_initialize_for_managed_host
is essentially the same ashostfxr_initialize_for_dotnet_command_line
but instead ofargc
/argv
, it takes the application, deps.json, and runtimeconfig.json as explicit parameters.hostfxr_create_delegate
is simply a wrapper around the create delegate functionality of coreclr.hostfxr_get_runtime_delegate
could not be reused since it always loads the target assembly into an isolated ALC (which is why it requires a handle fromhostfxr_initialize_for_runtime_config
- isolation is not supported in self-contained applications and it blocks on SCD).Design document update and discussion: #36990
The text was updated successfully, but these errors were encountered: