-
Notifications
You must be signed in to change notification settings - Fork 471
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
Do we need a thread-safe alternative to ModuleScope.DefineType
?
#399
Comments
@stakx Thanks for raising the issue. I'm in for the option 4, however I'd vote to shape the API in a bit different way: public Type DefineTypeSynchronized(
bool inStrongNamedModulePreferably,
Func<ModuleBuilder, Type> builder)
{
TypeCache.Lock.EnterWriteLock();
try
{
var module = ObtainDynamicModule(disableSignedModule == false && inStrongNamedModulePreferably);
return build.Invoke(module);
}
finally
{
TypeCache.Lock.ExitWriteLock();
}
} Such API will allow to perform the lookup before creating any type - you might want to look up for the already defined type and return it if it exists. That's exactly the scenario I met - I need to define |
Wouldn't it be more logical to use a (Btw. you can still access the module by querying a |
One more thing to consider given that we are around. Currently you work with Therefore, I'm both hands for the option 4 where each access to the
Please provide a sample, not sure I got it 😟
The point is to test before creating a |
Ah, of course. Now I feel a little stupid. :-D Granted, perhaps such a new method would have to deal with failure more gracefully. It could be converted to e.g. the
|
Probably it's a good idea. But we must be sure that introduced API enables all the potential usages, rather than a single dedicated scenario. The way with callback provided us with flexibility (e.g. perform any non-trivial look-ups), while
It might be hard to follow this issue if we need to constantly switch between this issue and original PR caused it 😅 Therefore, let's omit that part from here 😄 The goal is to provide API which allows to synchronize type creation between |
ModuleScope.DefineType
ModuleScope.DefineType
?
Sorry for the silence from my end. The current API allows a user to obtain the lock and write multiple types before releasing the lock, is this something the new API should also support? |
@jonorossi Could please provide a snippet of code? I want to sync with Castle's code and my own code (so we all are taking the same lock) and am unable to find the appropriate API. |
|
This method doesn't use the
I wasn't thinking about that before, but yes, it could happen that user tries to create delegate with e.g. internal types for parameters. In this case he can use |
this.cacheLock = Lock.CreateFor(typeCache.Lock); Yes,
If both Moq and NSubstitute are doing the same thing for mocking delegates maybe this is something DP should support, quickly skimming the Moq codebase I don't yet understand why it needs to create an interface with an Invoke method matching the delegate's signature, if someone wants to explain it to me that would be great, otherwise I'll try to find some time to look at it. With the amount of work the .NET teams are putting into AOT I can see we'll have scenarios to support in the near future where we can't have others generating random types into our DP assembly, otherwise we won't be able to load up the DP assembly and for things to just work. Moq's I'd like to see if we can do "1. Deprecate ModuleScope.DefineType as well". |
I'll try to come up with a detailed explanation, right now I'm not too sure myself. I suspect it has to do with Moq being able to internally store delegate mocks the same way as regular class/interface mocks.
I have a feeling this might be problematic for downstream libs, but regarding Moq, I'll do some brainstorming to see whether there's another way to mock delegates that might also work for NSubstitute et al. If we cannot deprecate this, we could always use the callbsck approach we're using with |
@stakx thanks. I'm just brainstorming so I'm happy to see us unpick this and work out the best outcome once we know all the details. If we do have to go with the callback option then we should resolve the problem of Moq's delegate cache being broken if the assembly is saved and reloaded. |
I could explain why we do so for the NSubstitute. Previously to support delegate proxy we created the expression trees and compiled them. However, it has been reported that it was very slow:
I found a way to improve that:
It helped a lot:
Now delegate's proxy is only twice slower which is fine. It appeared to be very simple, but effective approach. And it allowed to unify the proxy behavior, as we use the Castle in all the scenarios.
I'm not sure I got the point why you are going to load the DP assembly with the already constructed interface types - they are created at run time, so you don't know about them upfront... 😕 In any case, if the problem is only that we might start to create the duplicated types, it's enough to use the namespace with Guid, so the interface types will be always unique 😉 Will add that at NSubstitute part, it's a very good point! |
Thanks @zvirja, I see it is a recent performance improvement this year. Looks like a good solution, and likely something DP should do for you.
Back in the old days of partial trust web hosting it was a common request from NHibernate users to be able to use lazy loading which wasn't possible under partial trust because you couldn't emit types; web app startup performance was also the other reason. Support was added to save and load dynamically generated types via This save/load functionality isn't available in our .NET Standard builds because it uses binary serialisation and also has some limitations with certain usage of
As you can see the problem isn't about duplicate types, you won't even get duplicate types because the scenario people would use this functionality is where refemit isn't available so you'd hit a |
@jonorossi Sorry for the belated reply and many thanks for such a detailed explanation - now I clearly see the point! 👍 🍷 As for the module save/restore, I see value for libraries like NHibernate, which is used for the production code. However, for test libraries probably that's not such important... 😕 Anyway, currently we have 2 open questions to discuss:
If we add delegates proxy support, NSubstitute will not need to use the emit anymore, so it would be a good feature. Likely, the Moq as well 😊 |
@jonorossi - As it turns out, adding a delegate proxying capability to DynamicProxy would be rather simple because the necessary bits already appear to be in place. (See PR #403.) It's mostly a question whether we actually want to do it, and what the API should look like, and how much of DynamicProxy we want to deprecate in the process. |
Speaking for Moq 4: Assuming that
So, from Moq's perspective, assuming that either (a) or (b) are feasible, deprecating |
@stakx I thought you discovered that emitting
I'm definitely in favour of it, I don't like that others are emitting into the DP assembly, it would also mean that Windsor can use this more public API. I'll check out the pull request. |
Right. And that makes it unsuitable for DynamicProxy because it's all set up for at most two dynamic modules and downstream code probably expects this. But in Moq, we don't have these constraints. Moq could produce as many dynamic assemblies as it wants and keep track of which ones have the visibility attribute(s) for which client assemblies. Like AArnott suggested, one would only generate a new dynamic assembly if none of the existing ones have suitable visibility attributes. But creating such refined machinery just to support delegate proxies would be total overkill IMHO, I hope it won't be necessary. 😁 It might be an interesting pattern to keep in mind for DP vNextNext, perhaps. |
@zvirja alerted me to the fact that after deprecating
Lock
(in #391), it seems no longer possible in the layers above DynamicProxy to useModuleScope.DefineType
in a thread-safe manner; see https://github.com/nsubstitute/NSubstitute/pull/420/files#r197765187.I see three options to resolve this:
Do nothing.
That is, leave it up to downstream libraries to ensure that if they access
ModuleScope
'sModuleBuilder
s, they must not call into theirProxyGenerator
at the same time.Deprecate
ModuleScope.DefineType
as well.Not very realistic because, for instance, NSubstitute and Moq both rely on this API in order to mock delegate types.
Revert Deprecate
Lock
andModuleScope
's public type cache API #391.I'm biased here, but I think it would be a pity because that PR went in the right direction of closing doors into DynamicProxy's internals.
Expose the
ReaderWriterLockSlim
lock thatModuleScope
uses internally.ModuleScope
has an API that exposes the twoModuleBuilder
s that it uses, which essentially means that once a user starts accessingModuleBuilder
s, all thread-safety guarantees are off. We can no longer expose aLock
, but we could expose the internalReaderWriterLockSlim
instead.Augment
ModuleScope
with a thread-safe alternative forDefineType
.Apart from (0) above, this is my personal preference. This could be in the form of a new method on
ModuleScope
which performs the necessary locking internally, for example:(This option would probably entail deprecating the
ObtainDynamicModule
API as well.ModuleScope
would eventually have a less powerful API, but one where thread-safety would already be taken care of for downstream libraries.)@castleproject/committers, do you see any other options here? If not, which one of the above do you think we should go for?
The text was updated successfully, but these errors were encountered: