Thanks to visit codestin.com
Credit goes to github.com

Skip to content

global: synchronize initialization and shutdown with pthreads #3974

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 4, 2016

Conversation

pks-t
Copy link
Member

@pks-t pks-t commented Oct 27, 2016

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.

@pks-t
Copy link
Member Author

pks-t commented Oct 27, 2016

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;
}

@pks-t
Copy link
Member Author

pks-t commented Oct 27, 2016

This in fact supersedes #3972 and should fix #3969

@ethomson
Copy link
Member

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?

@pks-t
Copy link
Member Author

pks-t commented Oct 27, 2016

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;.

@pks-t
Copy link
Member Author

pks-t commented Oct 27, 2016

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.

@ethomson
Copy link
Member

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;.

🙇 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.
Thread B enters git_libgit2_init and increments git__n_inits from 0 -> 1. pthread_once will not run in this case.
Thread A is woken back up, locks the mutex and finally performs the shutdown.

@pks-t
Copy link
Member Author

pks-t commented Oct 28, 2016

Oh, yeah, you're right. The mutex also has to synchronize pthread_once(once_init), not only the function called by pthread_once. Thanks :)

@pks-t pks-t force-pushed the pks/synchronize-shutdown branch from 52e76ed to 89be214 Compare October 28, 2016 08:29
@pks-t
Copy link
Member Author

pks-t commented Oct 28, 2016

The issue should be fixed. I synchronize over pthread_once(&_once_init, init_once) now instead of synchronizing inside of init_once.

@pks-t
Copy link
Member Author

pks-t commented Oct 28, 2016

The failing test is unrelated

@pks-t
Copy link
Member Author

pks-t commented Nov 1, 2016

I accidentally included the example demonstrating the fix. Removed the file

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.
@pks-t pks-t force-pushed the pks/synchronize-shutdown branch 2 times, most recently from 2732448 to 2100d8d Compare November 1, 2016 13:59
pks-t added 2 commits November 2, 2016 08:53
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`.
@pks-t pks-t force-pushed the pks/synchronize-shutdown branch from 2100d8d to 1c33ecc Compare November 2, 2016 07:54
@pks-t
Copy link
Member Author

pks-t commented Nov 2, 2016

Fixed another issue with re-initialization when not using threads which were caught by a set of new tests

@pks-t pks-t merged commit 5fe5557 into master Nov 4, 2016
@ethomson ethomson deleted the pks/synchronize-shutdown branch January 9, 2019 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants