-
Notifications
You must be signed in to change notification settings - Fork 6
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
Named scope throws ScopeDisposedException when using "Release" configuration #13
Comments
As we were having the same issue, I was looking into what you posted here, trying to reproduce it with my own solution - yours has been scraped from tinyupload. When you're saying "IData objects are obtained using factory method" you should be saying "IData object", as for this scope there should only be one IData instance per IMainProcessor instance. Can you please reupload your VS solution or modify my example below, so that I can have a look at it to find out if it is the same issue that our company is dealing with? What I have been trying out without attached debugger and with Release build configuration did not yield any ObjectDisposedException. Our application crashed only once with said exception and we could not reproduce it. class Program {
static void Main(string[] args) {
var k = new StandardKernel();
k.Bind<IMainProcessor>().To<MainProcessor>().DefinesNamedScope("TheScope");
k.Bind<ISecondaryProcessor>().To<SecondaryProcessor>();
k.Bind<IDataFactory>().ToFactory();
k.Bind<IData>().To<Data>().InNamedScope("TheScope");
var mainProcessor = k.Get<IMainProcessor>();
mainProcessor.SecondaryProcessor.CreateDatas();
}
}
public interface IDataFactory {
IData Create();
}
public interface IData {}
public class Data : IData {
private static int instanceCounter = 0;
private int instance;
public Data() {
instance = instanceCounter++;
Console.Out.WriteLine("Creating new Data {instance}");
}
}
public class SecondaryProcessor : ISecondaryProcessor {
public IDataFactory DataFactory { get; set; }
public SecondaryProcessor(IDataFactory dataFactory) {
DataFactory = dataFactory;
}
public void CreateDatas() {
while (true) {
DataFactory.Create();
}
}
}
public interface ISecondaryProcessor {
void CreateDatas();
}
public class MainProcessor : IMainProcessor {
public ISecondaryProcessor SecondaryProcessor { get; set; }
public MainProcessor(ISecondaryProcessor secondaryProcessor) {
SecondaryProcessor = secondaryProcessor;
}
}
public interface IMainProcessor {
ISecondaryProcessor SecondaryProcessor { get; set; }
} I used the required extensions Factory, NamedScope and ContextPreservation, my packages.config looks like this: <packages>
<package id="Castle.Core" version="3.2.0" targetFramework="net452" />
<package id="Ninject" version="3.2.2.0" targetFramework="net452" />
<package id="Ninject.Extensions.ContextPreservation" version="3.2.0.0" targetFramework="net452" />
<package id="Ninject.Extensions.Factory" version="3.2.1.0" targetFramework="net452" />
<package id="Ninject.Extensions.NamedScope" version="3.2.0.0" targetFramework="net452" />
</packages> |
I have re-uploaded the test project here: http://s000.tinyupload.com/index.php?file_id=60787475607892861020 - I can reproduce the issue every single time using this solution. Make sure you do not use the vshost binary (do not use VS environment to run this). This bug is reproducible when running the standalone .exe file built using "Release" configuration. |
Thanks for reuploading. |
Please let me know if you happen to encounter the same problem again. I hope we can bring a developer's attention to this issue. |
Could this be an explanation of that problem? |
I am not sure, but I don't think so. Remo's answer indicates that in such case, the parent object is ready for deactivation as well, but in the test solution that I have uploaded, the parent object's work is not done as the for loop would normally keep executing. |
I'm unable to access the uploaded repro (because our proxy prevents me from accessing any file sharing services :( ). So just a quick question: |
I just changed the uploaded solution from bartuszekj to this:
I. e. I introduced a static field which holds the IMainProcessor instance. With this and without debugger and with release configuration, the problem does not occurr anymore. As soon as I switch it back to the fieldless version, I get the ScopeDisposedException. Does that mean, IMainProcessor is cleaned up? |
It seems to be a JIT thing, not a Ninject issue: |
@b3rnhard This also means something like processing is not async, is it? |
Yes, any need for the
|
Thank you @b3rnhard and @BrunoJuchli! So it looks like GC is picking that object up almost immediately, and adding a destructor indeed confirms this: ~MainProcessor()
{
Console.WriteLine("~MainProcessor()");
} It also looks like the fact that this is only happening when running a "Release"-built code, might be explained in one of the comments here: https://stackoverflow.com/questions/90871/debug-visual-studio-release-in-net
This makes sense and explains why isn't the public static void Main()
{
// Simulate heavy load, collect frequently
var task = new Task(() =>
{
while (true)
{
Thread.Sleep(500);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
}
});
task.Start();
var settings = new NinjectSettings {CachePruningInterval = new TimeSpan(0, 0, 5)};
IKernel kernel = new StandardKernel(settings, new NinjectBindingsModule());
PrintLoadedModules(kernel);
var processor = kernel.Get<IMainProcessor>();
processor.Process();
GC.KeepAlive(processor); // Add this
} This indeed works and lets "Release" code finish gracefully. I think this ticket can now be closed. |
This should work, too:
That way, the |
Yes, you are absolutely right, @b3rnhard. Your approach is definitely a better one. Thank you. |
You're welcome. Learned something, too. 👍 |
Further investigation on our project showed that:
I believe that OPs test solution did not expose the issue that is still existing in Ninject |
Thanks for the update. Any chance you can provide a mock-up solution with this setup that you have just described, so that I can confirm that I can reproduce that too? |
It wasn't a Ninject bug after all, "just" a design inconvenience of Ninject. I've added a repo to Github that explains and demonstrates the behavior here: https://github.com/b3rnhard/NinjectNamedScopesStrangeness |
Using named scopes in code built using "Release" configuration can cause scope objects to be disposed of and garbage collected prematurely, which in turn causes
ScopeDisposedException
to be thrown. This issue does not manifest itself when using "Debug" configuration.Here's a sample binding configuration that allows this to happen:
Main method retrieves
MainProcessor
viaKernel.Get<IMainProcessor>()
.ISecondaryProcessor
is passed toMainProcessor
via constructor injection, andIDataFactory
is passed toSecondaryProcessor
via constructor injection. Then, insideSecondaryProcessor
,IData
objects are obtained using factory method.If GC occurs while
SecondaryProcessor
works on itsIData
object(s) in a loop,GarbageCollectionCachePruner
will kick in and collect the named scope reference object. This results the subsequent request to retrieveIData
using factory methods to fail, andScopeDisposedException
to be thrown.Changing the binding setup by moving the
DefinesNamedScope("TheScope")
declaration to theSecondaryProcessor
's binding seems to work in this case, but it's not a viable solution, as multipleSecondaryProcessor
instances, that need to share the sameIData
object, might need to be created.A Visual Studio solution illustrating this problem can be downloaded from here: http://s000.tinyupload.com/index.php?file_id=60787475607892861020
Behavior can be reproduced by first building the solution using "Release" configuration, and then running the executable without debugger attached (e.g. using windows explorer).
The text was updated successfully, but these errors were encountered: