| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
|
I tried to come up with a test, but it wasn't easily possible as our testing frameworks requires global state as well. I used the following test program to verify the issue vanished: #include <pthread.h>
#include "common.h"
void *run(void *data)
{
int i;
for (i = 0; i < 20; i++)
{
git_libgit2_init();
git_libgit2_shutdown();
}
return data;
}
int main(int argc, char *argv[])
{
pthread_t threads[10];
unsigned i;
for (i = 0; i < sizeof(threads) / sizeof(*threads); i++)
{
pthread_create(&threads[i], NULL, run, NULL);
}
for (i = 0; i < sizeof(threads) / sizeof(*threads); i++)
{
pthread_join(threads[i], NULL);
}
return 0;
} |
Sorry, something went wrong.
Sorry, something went wrong.
|
So I may not be reasoning about this correctly, but I don't think this is quite sufficient. pthread_once should ensure that the init_once function is only called once, and synchronously (with respect to other callers with the same once token). In your sample program, you're initing and shutdowning. But if you ever have a shutdown that actually tears down the global state, you'll never bring it back. That once_init should really only ever get called once. I suspect that - independent of threading - if you just git_libgit2_init, git_libgit2_shutdown, git_libgit2_init that you are not actually initialized and the global state is a mess... Or am I not thinking about this correctly? |
Sorry, something went wrong.
|
No, we do actually re-initialize the pthread_once thingy. See git_libgit2_shutdown, where we reset _once_init with pthread_once_t new_once = PTHREAD_ONCE_INIT; _once_init = new_once;. |
Sorry, something went wrong.
|
Just to write down my thoughts and why it is correct to reset _once_init: when the last shutdown has been called, we actually do want to free all related data structures such as to avoid memory leaks in libgit2. So we are forced to reset _once_init, because when somebody calls init again we have to re-initialize all data structures. Usually I'd advice callers to not initialize/shutdown repeatedly, but I think we should nevertheless support this use case. |
Sorry, something went wrong.
🙇 Thank you, I didn't see that. 😪😫😪 So I think that there's still a situation here. Since we increment and decrement outside of the mutex, we could have two threads racing: Thread A enters git_libgit2_shutdown and decrements git__n_inits from 1 -> 0 and is paused before locking the mutex and performing the shutdown. |
Sorry, something went wrong.
|
Oh, yeah, you're right. The mutex also has to synchronize pthread_once(once_init), not only the function called by pthread_once. Thanks :) |
Sorry, something went wrong.
|
The issue should be fixed. I synchronize over pthread_once(&_once_init, init_once) now instead of synchronizing inside of init_once. |
Sorry, something went wrong.
|
The failing test is unrelated |
Sorry, something went wrong.
|
I accidentally included the example demonstrating the fix. Removed the file |
Sorry, something went wrong.
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.
When threading is not enabled for libgit2, we keep global state in a simple static variable. When libgit2 is shut down, we clean up the global state by freeing the global state's dynamically allocated memory. When libgit2 is built with threading, we additionally free the thread-local storage and thus completely remove the global state. In a non-threaded build, though, we simply leave the global state as-is, which may result in an error upon reinitializing libgit2. Fix the issue by zeroing out the variable on a shutdown, thus returning it to its initial state.
Exercise the logic surrounding deinitialization of the libgit2 library as well as repeated concurrent de- and reinitialization. This tries to catch races and makes sure that it is possible to reinitialize libgit2 multiple times. After deinitializing libgit2, we have to make sure to setup options required for testing. Currently, this only includes setting up the configuration search path again. Before, this has been set up once in `tests/main.c`.
|
Fixed another issue with re-initialization when not using threads which were caught by a set of new tests |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
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.