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

Add thread safety #31

Closed
wants to merge 6 commits into from
Closed

Add thread safety #31

wants to merge 6 commits into from

Conversation

mwoollard
Copy link
Contributor

Add locking around resolution and dictionary modification to ensure thread safety

My understanding is Dip isn't currently thread safe when resolving, with risk of two instances of singleton being returned if same type requested at same time from different threads. How about this change to address this? I'd ideally want any container I used to be thread safe :-)

Let me know what you think !

Kind regards
Mark

Add locking around resolution and dictionary modification to ensure thread safety
@AliSoftware
Copy link
Owner

We removed thread safety in the latest release because it lead to deadlocks when doing circular dependencies resolution (see CHANELOG)

I want to restore this thread safety as it is important but we have to be careful it doesn't generate issues with circular dependencies as demonstrated in the playground and add enough unit tests to ensure this in long term.

@mwoollard
Copy link
Contributor Author

OK - thanks for the feedback

@AliSoftware
Copy link
Owner

In your case I'm not sure objc_sync_enter and objc_sync_exit are reentrant and maybe we'll need to switch to NSRecursiveLock or something but then we'll have to study performance impact.

@ilyapuchka
Copy link
Collaborator

@mwoollard what's your use case where you need container to be thread safe?
As @AliSoftware said we need to make sure it does not dead lock on circular dependencies resolution. Did you run tests?

@mwoollard
Copy link
Contributor Author

@AliSoftware objc_sync_enter is recursive according to the comments in its definition header, stating it uses a recursive pthread_mutex internally. @ilyapuchka the current unit tests all passed. I can look adding some tests for thread safety if you'd like. Can run some performance comparisons with and without locking too. No specific use case right now as am just starting looking at IoC /w swift but multi-threaded access is likely as I move forward and just something I noticed.

Mark Woollard added 2 commits December 19, 2015 22:04
NSRecursiveLock is quicker than objc_sync_enter/exit so switch to it
Add init parameter to disable thread safety, default is enabled
@mwoollard
Copy link
Contributor Author

So tests suggest NSRecursiveLock is around 4 times more efficient than objc_sync_enter/exit (testing on 2 GHz Intel Core i7 MacBook Air). Using this as the lock approx doubles resolve time overhead (same system). Have committed some tests for threading and timing and added param to DependencyContainer init to allow thread safety to be turned off if desired.

}
else {
let resolvedInstance = builder(definition.factory)
return threadSafe {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe it will be better to lock only whole resolve<T, F>(tag tag: Tag? = nil, builder: F->T) throws -> T method that calls this one? The same will apply to other methods that will call _resolve in future (if there will be any). As for me less locking operations is better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, thinking about it a bit more, makes sense - less locking overhead

@ilyapuchka
Copy link
Collaborator

I think this thread safety parameter is an overkill. Dependency resolution is not intended to be used to resolve so many instances that it can drastically decrease performance compared to not using locks and that users would like to turn it off. For me it just makes api and implementation (optional lock) weird.
Also I would not include benchmark tests for in Dip tests. They are good for reference, but I believe are not needed every time.
I also have some other minor comments regarding code and testing circular dependencies, commented it in code.
Apart from that looks fine, thanks for pull request, @mwoollard !

Removed option to disable thread safety
Moved locking for resolve to single lock in public resolve func
Added locking in reset func
@mwoollard
Copy link
Contributor Author

Yes, thread safety option not needed, was added to compare performance

@ilyapuchka
Copy link
Collaborator

@AliSoftware your move, should we merge it?

@@ -57,13 +57,35 @@ public final class DependencyContainer {
configBlock(self)
}

// MARK: - Thread safety
private func threadSafe(closure: () -> Void) {

Copy link
Owner

Choose a reason for hiding this comment

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

For code style coherence, please remove leading and trailing empty lines inside both those threadSafe implementations.

@AliSoftware
Copy link
Owner

Apart from the nitpicking at code styling, LGTM 👍

Don't forget to add an entry in CHANGELOG.md for that change and to credit @mwoollard though 😉
Nice job!

@AliSoftware
Copy link
Owner

Before merging this there is two remaining things:

  • An entry in the CHANGELOG
  • Edit the README.md to remove / alter the paragraph talking about Dip not being thread-safe

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

Successfully merging this pull request may close these issues.

3 participants