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

Threading issue in LifeTimeScope.GetOrCreateAndShare #666

Closed
hackfaq opened this issue Jul 24, 2015 · 10 comments
Closed

Threading issue in LifeTimeScope.GetOrCreateAndShare #666

hackfaq opened this issue Jul 24, 2015 · 10 comments
Labels

Comments

@hackfaq
Copy link

hackfaq commented Jul 24, 2015

I have WCF service, and on method calls I get exception:

An item with the same key has already been added.

System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
   в System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   в System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
   в Autofac.Core.Lifetime.LifetimeScope.GetOrCreateAndShare(Guid id, Func`1 creator)

as I investigated sources such error can't happen, because it checks before add.

@tillig
Copy link
Member

tillig commented Jul 24, 2015

Can you please provide a repro and post it somewhere? Given many people are successfully using Autofac with WCF and not seeing this error, we'll need to see it ourselves in order to troubleshoot.

@hackfaq
Copy link
Author

hackfaq commented Jul 25, 2015

look at image, I already have TicketController in _sharedInstances with id, but it tries tp insert again.
image

@hackfaq
Copy link
Author

hackfaq commented Jul 25, 2015

I have rewrited method:GetOrCreateAndShare, added check for key, and it it works now.

public object GetOrCreateAndShare(Guid id, Func<object> creator)
        {
            if (creator == null)
            {
                throw new ArgumentNullException("creator");
            }
            lock (_synchRoot)
            {
                object result;
                if (!_sharedInstances.TryGetValue(id, out result))
                {
                    result = creator();
                    lock (_synchRoot)
                    {
                        if (!_sharedInstances.ContainsKey(id))
                            _sharedInstances.Add(id, result);
                    }
                }
                return result;
            }
        }

@alexmg
Copy link
Member

alexmg commented Jul 25, 2015

A unit test showing the issue along with the fix would be best. Wondering what exactly the cause is and a screen capture isn't something we can execute to understand the problem.

@hackfaq
Copy link
Author

hackfaq commented Jul 25, 2015

Dictionary is not thread safe, so it should be double checked, I think

@alexmg
Copy link
Member

alexmg commented Jul 25, 2015

I think I see the problem. I'll take a look. I suspect we should also prevent the creator function from being invoked a second time too, even if the second lock prevents the instance from being added to the dictionary.

@tillig
Copy link
Member

tillig commented Jul 25, 2015

I'm guessing the ContainsKey call is what made the difference (though it escapes me how, since it should have been covered by the try-get). The second/nested lock won't have any effect on the use of the dictionary because no other threads can get into that critical section while the outer lock is held.

@hackfaq
Copy link
Author

hackfaq commented Jul 25, 2015

@tillig tillig added the bug label Jul 27, 2015
@tillig tillig changed the title Issue with LifeTimeScope.GetOrCreateAndShare Threading issue in LifeTimeScope.GetOrCreateAndShare Sep 4, 2015
@alexmg
Copy link
Member

alexmg commented Nov 10, 2015

This isn't a threading issue at all. The problem is that while creating an instance of A, you are invoking a factory during its construction that creates another instance of A. The factory generated A gets added to the dictionary because its first to complete creation, and when the original A is finally being added to the dictionary it's too late. We don't detect this in the circular dependency check because the initial and pending activation of A hasn't yet completed.

Your second unit test passes because when B is created first, it creates the instance of A via the factory, and when it then requests A as its second constructor parameter, the instance already exists and no attempt to create it is made.

The code change you made basically allows a second instance to be created even though you are only supposed to receive one per lifetime scope (A is configured as InstancePerLifetimeScope). This is being prevented in the current code through the exception, though a meaningful exception warning you what is happening would be better. I'm attempting to do exactly that but have to battle through the DNX RC2 upgrade first.

alexmg added a commit that referenced this issue Nov 13, 2015
…LifetimeScope service attempts to create an instance of itself during its construction.
@alexmg
Copy link
Member

alexmg commented Nov 13, 2015

I have updated LifetimeScope to throw a DependencyResolutionException with the message below:

The constructor of type '{0}' attempted to create another instance of itself. This is not permitted because the service is configured to only allowed a single instance per lifetime scope.

That should be more helpful than the dictionary exception.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants