/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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);
    PR_STDIO_INIT();
    padStack (argc, argv);
}


/* bug1test.c */