Skip to content

Conversation

@stakx
Copy link
Member

@stakx stakx commented Aug 31, 2024

This is an alternative to #679. Consider it a proof of concept that is to be discussed further, over in #473. If we went ahead with this approach, the code here might have to be cleaned up a little more.

@stakx stakx marked this pull request as draft August 31, 2024 14:04
@stakx stakx force-pushed the enhancements/collectible-assemblies branch from 617627e to 42dfbc7 Compare October 19, 2024 18:32
@stakx stakx changed the title Draft: Support collectible assemblies via a new CollectibleProxyBuilder Support collectible assemblies via new CollectibleProxyBuilder Oct 19, 2024
@stakx stakx marked this pull request as ready for review October 19, 2024 18:33
@stakx stakx self-assigned this Oct 19, 2024
@stakx stakx added this to the vNext milestone Oct 19, 2024
@stakx
Copy link
Member Author

stakx commented Oct 19, 2024

@castleproject/committers, I'll let this PR sit for a few days, in case anyone wants to review. If there are no objections, I'll go ahead and merge it in a few days' time as the enhancement to DynamicProxy it contains is fairly trivial.

Copy link
Member

@jonorossi jonorossi left a comment

Choose a reason for hiding this comment

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

@stakx Looks good. Could we add a very basic unit test to ensure the this new proxy builder at least works?

@stakx
Copy link
Member Author

stakx commented Nov 24, 2024

Thanks for reviewing.

You're right, I forgot to write a test, since this started out only as a proof of concept. I'll add one.

@simonferquel
Copy link

Hi @stakx, I have some reserve about this approach as it will require all moqing frameworks to opt-into leverage this to be able to work without leaking Assembly Load Contexts in environments that leverage them.

The example of Unity 7 where we are moving to CoreCLR and take advantage of ALCs (AssemblyLoadContext) to allow users to iterate on their code without reloading the editor:

  • We load user code and packages into an unloadable ALC. This user code may contain tests which uses any Moqing framework built on top of Castle.Core
  • Whenever the user modify its code or update a package, we cleanup all references the engine has to user object, and unload the user ALC, compile new version of code, and reload the new ALC

If any non-unloadable ALC keep references to anything loaded within the User ALC, the unload will never complete (the GC will see outstanding references to some objects belonging to that ALC and thus will keep it alive). We call that ALC leaks in Unity.

With that PR, if a Moqing framework relying on Castle.Core wants to play nice with Unity (or any other environment that does leverage unloadable ALCs), it will need to explicitly opt-into using this new CollectibleProxyBuilder. I don't think it is the responsibility of the Moqing framework, but more about the hosting process to decide in which kind of ALC generated code should get loaded: that is why I first introduced a global static setting that a runtime environment could use to define this behavior (e.g. if Unity detects that Castle.Core is loaded, it would set the setting accordingly so that any moqing framework would generate its code in a way that plays nice with Unity runtime).
The default setting value that would depend on whether Castle.Core itself is loaded in a Collectible ALC makes sense to me, as it means it as been loaded as a dependency of something that may be collected at some point in the process lifetime, and so whatever it generated dynamically should not be able to keep its ALC alive and should thus be collectible. But I could live with that logic not enabled.

So what I really think we should have to define if the builders should emit collectible types is this:

  • A global setting that could let an hosting environment set the safe defaults (with optionally the "sensible defaults" I introduced in the original PR relying on detecting if Castle.Core assembly itself is in a collectible ALC)
  • An explicit way to let direct users of Castle.Core override this global setting

That could be implemented by having:

  • DefaultProxyBuilder add a protected virtual bool GeneratedCollectibleTypes property, implemented by looking at the global setting
  • Adding a CollectibleProxyBuilder and NonCollectibleProxyBuilder that would override the property to ignore the global setting

What would you think of that ?

@Romfos Romfos mentioned this pull request Dec 12, 2025
10 tasks
@stakx
Copy link
Member Author

stakx commented Dec 23, 2025

@simonferquel, I've decided not to answer right away because had I done so, you would have got my standard canned response: that DynamicProxy is a general-purpose library and shouldn't be opinionated (and that it's therefore really up to mocking libraries to opt-into the new CollectibleProxyBuilder.

After getting some distance, while I still believe the above statement to be true, I think I also see your point. DynamicProxy sometimes does try to make decisions itself so that things "just work" (e. g. when choosing whether to put generated proxy types in a strong-named or non-strong-named dynamic assembly).

I am, however, still uncertain whether the auto-detection logic you originally proposed would be correct in all circumstances. (I know: you've provided a global switch to adjust it when things go wrong. I'd still prefer there to be no such global switches, if possible.) My uncertainties are based on there being several aspects to the problem space that I don't fully understand yet:

  • AssemblyLoadContexts can be collectible or non-collectible, and the same goes for dynamic assemblies. So there are in theory four combinations... what are the implications of each of these?
  • One could base the decision either on where DynamicProxy's runtime is loaded (in a collectible or in a non-collectible AssemblyLoadContext?), or on where the types to be proxied are loaded (though that varies from one proxied type to the next)... or even a combination of both.

I have thought more on these, but I'll stop here for the moment. Perhaps you have some thoughts of your own regarding those questions that you'd care to share.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants