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

Cannot inject a struct using a Func wrapper #109

Closed
rdeago opened this issue Apr 3, 2019 · 4 comments
Closed

Cannot inject a struct using a Func wrapper #109

rdeago opened this issue Apr 3, 2019 · 4 comments
Assignees
Labels
bug Something isn't working
Milestone

Comments

@rdeago
Copy link
Contributor

rdeago commented Apr 3, 2019

The situation

Using DryIoc v4.0.2:

  1. A class implementing a service takes a CancellationToken as a constructor parameter.

  2. I register a singleton CancellationToken in the container.

  3. I do not resolve the class directly, but use the Func<> wrapper to obtain a delegate returning an instance of the service.

Expected outcome

I would expect to be able to call the resolved delegate, obtaining an instance of the class implementing the service.

Actual outcome

When using FastExpressionCompiler (the default), DryIoc can resolve the delegate, but the resolved delegate throws a System.InvalidCastException with the following message:

Unable to cast object of type 'System.Threading.CancellationTokenSource' to type 'System.Threading.CancellationToken'.

When trying the same code in a .NET fiddle the exception thrown is different, maybe due to some sort of sandboxing on the server:

System.Security.VerificationException: Operation could destabilize the runtime.

When NOT using FastExpressionCompiler, DryIoc cannot even resolve the delegate. Instead a System.ArgumentException is thrown with the following message:

Expression of type 'System.Threading.CancellationToken' cannot be used for return type 'System.Object'

Things I've tried

  • Explicitly specifying Singleton reuse for the cancellation token vs. having a default Reuse of Singleton: no change.

  • Registering the service as transient vs. singleton: no change.

  • Registering both CancellationTokenSource and CancellationToken as singletons (with all the required Made trickery) vs. having an externally created CancellationTokenSource and using RegisterDelegate(_ => cts.Token, reuse: Reuse.Singleton): no change.

A PR with relevant unit tests in on its way.

@dadhi
Copy link
Owner

dadhi commented Apr 3, 2019

Thanks for reporting,
The problem is likely that CancellationToken is struct and not properly converted to object by DryIoc.
Here is the updated fiddle with the workaround: https://dotnetfiddle.net/UPiO5Y

Anyway, I am planning to fix the problem on DryIoc side.

@rdeago
Copy link
Contributor Author

rdeago commented Apr 3, 2019

Thanks a lot @dadhi for you time and patience. I have done some research of my own in the meantime, arriving to the same conclusion: DryIoc generates a lambda that tries to return an unboxed struct in lieu of an object.

This also explains the InvalidCastException, by the way.
The only field of a CancellationToken is a reference to its CancellationTokenSource; this makes it exactly the same size as a pointer.
When a CancellationToken is passed back via the stack instead of a reference to an object, no desyncing of the stack occurs. The caller, though, is expecting (a reference to) an object that can be cast to CancellationToken and what it gets instead is (a reference to) a CancellationTokenSource; hence the InvalidCastException.
Were we talking about some other value type, one with a different in-memory size, I guess things would get... interesting. 😄

I also thank you for the workaround, although I have always used RegisterDelegate for CancellationToken in applications. I came up with those two registrations you saw in the fiddle just for the sake of example, because the docs state that RegisterDelegate should be used as sparingly as possible.

I probably should have mentioned earlier that the application that's giving me problems runs under latest Mono on a Raspberry Pi. 😀 The exception thrown when using FastExceptionCompiler is different:

InvalidProgramException: Invalid IL code in (wrapper dynamic-method) FastExpressionCompiler.LightExpression.ExpressionCompiler: (FastExpressionCompiler.LightExpression.ExpressionCompiler/Closure`2<DryIoc.FactoryDelegate, DryIoc.IResolverContext>): IL_001b: ret

It seems to me that Mono throws a different exception, but for exactly the same reason: the fact that the RET is considered invalid kind of gives it away.

What's strange is that the exception occurs even when using RegisterDelegate. This too can be due to the different runtime, but leaves me puzzled nonetheless.

@rdeago rdeago changed the title Cannot use singleton CancellationToken with Func wrapper Cannot inject a struct using a Func wrapper Apr 3, 2019
dadhi pushed a commit that referenced this issue Apr 4, 2019
* Add unit tests for issue #109.

* Ignore failing tests.
@dadhi dadhi closed this as completed in a435b79 Apr 10, 2019
@dadhi dadhi self-assigned this Apr 10, 2019
@dadhi dadhi added the bug Something isn't working label Apr 10, 2019
@dadhi dadhi modified the milestones: v4.0.2, v4.0.3 Apr 10, 2019
@dadhi
Copy link
Owner

dadhi commented Apr 10, 2019

DryIoc v4.0.3 with the fix is out

@rdeago
Copy link
Contributor Author

rdeago commented Apr 11, 2019

Thanks a lot @dadhi!

Leszek-Kowalski pushed a commit to Leszek-Kowalski/DryIoc that referenced this issue Oct 11, 2019
* Add unit tests for issue dadhi#109.

* Ignore failing tests.
Leszek-Kowalski pushed a commit to Leszek-Kowalski/DryIoc that referenced this issue Oct 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants