diff --git a/README.md b/README.md index 98aaa1dd7..f25bb12bb 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 000000000..475931885 --- /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 11f89729a..6b4287033 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 55b31196e..3c91860cd 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; + 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; + num_locks = CRYPTO_num_locks(); + openssl_locks = git__calloc(num_locks, sizeof(git_mutex)); + GITERR_CHECK_ALLOC(openssl_locks); + + 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; } - - 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; - } - } - - CRYPTO_set_locking_callback(openssl_locking_function); } - 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 }