#!/usr/bin/env qore
# -*- mode: qore; indent-tabs-mode: nil -*-

%new-style
%enable-all-warnings
%require-types
%strict-args
%require-our
%disable-warning non-existent-method-call

%requires ../../../../qlib/QUnit.qm

class MyRWLock inherits RWLock {
    int methodGate(any m) {
        return 0;
    }
}

%exec-class RwlockTest

public class RwlockTest inherits QUnit::Test {
    private {
        int iters   = 1000;
        int writers = 10;
        int readers = 10;

        MyRWLock rwl();
        Counter readc;
        Counter writec;
        hash o = {
            "key1": "key1",
            "key2": "key2",
            "key3": "key3",
            "key4": "key4",
        };
    }

    constructor() : Test("Rwlock test", "1.0") {
        addTestCase("Rwlock test", \testRwlock(), NOTHING);

        # Return for compatibility with test harness that checks return value.
        set_return_value(main());
    }

    globalSetUp() {
        readc = new Counter(readers);
        writec = new Counter(writers);
    }

    testRwlock() {
        while (writers) {
            background write_thread();
            writers--;
        }
        while (readers) {
            background read_thread();
            readers--;
        }

        # There's currently no thread_wait/thread_join, therefore have to use
        # this method of synchronization.
        readc.waitForZero();
        writec.waitForZero();
    }

    read_thread() {
        bool pass = True;
        for (int i = 0; i < iters; i++) {
            int t;
            rwl.readLock();
            if (o.key1 == o.key2)
                t = 1;
                #printf("read_thread (%02d) %d/%d:  key1 = key2\n", gettid(), $i, iters);
            else
                t = 1;
            #printf("read_thread (%02d) %d/%d: key1 != key2\n", gettid(), $i, iters);

            rwl.readLock();
            if (o.key3 == o.key4)
                t = 1;
                #printf("read_thread (%02d) %d/%d: key3 = key4\n", gettid(), $i, iters);
            else
                t = 1;
            #printf("read_thread (%02d) %d/%d: key3 != key4\n", gettid(), $i, iters);
            rwl.readUnlock();

            if (int n = rwl.numWriters()) {
                pass = False;
                n++;
                break;
            }
            rwl.readUnlock();
        }
        assertTrue(pass, "read check");
        readc.dec();
    }

    write_thread() {
        bool pass = True;
        for (int i = 0; i < iters; i++) {
            #printf("write_thread (%02d) %d/%d trying to get lock\n",
            #       gettid(), $i, iters);
            rwl.writeLock();
            any t = o.key2;
            o.key2 = o.key1;
            o.key1 = t;

            if (bool n = rwl.numWriters() > 1) {
                pass = False;
                n = False;
                break;
            }

            t = o.key4;
            o.key4 = o.key3;
            o.key3 = t;

            t = rand() % 100;
            o.t = rand() % 1000;

            #printf("write_thread (%02d) %d/%d: 1: %s 2: %s 3: %s 4: %s (new %s)\n",
            #       gettid(), $i, iters, $o.key1, $o.key2, $o.key3, $o.key4, $t);

            rwl.writeUnlock();
        }
        assertTrue(pass, "write check");
        writec.dec();
    }
}
