Commit 59c6c2860a573521a96a402f8232127ceb27b0f6

Patrick Steinhardt 2016-10-27T12:31:17

global: synchronize initialization and shutdown with pthreads When trying to initialize and tear down global data structures from different threads at once with `git_libgit2_init` and `git_libgit2_shutdown`, we race around initializing data. While we use `pthread_once` to assert that we only initilize data a single time, we actually reset the `pthread_once_t` on the last call to `git_libgit2_shutdown`. As resetting this variable is not synchronized with other threads trying to access it, this is actually racy when one thread tries to do a complete shutdown of libgit2 while another thread tries to initialize it. Fix the issue by creating a mutex which synchronizes `init_once` and the library shutdown.

diff --git a/src/global.c b/src/global.c
index 45b1ab8..de722c7 100644
--- a/src/global.c
+++ b/src/global.c
@@ -247,6 +247,7 @@ BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpvReserved)
 #elif defined(GIT_THREADS) && defined(_POSIX_THREADS)
 
 static pthread_key_t _tls_key;
+static pthread_mutex_t _init_mutex = PTHREAD_MUTEX_INITIALIZER;
 static pthread_once_t _once_init = PTHREAD_ONCE_INIT;
 int init_error = 0;
 
@@ -268,12 +269,19 @@ static void init_once(void)
 
 int git_libgit2_init(void)
 {
-	int ret;
+	int ret, err;
 
 	ret = git_atomic_inc(&git__n_inits);
-	pthread_once(&_once_init, init_once);
 
-	return init_error ? init_error : ret;
+	if ((err = pthread_mutex_lock(&_init_mutex)) != 0)
+		return err;
+	err = pthread_once(&_once_init, init_once);
+	err |= pthread_mutex_unlock(&_init_mutex);
+
+	if (err || init_error)
+		return err | init_error;
+
+	return ret;
 }
 
 int git_libgit2_shutdown(void)
@@ -285,6 +293,9 @@ int git_libgit2_shutdown(void)
 	if ((ret = git_atomic_dec(&git__n_inits)) != 0)
 		return ret;
 
+	if ((ret = pthread_mutex_lock(&_init_mutex)) != 0)
+		return ret;
+
 	/* Shut down any subsystems that have global state */
 	shutdown_common();
 
@@ -298,6 +309,9 @@ int git_libgit2_shutdown(void)
 	git_mutex_free(&git__mwindow_mutex);
 	_once_init = new_once;
 
+	if ((ret = pthread_mutex_unlock(&_init_mutex)) != 0)
+		return ret;
+
 	return 0;
 }