Repository navigation
Thread safety implementation using locking #836
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
Merged
Merged
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
f07b098
Add LockMaster
srajko 64ba08a
Use LockMaster for sync and async functions
srajko cb92954
Move template specializations out of class definition
srajko 81740e3
Add repo locking for commits
srajko 12eca23
Remove use of auto
srajko 7579e3a
Add temporary unlock during callbacks
srajko 7d31f52
Test callback deadlock
srajko 908b8b5
Clean up mutexes
srajko c33fa10
Ensure mutexes are locked all at the same time
srajko 65a998d
Add opt-in / opt-out mechanism for thread safety
srajko 040a4ee
Fix uv_mutex_trylock response handling
srajko 5efab3f
Remove use of shared_ptr
srajko 4b506f7
Address c++11 warnings
srajko 7257e5e
Move LockMaster to its own files
srajko fb4603d
Reorganize and clean up code
srajko 90d8073
Use auto, range-based for loops, and for_each
srajko ab3d2da
Use ObjectInfo instead of pair for readability
srajko dbce34f
Initialize LockMaster before using it
srajko 1c6ea17
Remove unused else
srajko 7a5a37f
Make thread safety enable / disable more robust to toggling
srajko File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Move LockMaster to its own files
- Loading branch information
commit 7257e5ecf2ea810200aa8a8810c6f405a72a8d2c
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| #include <git2.h> | ||
| #include <uv.h> | ||
| #include <map> | ||
| #include "../include/lock_master.h" | ||
|
|
||
| std::map<const void *, std::pair<uv_mutex_t *, unsigned> > mutexes; | ||
| uv_mutex_t map_mutex; | ||
| uv_key_t current_lock_master_key; | ||
| uv_async_t cleanup_mutexes_handle; | ||
|
|
||
| void cleanup_mutexes(uv_async_t *async) { | ||
| uv_mutex_lock(&map_mutex); | ||
|
|
||
| for(std::map<const void *, std::pair<uv_mutex_t *, unsigned> >::iterator it=mutexes.begin(); it != mutexes.end(); ) | ||
| { | ||
| uv_mutex_t *mutex = it->second.first; | ||
| unsigned use_count = it->second.second; | ||
| // if the mutex is only referenced by the mutexes map, | ||
| // we can destroy it (because any LockMaster that is using the mutex would | ||
| // hold it in its object_mutexes) | ||
| if (!use_count) { | ||
| uv_mutex_destroy(mutex); | ||
| free(mutex); | ||
| std::map<const void *, std::pair<uv_mutex_t *, unsigned> >::iterator to_erase = it; | ||
| it++; | ||
| mutexes.erase(to_erase); | ||
| } else { | ||
| it++; | ||
| } | ||
| } | ||
|
|
||
| uv_mutex_unlock(&map_mutex); | ||
| } | ||
|
|
||
| void LockMaster::Initialize() { | ||
| uv_mutex_init(&map_mutex); | ||
| uv_key_create(¤t_lock_master_key); | ||
| uv_async_init(uv_default_loop(), &cleanup_mutexes_handle, cleanup_mutexes); | ||
| } | ||
|
|
||
| std::vector<uv_mutex_t *> LockMaster::GetMutexes(int use_count_delta) | ||
| { | ||
| std::vector<uv_mutex_t *> object_mutexes; | ||
|
|
||
| uv_mutex_lock(&map_mutex); | ||
|
|
||
| for (std::set<const void *>::const_iterator it = objects_to_lock.begin(); it != objects_to_lock.end(); it++) { | ||
| const void *object = *it; | ||
| if(object) { | ||
| // ensure we have an initialized mutex for each object | ||
| std::map<const void *, std::pair<uv_mutex_t *, unsigned> >::iterator mutex_it = mutexes.find(object); | ||
| if(mutex_it == mutexes.end()) { | ||
| mutex_it = mutexes.insert( | ||
| std::make_pair( | ||
| object, | ||
| std::make_pair((uv_mutex_t *)malloc(sizeof(uv_mutex_t)), 0U) | ||
| ) | ||
| ).first; | ||
| uv_mutex_init(mutex_it->second.first); | ||
| } | ||
|
|
||
| object_mutexes.push_back(mutex_it->second.first); | ||
| mutex_it->second.second += use_count_delta; | ||
| } | ||
| } | ||
|
|
||
| uv_mutex_unlock(&map_mutex); | ||
|
|
||
| return object_mutexes; | ||
| } | ||
|
|
||
| void LockMaster::Register() | ||
| { | ||
| uv_key_set(¤t_lock_master_key, this); | ||
| } | ||
|
|
||
| void LockMaster::Unregister() | ||
| { | ||
| uv_key_set(¤t_lock_master_key, NULL); | ||
| } | ||
|
|
||
| void LockMaster::Lock(bool acquire_mutexes) | ||
| { | ||
| std::vector<uv_mutex_t *> object_mutexes = GetMutexes(acquire_mutexes * 1); | ||
|
|
||
| std::vector<uv_mutex_t *>::iterator already_locked = object_mutexes.end(); | ||
|
|
||
| // we will attempt to lock all the mutexes at the same time to avoid deadlocks | ||
| // note in most cases we are locking 0 or 1 mutexes. more than 1 implies | ||
| // passing objects with different repos/owners in the same call. | ||
| std::vector<uv_mutex_t *>::iterator it; | ||
| do | ||
| { | ||
| // go through all the mutexes and try to lock them | ||
| for(it = object_mutexes.begin(); it != object_mutexes.end(); it++) { | ||
| // if we already locked this mutex in a previous pass via uv_mutex_lock, | ||
| // we don't need to lock it again | ||
| if(it == already_locked) { | ||
| continue; | ||
| } | ||
| // first, try to lock (non-blocking) | ||
| bool failure = uv_mutex_trylock(*it); | ||
| if(failure) { | ||
| // we have failed to lock a mutex... unlock everything we have locked | ||
| for(std::vector<uv_mutex_t *>::iterator unlock_it = object_mutexes.begin(); unlock_it != it; unlock_it++) { | ||
| uv_mutex_unlock(*unlock_it); | ||
| } | ||
| if(already_locked > it && already_locked != object_mutexes.end()) { | ||
| uv_mutex_unlock(*already_locked); | ||
| } | ||
| // now do a blocking lock on what we couldn't lock | ||
| uv_mutex_lock(*it); | ||
| // mark that we have already locked this one | ||
| // if there are more mutexes than this one, we will go back to locking everything | ||
| already_locked = it; | ||
| break; | ||
| } | ||
| } | ||
| } while(it != object_mutexes.end()); | ||
| } | ||
|
|
||
| void LockMaster::Unlock(bool release_mutexes) | ||
| { | ||
| std::vector<uv_mutex_t *> object_mutexes = GetMutexes(release_mutexes * -1); | ||
|
|
||
| for(std::vector<uv_mutex_t *>::iterator it=object_mutexes.begin(); it != object_mutexes.end(); it++) { | ||
| uv_mutex_unlock(*it); | ||
| } | ||
| } | ||
|
|
||
| void LockMaster::CleanupMutexes() | ||
| { | ||
| // schedule mutex cleanup on the main event loop | ||
| // this somewhat delays and debounces cleanup (uv_async_send coalesces calls) | ||
| uv_async_send(&cleanup_mutexes_handle); | ||
| } | ||
|
|
||
| LockMaster::TemporaryUnlock::TemporaryUnlock() { | ||
| if(!enabled) { | ||
| return; | ||
| } | ||
| lockMaster = (LockMaster *)uv_key_get(¤t_lock_master_key); | ||
| lockMaster->Unlock(false); | ||
| } | ||
|
|
||
| LockMaster::TemporaryUnlock::~TemporaryUnlock() { | ||
| if(!enabled) { | ||
| return; | ||
| } | ||
| lockMaster->Lock(false); | ||
| } | ||
|
|
||
| bool LockMaster::enabled = false; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ | |
| }, | ||
|
|
||
| "sources": [ | ||
| "src/lock_master.cc", | ||
| "src/nodegit.cc", | ||
| "src/wrapper.cc", | ||
| "src/functions/copy.cc", | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be pulled outside the
if/elsesince we do it in both cases.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I almost did that once too, going back through the code. Then I remembered that the following line (
mutexes.erase(to_erase);) invalidates everything referring to the erased element, includingitbefore it gets incremented. Soithas to be incremented before weerase. Inc++11,erasereturns an iterator that refers to the next element and that avoids the problem, but that particular functionality isn't available on some of our targeted environments.