git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH v2 2/2] hex: make hash_to_hex_algop() and friends thread-safe

From
Matheus Tavares Bernardino <matheus.bernardino@usp.br>
Date
Jun 30, 2020, 20:37 UTC
Message-ID
<CAHd-oW6U9krWYd++5ipGA6e4Xy6ZoBxE+QVFpshrjUKjN+RH0g@mail.gmail.com>
In-Reply-To
<nycvar.QRO.7.76.6.2006291646420.56@tvgsbejvaqbjf.bet>

On Tue, Jun 30, 2020 at 12:01 PM Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:

Show 15 quoted lines
>
> Hi Matheus,
>
> I am fine with the Windows changes (although I have to admit that I did
> not find time to test things yet).
>
> There is one problem in that I do not necessarily know that the memory is
> released correctly when threads end; You will notice that the
> `pthread_key_create()` shim in `compat/win32/pthread.h` does not use the
> `destructor` parameter at all. The documentation at
>
>       https://docs.microsoft.com/en-us/windows/win32/procthread/using-thread-local-storage
>
> is also not terribly clear _how_ the memory is released that was assigned
> via `TlsSetValue()`.

Yes, I wasn't sure about that either... It would be great if someone familiar with TLS memory on Windows could help us with this.

> A couple more things:
>
> On Fri, 26 Jun 2020, Matheus Tavares wrote:
> >
[...]
Show 5 quoted lines
> > +struct hexbuf_array {
> > +     int idx;
>
> Is there a specific reason why you renamed `bufno` to `idx`? If not, I'd
> rather keep the old name.
OK, I'll change it back in v3.
Show 62 quoted lines
> > +     char bufs[4][GIT_MAX_HEXSZ + 1];
> > +};
> > +
> > +#ifdef HAVE_THREADS
> > +static pthread_key_t hexbuf_array_key;
> > +static pthread_once_t hexbuf_array_once = PTHREAD_ONCE_INIT;
> > +
> > +static void init_hexbuf_array_key(void)
> > +{
> > +     if (pthread_key_create(&hexbuf_array_key, free))
> > +             die(_("failed to initialize threads' key for hash to hex conversion"));
> > +}
> > +
> > +#else
> > +static struct hexbuf_array default_hexbuf_array;
> > +#endif
> > +
> >  char *hash_to_hex_algop(const unsigned char *hash, const struct git_hash_algo *algop)
> >  {
> > -     static int bufno;
> > -     static char hexbuffer[4][GIT_MAX_HEXSZ + 1];
> > -     bufno = (bufno + 1) % ARRAY_SIZE(hexbuffer);
> > -     return hash_to_hex_algop_r(hexbuffer[bufno], hash, algop);
> > +     struct hexbuf_array *ha;
> > +
> > +#ifdef HAVE_THREADS
> > +     void *value;
> > +
> > +     if (pthread_once(&hexbuf_array_once, init_hexbuf_array_key))
> > +             die(_("failed to initialize threads' key for hash to hex conversion"));
> > +
> > +     value = pthread_getspecific(hexbuf_array_key);
> > +     if (value) {
> > +             ha = (struct hexbuf_array *) value;
> > +     } else {
> > +             ha = xmalloc(sizeof(*ha));
> > +             if (pthread_setspecific(hexbuf_array_key, (void *)ha))
> > +                     die(_("failed to set thread buffer for hash to hex conversion"));
> > +     }
> > +#else
> > +     ha = &default_hexbuf_array;
> > +#endif
>
> This introduces two ugly `#ifdef HAVE_THREADS` constructs which are
> problematic because they are the most likely places to introduce compile
> errors.
>
> I wonder whether you considered introducing a function (and probably a
> macro) that transparently gives you a thread-specific instance of a given
> data type? The caller would look something like
>
>         struct hexbuf_array *hex_array;
>
>         GET_THREADSPECIFIC(hex_array);
>
> where the macro would look somewhat like this:
>
>         #define GET_THREADSPECIFIC(var) \
>                 if (get_thread_specific(&var, sizeof(var)) < 0)
>                         die(_("Failed to get thread-specific %s"), #var);
>
> and the function would allocate and assign the variable.

Hmm, we would need two macros, wouldn't we? GET_THREADSPECIFIC(var) and a DECLARE_THREADSPECIFIC(var, destructor), to declare the pthread_once_t and pthread_key_t variables, as well as define a initialization function for the key (i.e. the callback to pthread_once()). Or we could provide these declarations ourselves, but then we would need the "ifdef HAVE_THREADS" guard to avoid calling pthread_key_create() when there is no multithreading.

I think that would work, yes. Alternatively, I think we could adjust the dummy pthread_key_* functions in thread-utils.h to emulate the real ones when HAVE_THREADS == false. Then we could eliminate the `#ifdef HAVE_THREADS` guards and use the same code for both cases here (and everywhere else pthread_key is used). I haven't thought about it carefully enough yet, but I think this *might* be as simple as adding the following defines in the "#ifdef NO_PTHREADS" section of thread-utils.h:

#define pthread_key_t void *
/*
 * The destructor is not used in this case as the main thread will only
 * exit when the program terminates.
 */
#define pthread_key_create(key_ptr, unused) return_0((*key_ptr) = NULL)
#define pthread_setspecific(key, value) return_0((key) = (value))
#define pthread_getspecific(key) (key)
#define pthread_key_delete(key) return_0(NULL)
static inline int return_0(void *unused)
{
        return 0;
}

That's the general idea, but we might as well define a `struct dummy_pthread_key_t` instead of defining the key directly as `void *` (and have functions instead of macros). This way we could store, e.g., an "initialized" flag that could be used to return an error code on double-initializations.

What do you think?
Previous: Johannes SchindelinNext: Johannes Schindelin
Message 19 of 25 in “[RFC] Thread safety in some low-level functions”
  1. Matheus Tavares BernardinoJun 24, 2020
  2. Matheus Tavares BernardinoJun 24, 2020
  3. brian m. carlsonJun 25, 2020
  4. 0/2 Make oid_to_hex() thread-safeMatheus Tavares, Jun 25, 2020
  5. 1/2 compat/win32/pthread: add pthread_once()Matheus Tavares, Jun 25, 2020
  6. Christian CouderJun 26, 2020
  7. Matheus Tavares BernardinoJun 26, 2020
  8. 2/2 hex: make hash_to_hex_algop() and friends thread-safeMatheus Tavares, Jun 25, 2020
  9. brian m. carlsonJun 25, 2020
  10. Matheus Tavares BernardinoJun 25, 2020
  11. Matheus Tavares BernardinoJun 26, 2020
  12. Christian CouderJun 26, 2020
  13. Christian CouderJun 26, 2020
  14. Matheus Tavares BernardinoJun 26, 2020
  15. 0/2 Make oid_to_hex() thread-safeMatheus Tavares, Jun 26, 2020
  16. 1/2 compat/win32/pthread: add pthread_once()Matheus Tavares, Jun 26, 2020
  17. 2/2 hex: make hash_to_hex_algop() and friends thread-safeMatheus Tavares, Jun 26, 2020
  18. Johannes SchindelinJun 29, 2020
  19. Matheus Tavares BernardinoJun 30, 2020
  20. Johannes SchindelinJul 1, 2020
  21. Johannes SchindelinJul 16, 2020
  22. Matheus Tavares BernardinoJul 18, 2020
  23. Johannes SchindelinAug 10, 2020
  24. Matheus TavaresJul 18, 2020
  25. René ScharfeJul 26, 2020

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.