#7356·libgit2

`git_remote_disconnect` leaves open file descriptors behind

Author: civodulCreated Aug 24, 2026Updated Aug 24, 2026

As of 1.9.7, the program below shows that, after git_remote_disconnect, an open file descriptor (socket connected to the Git server) is left behind:

c
#include <git2.h>
#include <assert.h>
#include <stdio.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <sys/socket.h>
#include <assert.h>

#define REPO_URL "https://codeberg.org/shepherd/shepherd.git"
GIT_EXTERN(int) git_repository__cleanup(git_repository *repo);

static void
show_open_file_descriptors ()
{
  DIR *dir;
  struct dirent *ent;

  dir = opendir ("/proc/self/fd");
  chdir ("/proc/self/fd");
  for (ent = NULL; ent = readdir (dir), ent != NULL; )
    {
      if (strcmp (ent->d_name, ".") == 0 || strcmp (ent->d_name, "..") == 0)
        continue;

      char target[1024];
      ssize_t size;
      size = readlink (ent->d_name, target, sizeof target);
      target[size < 0 ? 0 : size] = '\0';
      printf ("%s -> %s\n", ent->d_name, target);
    }
  closedir (dir);
}

int
main ()
{
  int err;
  err = git_libgit2_init ();
  assert (err == 1);

  git_clone_options opts;
  err = git_clone_init_options (&opts, GIT_CLONE_OPTIONS_VERSION);
  assert (err == 0);

  git_repository *repo;
  err = git_clone (&repo, REPO_URL, "/tmp/example", &opts);
  assert (err == 0);
  git_repository_free (repo);

  show_open_file_descriptors ();

  err = git_repository_open (&repo, "/tmp/example");
  assert (err == 0);

  git_remote *remote;
  err = git_remote_lookup (&remote, repo, "origin");
  assert (err == 0);

  err = git_remote_fetch (remote, NULL, NULL, NULL);
  assert (err == 0);

  err = git_remote_disconnect (remote);
  assert (err == 0);

  /* git_remote_free (remote); */
  /* assert (err == 0); */

  /* git_repository__cleanup (repo); */
  git_repository_free (repo);
  printf ("%s:%i\n", __FILE__, __LINE__);
  show_open_file_descriptors ();

  return 0;
}

Uncomment the git_remote_free call and the socket gets closed.

My suggestion is to change git_remote_disconnect or rather its backend so that no file descriptor is left open once it's been called; the patch below does exactly that:

diff
diff --git a/src/libgit2/transports/http.c b/src/libgit2/transports/http.c
index f344888d0..15fe8dfb6 100644
--- a/src/libgit2/transports/http.c
+++ b/src/libgit2/transports/http.c
@@ -729,6 +729,10 @@ static int http_close(git_smart_subtransport *t)
 	git_net_url_dispose(&transport->server.url);
 	git_net_url_dispose(&transport->proxy.url);
 
+	/* The call below closes associated file descriptors.  */
+	git_http_client_free(transport->http_client);
+	transport->http_client = NULL;
+
 	return 0;
 }
 
@@ -736,8 +740,6 @@ static void http_free(git_smart_subtransport *t)
 {
 	http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent);
 
-	git_http_client_free(transport->http_client);
-
 	http_close(t);
 	git__free(transport);
 }

WDYT? I can make it a pull request if that sounds like the right approach.

(This was originally reported here.)