Merge pull request #2676 from libgit2/cmn/threading Threading and crypto libraries
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
diff --git a/README.md b/README.md
index 98aaa1d..f25bb12 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,11 @@ dependencies, it can make use of a few libraries to add to it:
- LibSSH2 to enable the SSH transport
- iconv (OSX) to handle the HFS+ path encoding peculiarities
+Threading
+=========
+
+See [THREADING](THREADING.md) for information
+
Building libgit2 - Using CMake
==============================
diff --git a/THREADING.md b/THREADING.md
new file mode 100644
index 0000000..4759318
--- /dev/null
+++ b/THREADING.md
@@ -0,0 +1,79 @@
+Threads in libgit2
+==================
+
+You may safely use any libgit2 object from any thread, though there
+may be issues depending on the cryptographic libraries libgit2 or its
+dependencies link to (more on this later). For libgit2 itself,
+provided you take the following into consideration you won't run into
+issues:
+
+Sharing objects
+---------------
+
+Use an object from a single thread at a time. Most data structures do
+not guard against concurrent access themselves. This is because they
+are rarely used in isolation and it makes more sense to synchronize
+access via a larger lock or similar mechanism.
+
+There are some objects which are read-only/immutable and are thus safe
+to share across threads, such as references and configuration
+snapshots.
+
+Error messages
+--------------
+
+The error message is thread-local. The `giterr_last()` call must
+happen on the same thread as the error in order to get the
+message. Often this will be the case regardless, but if you use
+something like the GDC on MacOS (where code is executed on an
+arbitrary thread), the code must make sure to retrieve the error code
+on the thread where the error happened.
+
+Threads and cryptographic libraries
+=======================================
+
+On Windows
+----------
+
+When built as a native Windows DLL, libgit2 uses WinCNG and WinHTTP,
+both of which are thread-safe. You do not need to do anything special.
+
+When using libssh2 which itself uses WinCNG, there are no special
+steps necessary. If you are using a MinGW or similar environment where
+libssh2 uses OpenSSL or libgcrypt, then the non-Windows case affects
+you.
+
+Non-Windows
+-----------
+
+On the rest of the platforms, libgit2 uses OpenSSL to be able to use
+HTTPS as a transport. This library is made to be thread-implementation
+agnostic, and the users of the library must set which locking function
+it should use. This means that libgit2 cannot know what to set as the
+user of libgit2 may use OpenSSL independently and the locking settings
+must survive libgit2 shutting down.
+
+libgit2 does provide a convenience function
+`git_openssl_set_locking()` to use the platform-native mutex
+mechanisms to perform the locking, which you may rely on if you do not
+want to use OpenSSL outside of libgit2, or you know that libgit2 will
+outlive the rest of the operations. It is not safe to use OpenSSL
+multi-threaded after libgit2's shutdown function has been called.
+
+See the
+[OpenSSL documentation](https://www.openssl.org/docs/crypto/threads.html)
+on threading for more details.
+
+libssh2 may be linked against OpenSSL or libgcrypt. If it uses
+OpenSSL, you only need to set up threading for OpenSSL once and the
+above paragraphs are enough. If it uses libgcrypt, then you need to
+set up its locking before using it multi-threaded. libgit2 has no
+direct connection to libgcrypt and thus has not convenience functions for
+it (but libgcrypt has macros). Read libgcrypt's
+[threading documentation for more information](http://www.gnupg.org/documentation/manuals/gcrypt/Multi_002dThreading.html)
+
+It is your responsibility as an application author or packager to know
+what your dependencies are linked against and to take the appropriate
+steps to ensure the cryptographic libraries are thread-safe. We agree
+that this situation is far from ideal but at this time it is something
+the application authors need to deal with.
diff --git a/include/git2/threads.h b/include/git2/threads.h
index 11f8972..6b42870 100644
--- a/include/git2/threads.h
+++ b/include/git2/threads.h
@@ -44,6 +44,22 @@ GIT_EXTERN(int) git_threads_init(void);
*/
GIT_EXTERN(void) git_threads_shutdown(void);
+/**
+ * Initialize the OpenSSL locks
+ *
+ * OpenSSL requires the application to determine how it performs
+ * locking. This is a convenience function which libgit2 provides for
+ * allocating and initializing the locks as well as setting the
+ * locking function to use the system's native locking functions.
+ *
+ * The locking function will be cleared and the memory will be freed
+ * when you call git_threads_sutdown().
+ *
+ * @return 0 on success, -1 if there are errors or if libgit2 was not
+ * built with OpenSSL and threading support.
+ */
+GIT_EXTERN(int) git_openssl_set_locking(void);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/src/global.c b/src/global.c
index 55b3119..3c91860 100644
--- a/src/global.c
+++ b/src/global.c
@@ -64,8 +64,9 @@ void openssl_locking_function(int mode, int n, const char *file, int line)
}
}
-static void shutdown_ssl(void)
+static void shutdown_ssl_locking(void)
{
+ CRYPTO_set_locking_callback(NULL);
git__free(openssl_locks);
}
#endif
@@ -96,30 +97,35 @@ static void init_ssl(void)
SSL_CTX_free(git__ssl_ctx);
git__ssl_ctx = NULL;
}
+#endif
+}
+int git_openssl_set_locking(void)
+{
+#ifdef GIT_SSL
# ifdef GIT_THREADS
- {
- int num_locks, i;
-
- num_locks = CRYPTO_num_locks();
- openssl_locks = git__calloc(num_locks, sizeof(git_mutex));
- if (openssl_locks == NULL) {
- SSL_CTX_free(git__ssl_ctx);
- git__ssl_ctx = NULL;
- }
+ int num_locks, i;
- for (i = 0; i < num_locks; i++) {
- if (git_mutex_init(&openssl_locks[i]) != 0) {
- SSL_CTX_free(git__ssl_ctx);
- git__ssl_ctx = NULL;
- }
- }
+ num_locks = CRYPTO_num_locks();
+ openssl_locks = git__calloc(num_locks, sizeof(git_mutex));
+ GITERR_CHECK_ALLOC(openssl_locks);
- CRYPTO_set_locking_callback(openssl_locking_function);
+ for (i = 0; i < num_locks; i++) {
+ if (git_mutex_init(&openssl_locks[i]) != 0) {
+ giterr_set(GITERR_SSL, "failed to initialize openssl locks");
+ return -1;
+ }
}
- git__on_shutdown(shutdown_ssl);
+ CRYPTO_set_locking_callback(openssl_locking_function);
+ git__on_shutdown(shutdown_ssl_locking);
+ return 0;
+# else
+ giterr_set(GITERR_THREAD, "libgit2 as not built with threads");
+ return -1;
# endif
+ giterr_set(GITERR_SSL, "libgit2 was not built with OpenSSL support");
+ return -1;
#endif
}