curl: explicitly initialize and cleanup global curl state Our curl-based streams make use of the easy curl interface. This interface automatically initializes and de-initializes the global curl state by calling out to `curl_global_init` and `curl_global_cleanup`. Thus, all global state will be repeatedly re-initialized when creating multiple curl streams in succession. Despite being inefficient, this is not thread-safe due to `curl_global_init` being not thread-safe itself. Thus a multi-threaded programing handling multiple curl streams at the same time is inherently racy. Fix the issue by globally initializing and cleaning up curl's state.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
diff --git a/src/curl_stream.c b/src/curl_stream.c
index ac0f95a..f07370f 100644
--- a/src/curl_stream.c
+++ b/src/curl_stream.c
@@ -12,6 +12,7 @@
#include "stream.h"
#include "git2/transport.h"
#include "buffer.h"
+#include "global.h"
#include "vector.h"
#include "proxy.h"
@@ -36,6 +37,18 @@ typedef struct {
git_cred *proxy_cred;
} curl_stream;
+int git_curl_stream_global_init(void)
+{
+ if (curl_global_init(CURL_GLOBAL_ALL) != 0) {
+ giterr_set(GITERR_NET, "could not initialize curl");
+ return -1;
+ }
+
+ /* `curl_global_cleanup` is provided by libcurl */
+ git__on_shutdown(curl_global_cleanup);
+ return 0;
+}
+
static int seterr_curl(curl_stream *s)
{
giterr_set(GITERR_NET, "curl error: %s\n", s->curl_error);
@@ -351,6 +364,11 @@ int git_curl_stream_new(git_stream **out, const char *host, const char *port)
#include "stream.h"
+int git_curl_stream_global_init(void)
+{
+ return 0;
+}
+
int git_curl_stream_new(git_stream **out, const char *host, const char *port)
{
GIT_UNUSED(out);
diff --git a/src/curl_stream.h b/src/curl_stream.h
index 283f0fe..6f192c5 100644
--- a/src/curl_stream.h
+++ b/src/curl_stream.h
@@ -9,6 +9,7 @@
#include "git2/sys/stream.h"
+extern int git_curl_stream_global_init(void);
extern int git_curl_stream_new(git_stream **out, const char *host, const char *port);
#endif
diff --git a/src/global.c b/src/global.c
index afa57e1..0b347a3 100644
--- a/src/global.c
+++ b/src/global.c
@@ -10,6 +10,7 @@
#include "sysdir.h"
#include "filter.h"
#include "merge_driver.h"
+#include "curl_stream.h"
#include "openssl_stream.h"
#include "thread-utils.h"
#include "git2/global.h"
@@ -22,7 +23,7 @@
git_mutex git__mwindow_mutex;
-#define MAX_SHUTDOWN_CB 9
+#define MAX_SHUTDOWN_CB 10
static git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB];
static git_atomic git__n_shutdown_callbacks;
@@ -62,7 +63,8 @@ static int init_common(void)
(ret = git_filter_global_init()) == 0 &&
(ret = git_merge_driver_global_init()) == 0 &&
(ret = git_transport_ssh_global_init()) == 0 &&
- (ret = git_openssl_stream_global_init()) == 0)
+ (ret = git_openssl_stream_global_init()) == 0 &&
+ (ret = git_curl_stream_global_init()) == 0)
ret = git_mwindow_global_init();
GIT_MEMORY_BARRIER;