public inbox for cygwin@cygwin.com
 help / color / mirror / Atom feed
* random is not multithread-safe in Cygwin
@ 2023-11-10 16:19 Bruno Haible
  2023-11-13 16:12 ` Corinna Vinschen
  0 siblings, 1 reply; 5+ messages in thread
From: Bruno Haible @ 2023-11-10 16:19 UTC (permalink / raw)
  To: cygwin

[-- Attachment #1: Type: text/plain, Size: 856 bytes --]

The function 'random' is, unlike 'rand', not marked as not MT-safe in POSIX
[1][2]. Thus it must be multithread-safe [3]:
  "Each function defined in the System Interfaces volume of POSIX.1-2017
   is thread-safe unless explicitly stated otherwise."

And indeed glibc, musl libc, AIX, Android, and even NetBSD implement it in a
multithread-safe way.

On Cygwin 2.9.0 and 3.4.6, it is not multithread-safe.

How to reproduce:
1. Compile the attached program.
   $ x86_64-pc-cygwin-gcc foo.c
2. Run it.
   $ ./a.exe
Expected: No output.
Actual: Output such as
  Expected value #367 not found in multithreaded results.

[1] https://pubs.opengroup.org/onlinepubs/9699919799/functions/initstate.html
[2] https://pubs.opengroup.org/onlinepubs/9699919799/functions/rand.html
[3] https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_407

[-- Attachment #2: foo.c --]
[-- Type: text/x-csrc, Size: 4391 bytes --]

/* Multithread-safety test for random().
   Copyright (C) 2023 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

/* Written by Bruno Haible <bruno@clisp.org>, 2023.  */

/* Whether to help the scheduler through explicit yield().
   Uncomment this to see if the operating system has a fair scheduler.  */
#define EXPLICIT_YIELD 1

/* Number of simultaneous threads.  */
#define THREAD_COUNT 4

/* Number of random() invocations operations performed in each thread.
   This value is chosen so that the unit test terminates quickly.
   To reliably determine whether a random() implementation is multithread-safe,
   set REPEAT_COUNT to 1000000 and run the test 100 times:
     $ for i in `seq 100`; do ./test-random-mt; done
 */
#define REPEAT_COUNT 1000000

/* Specification.  */
#include <stdlib.h>

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

#if EXPLICIT_YIELD
# include <sched.h>
# define yield() sched_yield ()
#else
# define yield()
#endif

/* This test runs REPEAT_COUNT invocations of random() in each thread and stores
   the result, then compares the first REPEAT_COUNT among these
     THREAD_COUNT * REPEAT_COUNT
   random numbers against a precomputed sequence with the same seed.  */

static void *
random_invocator_thread (void *arg)
{
  long *storage = (long *) arg;
  int repeat;

  for (repeat = 0; repeat < REPEAT_COUNT; repeat++)
    {
      storage[repeat] = random ();
      yield ();
    }

  return NULL;
}

int
main ()
{
  unsigned int seed = 19891109;

  /* First, get the expected sequence of random() results.  */
  srandom (seed);
  long *expected = (long *) malloc (REPEAT_COUNT * sizeof (long));
  assert (expected != NULL);
  {
    int repeat;
    for (repeat = 0; repeat < REPEAT_COUNT; repeat++)
      expected[repeat] = random ();
  }

  /* Then, run REPEAT_COUNT invocations of random() each, in THREAD_COUNT
     separate threads.  */
  pthread_t threads[THREAD_COUNT];
  long *thread_results[THREAD_COUNT];
  srandom (seed);
  {
    int i;
    for (i = 0; i < THREAD_COUNT; i++)
      {
        thread_results[i] = (long *) malloc (REPEAT_COUNT * sizeof (long));
        assert (thread_results[i] != NULL);
      }
    for (i = 0; i < THREAD_COUNT; i++)
      assert (pthread_create (&threads[i], NULL, random_invocator_thread, thread_results[i]) == 0);
  }

  /* Wait for the threads to terminate.  */
  {
    int i;
    for (i = 0; i < THREAD_COUNT; i++)
      assert (pthread_join (threads[i], NULL) == 0);
  }

  /* Finally, determine whether the threads produced the same sequence of
     random() results.  */
  {
    int expected_index;
    int result_index[THREAD_COUNT];
    int i;

    for (i = 0; i < THREAD_COUNT; i++)
      result_index[i] = 0;

    for (expected_index = 0; expected_index < REPEAT_COUNT; expected_index++)
      {
        long expected_value = expected[expected_index];

        for (i = 0; i < THREAD_COUNT; i++)
          {
            if (thread_results[i][result_index[i]] == expected_value)
              {
                result_index[i]++;
                break;
              }
          }
        if (i == THREAD_COUNT)
          {
            if (expected_index == 0)
              {
                /* This occurs on platforms like OpenBSD, where srandom() has no
                   effect and random() always return non-deterministic values.
                   Mark the test as SKIP.  */
                fprintf (stderr, "Skipping test: random() is non-deterministic.\n");
                return 77;
              }
            else
              {
                fprintf (stderr, "Expected value #%d not found in multithreaded results.\n",
                         expected_index);
                return 1;
              }
          }
      }
  }

  return 0;
}

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2023-11-13 19:07 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-11-10 16:19 random is not multithread-safe in Cygwin Bruno Haible
2023-11-13 16:12 ` Corinna Vinschen
2023-11-13 17:34   ` Brian Inglis
2023-11-13 18:04   ` Bruno Haible
2023-11-13 19:07     ` Corinna Vinschen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).