/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
Attached is a test program that uses the nspr1 to demonstrate a bug
under NT4.0. The fix has already been mentioned (add a ResetEvent just
before leaving the critical section in _PR_CondWait in hwmon.c).
*/

#include "prthread.h"
#include "prtypes.h"
#include "prinit.h"
#include "prmon.h"
#include "prlog.h"

typedef struct Arg_s {
  PRInt32 a, b;
} Arg_t;

PRMonitor* gMonitor;    // the monitor
PRInt32 gReading;       // number of read locks
PRInt32 gWriteWaiting;  // number of threads waiting for write lock
PRInt32 gReadWaiting;   // number of threads waiting for read lock

PRInt32 gCounter;  // a counter

// stats
PRInt32 gReads;          // number of successful reads
PRInt32 gMaxReads;       // max number of simultaneous reads
PRInt32 gMaxWriteWaits;  // max number of writes that waited for read
PRInt32 gMaxReadWaits;   // max number of reads that waited for write wait

void spin(PRInt32 aDelay) {
  PRInt32 index;
  PRInt32 delay = aDelay * 1000;

  PR_Sleep(0);

  // randomize delay a bit
  delay =
      (delay / 2) + (PRInt32)((float)delay * ((float)rand() / (float)RAND_MAX));

  for (index = 0; index < delay * 10; index++)
    // consume a bunch of cpu cycles
    ;
  PR_Sleep(0);
}

void doWriteThread(void* arg) {
  PRInt32 last;
  Arg_t* args = (Arg_t*)arg;
  PRInt32 aWorkDelay = args->a, aWaitDelay = args->b;
  PR_Sleep(0);

  while (1) {
    // -- enter write lock
    PR_EnterMonitor(gMonitor);

    if (0 < gReading)  // wait for read locks to go away
    {
      PRIntervalTime fiveSecs = PR_SecondsToInterval(5);

      gWriteWaiting++;
      if (gWriteWaiting > gMaxWriteWaits) {  // stats
        gMaxWriteWaits = gWriteWaiting;
      }
      while (0 < gReading) {
        PR_Wait(gMonitor, fiveSecs);
      }
      gWriteWaiting--;
    }
    // -- write lock entered

    last = gCounter;
    gCounter++;

    spin(aWorkDelay);

    PR_ASSERT(gCounter == (last + 1));  // test invariance

    // -- exit write lock
    //    if (0 < gReadWaiting)   // notify waiting reads (do it anyway to show
    //    off the CondWait bug)
    PR_NotifyAll(gMonitor);

    PR_ExitMonitor(gMonitor);
    // -- write lock exited

    spin(aWaitDelay);
  }
}

void doReadThread(void* arg) {
  PRInt32 last;
  Arg_t* args = (Arg_t*)arg;
  PRInt32 aWorkDelay = args->a, aWaitDelay = args->b;
  PR_Sleep(0);

  while (1) {
    // -- enter read lock
    PR_EnterMonitor(gMonitor);

    if (0 < gWriteWaiting)  // give up the monitor to waiting writes
    {
      PRIntervalTime fiveSecs = PR_SecondsToInterval(5);

      gReadWaiting++;
      if (gReadWaiting > gMaxReadWaits) {  // stats
        gMaxReadWaits = gReadWaiting;
      }
      while (0 < gWriteWaiting) {
        PR_Wait(gMonitor, fiveSecs);
      }
      gReadWaiting--;
    }

    gReading++;

    gReads++;                    // stats
    if (gReading > gMaxReads) {  // stats
      gMaxReads = gReading;
    }

    PR_ExitMonitor(gMonitor);
    // -- read lock entered

    last = gCounter;

    spin(aWorkDelay);

    PR_ASSERT(gCounter == last);  // test invariance

    // -- exit read lock
    PR_EnterMonitor(gMonitor);  // read unlock
    gReading--;

    //    if ((0 == gReading) && (0 < gWriteWaiting))  // notify waiting writes
    //    (do it anyway to show off the CondWait bug)
    PR_NotifyAll(gMonitor);
    PR_ExitMonitor(gMonitor);
    // -- read lock exited

    spin(aWaitDelay);
  }
}

void fireThread(char* aName, void (*aProc)(void* arg), Arg_t* aArg) {
  PRThread* thread =
      PR_CreateThread(PR_USER_THREAD, aProc, aArg, PR_PRIORITY_NORMAL,
                      PR_LOCAL_THREAD, PR_UNJOINABLE_THREAD, 0);
}

int pseudoMain(int argc, char** argv, char* pad) {
  PRInt32 lastWriteCount = gCounter;
  PRInt32 lastReadCount = gReads;
  Arg_t a1 = {500, 250};
  Arg_t a2 = {500, 500};
  Arg_t a3 = {250, 500};
  Arg_t a4 = {750, 250};
  Arg_t a5 = {100, 750};
  Arg_t a6 = {100, 500};
  Arg_t a7 = {100, 750};

  gMonitor = PR_NewMonitor();

  fireThread("R1", doReadThread, &a1);
  fireThread("R2", doReadThread, &a2);
  fireThread("R3", doReadThread, &a3);
  fireThread("R4", doReadThread, &a4);

  fireThread("W1", doWriteThread, &a5);
  fireThread("W2", doWriteThread, &a6);
  fireThread("W3", doWriteThread, &a7);

  fireThread("R5", doReadThread, &a1);
  fireThread("R6", doReadThread, &a2);
  fireThread("R7", doReadThread, &a3);
  fireThread("R8", doReadThread, &a4);

  fireThread("W4", doWriteThread, &a5);
  fireThread("W5", doWriteThread, &a6);
  fireThread("W6", doWriteThread, &a7);

  while (1) {
    PRInt32 writeCount, readCount;
    PRIntervalTime fiveSecs = PR_SecondsToInterval(5);
    PR_Sleep(fiveSecs);  // get out of the way

    // print some stats, not threadsafe, informative only
    writeCount = gCounter;
    readCount = gReads;
    printf("\ntick %d writes (+%d), %d reads (+%d) [max %d, %d, %d]",
           writeCount, writeCount - lastWriteCount, readCount,
           readCount - lastReadCount, gMaxReads, gMaxWriteWaits, gMaxReadWaits);
    lastWriteCount = writeCount;
    lastReadCount = readCount;
    gMaxReads = gMaxWriteWaits = gMaxReadWaits = 0;
  }
  return 0;
}

static void padStack(int argc, char** argv) {
  char pad[512]; /* Work around bug in nspr on windoze */
  pseudoMain(argc, argv, pad);
}

int main(int argc, char** argv) {
  PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
  padStack(argc, argv);
}

/* bug1test.c */
