/* 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 */
