Skip to content

Keep libgit2 initialized for the lifetime of a Remote to fix ASAN leak - #32

Closed
alt-graph with Copilot wants to merge 2 commits into
mainfrom
copilot/fix-memory-leak-ubuntu-26-04
Closed

alt-graph with Copilot wants to merge 2 commits into
mainfrom
copilot/fix-memory-leak-ubuntu-26-04

Conversation

Copilot AI commented Oct 14, 2026 •

Copy link
Copy Markdown

ASAN reports a small leak (3 allocations, 200 bytes) originating in git_error_set when a git::Remote is destroyed. This is likely caused by the Remote outliving its Repository, not by libgit2 itself.

Repository calls git_libgit2_init()/git_libgit2_shutdown() in its constructor and destructor. A Remote can outlive its Repository, as test_Remote.cc does with repo.reset(). When that happens, git_remote_free runs after the final shutdown and sets a fresh thread-local error. Nothing frees that error afterwards.

  • Remote.h
    • Added a private LibGitInitGuard member that takes a libgit2 init reference on construction and releases it on destruction.
    • It is declared before remote_, so it is destroyed after git_remote_free runs.
    • The move constructor takes its own init reference, which keeps init and shutdown calls balanced when a Remote is moved.

I couldn't build or run the tests in my environment, so the fix is unverified. Please confirm with an ASAN build on Ubuntu 26.04.

Copilot AI linked an issue Oct 14, 2026 that may be closed by this pull request
Co-authored-by: alt-graph <28754274+alt-graph@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix memory leak under Ubuntu 26.04 Keep libgit2 initialized for the lifetime of a Remote to fix ASAN leak Oct 14, 2026
Copilot AI requested a review from alt-graph October 14, 2026 14:36
@alt-graph

Copy link
Copy Markdown
Member

Modified and cherry-picked into main.

@alt-graph alt-graph closed this Oct 14, 2026
@Finii

Finii commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

The fix is ok-ish, but maybe it is a bit too narrow minded?

It fixes cases when a Remote object is destroyed after (all) Repository objects are destroyed. Or the other way around, if a Remote is created first and then a Repository is created.

I find the LLM explanation a bit superficial and the fix incomplete.

In the Remote case, here, we can not libgit2_shutdown() before the remote_ data member is destructed, because remote_ is a unique_ptr with a specific destructor function, git_remore_free().

That is all sound and good, BUT we have a lot more objects here

using LibGitTree = std::unique_ptr<git_tree, void (*)(git_tree*)>;
using LibGitSignature = std::unique_ptr<git_signature, void (*)(git_signature*)>;
using LibGitIndex = std::unique_ptr<git_index, void (*)(git_index*)>;
using LibGitRepository = std::unique_ptr<git_repository, void (*)(git_repository*)>;
using LibGitRemote = std::unique_ptr<git_remote, void (*)(git_remote*)>;
using LibGitCommit = std::unique_ptr<git_commit, void (*)(git_commit*)>;
using LibGitStatusList = std::unique_ptr<git_status_list, void (*)(git_status_list*)>;
using LibGitReference = std::unique_ptr<git_reference, void (*)(git_reference*)>;
using LibGitBuf = std::unique_ptr<git_buf, void (*)(git_buf*)>;
using LibGitBranchIterator
= std::unique_ptr<git_branch_iterator, void (*)(git_branch_iterator*)>;

that all can outlive the libgit2 shutdown.

The library itself suggests: Usually you don’t need to call the shutdown function

Screenshot 2026-10-15 at 15 15 56

https://libgit2.org/docs/guides/101-samples/

For example one of our wrapper functions:

LibGitStatusList status_list_new(
    git_repository* repo, const git_status_options& status_opt);

We need a git_repository pointer to construct the status but there is no guarantee that not all Repository and Remote objects are long dead when the LibGitStatusList is finally destroyed and git_status_list_free() is called.

So, I would strive for a more general solution

We could create 'new' deallocator functions, that first call the libgit object-specific free and then shutdown, i.e. decrement the refcounter (and of course all objects need to call libgit2's init to increment the counter first.

Which is a hassle, but Imho much more straightforward then this LibGitInitGuard that is a rather complicated approach and does not even work in the spot that is the root cause of (i.e. the libgit deallocator functions).

Or we can go by the suggestion of libgit's maintainers and just init() the library whereever needed and never shutdown (which might look a bit 'dirty', but otoh thats also the case with the standard file descriptors and streams etc...

The merged solution is ... just an 'LLM fix', and not a good one at that 😒


Edit: For the libgit4cpp user who wants "correct" memory management, we could always just add a function

void git::LibGitShutdown() {
    while (git_libgit2_shutdown() > 0);
}

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.

Memory leak under Ubuntu 26.04

3 participants