Repository navigation
Conversation
When libgit2 is configured with a custom allocator via GIT_OPT_SET_ALLOCATOR (such as in Ruby/Rugged bindings), memory allocations can trigger a garbage collection cycle. If the GC finalizer frees an unreachable repository, git_repository_free calls git_mwindow_put_pack, which attempts to acquire git_mwindow__mutex. Previously, git_mwindow_get_pack held git_mwindow__mutex across git_packfile_alloc. When an allocation triggered a GC pass, the thread deadlocked on the non-recursive mutex with itself. A similar issue existed in git_mwindow_open when allocating git_mwindow structures. Move git_packfile_alloc and git_mwindow allocations outside of the git_mwindow__mutex critical sections: * In git_mwindow_get_pack, perform an optimistic cache check, release the mutex before allocating, and perform a double-checked lookup when re-acquiring the mutex. * In git_mwindow_open, allocate git_mwindow structures outside the mutex and re-check the window list after re-acquiring the lock. * Add regression test in tests/libgit2/pack/sharing.c with a custom allocator that triggers re-entrant repository disposal during packfile allocation.
Author
|
@ethomson Not sure if you're the right person to ping, but would like your thoughts / review here. |
This branch has not been deployed
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary: libgit2 can deadlock when holding git_mwindow__mutex across allocations if a custom allocator, under GC pressure, runs a finalizer that re-enters libgit2 and tries to acquire the same mutex.
When libgit2 is configured with a custom allocator via GIT_OPT_SET_ALLOCATOR (such as in Ruby/Rugged bindings), memory allocations can trigger a garbage collection cycle. If the GC finalizer frees an unreachable repository, git_repository_free calls git_mwindow_put_pack, which attempts to acquire git_mwindow__mutex.
Previously, git_mwindow_get_pack held git_mwindow__mutex across git_packfile_alloc. When an allocation triggered a GC pass, the thread deadlocked on the non-recursive mutex with itself. A similar issue existed in git_mwindow_open when allocating git_mwindow structures.
sequenceDiagram participant Thread as Worker Thread participant Mutex as git_mwindow__mutex participant Alloc as Custom Allocator (Ruby xmalloc) participant GC as Garbage Collector Thread->>Mutex: 1. git_mutex_lock() [ACQUIRED] Thread->>Alloc: 2. git_packfile_alloc() -> malloc() Alloc->>GC: 3. Memory pressure triggers GC GC->>Thread: 4. Finalizer runs: git_repository_free() Thread->>Mutex: 5. git_mwindow_put_pack() -> git_mutex_lock() Note over Thread,Mutex: 6. DEADLOCK! (Mutex already held by Step 1 on same thread)Move git_packfile_alloc and git_mwindow allocations outside of the git_mwindow__mutex critical sections:
Why it emerged recently:
TypedDatainstead ofDatarugged#998), Rugged converted all classes to TypedData_Wrap_Struct with RUBY_TYPED_FREE_IMMEDIATELY [ref for repo], allowing Ruby's GC to run rb_git_repo__free synchronously inside xmalloc allocations and directly triggering the lock re-entrancy deadlock.I'm not sure if there are other places in the code where this can happen as well, but I suspect there are others. In general, we shouldn't do an allocation within a mutex, but especially since the allocator can do more than allocate.