Using 32-bit cygwin that I set up yesterday.    I found that a call to
clock_gettime(CLOCK_MONOTONIC, ..) has a one-time initialization that
is not thread-safe.  If two threads call this at the same time, they
will race.  The results I am seeing are typically that one of the two
callers get a timespec structure with zero values, and no error return
code from the call.

I prepared a small test that exposes this issue.  Given it is a race
condition, you have to run the test in a loop for it to happen.  I
have attached the Makefile and source code.  If you run "make test" it
will expose the issue:

$ make test
cc    -c -o cyg_hires_clock_race.o cyg_hires_clock_race.c
cc   cyg_hires_clock_race.o   -o cyg_hires_clock_race
ERROR: one of the timespec structures was zero:
main thread: tv_sec =     356242 tv_nsec =  376075900
 2nd thread: tv_sec =          0 tv_nsec =          0
ERROR: one of the timespec structures was zero:
main thread: tv_sec =     356242 tv_nsec =  519016800
 2nd thread: tv_sec =          0 tv_nsec =          0
ERROR: one of the timespec structures was zero:
main thread: tv_sec =     356242 tv_nsec =  734794100
 2nd thread: tv_sec =          0 tv_nsec =          0
ERROR: one of the timespec structures was zero:
main thread: tv_sec =     356243 tv_nsec =  463632400
 2nd thread: tv_sec =          0 tv_nsec =          0
make: *** [Makefile:6: test] Error 1

Relevant cygwin version information:

Cygwin DLL version info:
        DLL version: 2.11.2
        DLL epoch: 19
        DLL old termios: 5
        DLL malloc env: 28
        Cygwin conv: 181
        API major: 0
        API minor: 329
        Shared data: 5
        DLL identifier: cygwin1
        Mount registry: 3
        Cygwin registry name: Cygwin
        Installations name: Installations
        Cygdrive default prefix:
        Build date:
        Shared id: cygwin1S5
/*
 * Copyright (C) 2018 James E. King III
 *
 * Exposes a race condition in hires clock initialization
 * If two threads call ::clock_gettime(CLOCK_MONOTONIC) they
 * will race to perform one-time global initialization.
 * During this race it is possible to see the timespec filled
 * in by ::clock_gettime can have tv_sec == 0 && tv_nsec == 0
 * 
 * As this is a race you have to run the test in a loop to catch it.
 */

#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <time.h>

void *fill_ts(void *vts)
{
    struct timespec *pts = (struct timespec *)vts;
    assert(!clock_gettime(CLOCK_MONOTONIC, pts));
    return NULL;
}

int main()
{
    struct timespec main_ts;
    struct timespec thread_ts;

    pthread_t thr;
    assert(!pthread_create(&thr, NULL, &fill_ts, &thread_ts));
    (void)fill_ts(&main_ts);
    assert(!pthread_join(thr, NULL));

    int failed =
        ((  main_ts.tv_sec == 0 &&   main_ts.tv_nsec == 0) ||
         (thread_ts.tv_sec == 0 && thread_ts.tv_nsec == 0));

    if (failed)
    {
        fprintf(stderr, "ERROR: one of the timespec structures was zero:\n");
        fprintf(stderr, "main thread: tv_sec = %10u tv_nsec = %10u\n",   
main_ts.tv_sec,   main_ts.tv_nsec);
        fprintf(stderr, " 2nd thread: tv_sec = %10u tv_nsec = %10u\n", 
thread_ts.tv_sec, thread_ts.tv_nsec);
        return 1;
    }

    return 0;
}

Attachment: Makefile
Description: Binary data

--
Problem reports:       http://cygwin.com/problems.html
FAQ:                   http://cygwin.com/faq/
Documentation:         http://cygwin.com/docs.html
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple

Reply via email to