#!/usr/bin/env qore

%require-our

our $dl;  # deadlock flag

synchronized sub internal_deadlock_a($c) {
    if (exists $c) {
	$c.dec();
	$c.waitForZero();
    }
    if ($dl)
	return;
    try {
	return internal_deadlock_b();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
	$dl = True;
    }
}

synchronized sub internal_deadlock_b($c) {
    if (exists $c) {
	$c.dec();
	$c.waitForZero();
    }
    if ($dl)
	return;
    try {
	return internal_deadlock_a();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
	$dl = True;
    }
}

sub mutex_gate_deadlock_a($c, $m, $g) {
    my $al = new AutoLock($m);
    $c.dec();
    $c.waitForZero();
    try {
	$g.enter();
	$g.exit();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
}

sub mutex_gate_deadlock_b($c, $m, $g) {
    my $ag = new AutoGate($g);
    $c.dec();
    $c.waitForZero();
    try {
	$m.lock();
	$m.unlock();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
}

sub readwrite_deadlock_c($c, $rw1, $rw2) {
    my $al = new AutoWriteLock($rw1);
    $c.dec();
    $c.waitForZero();
    try {
	$rw2.writeLock();
	$rw2.writeUnlock();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
}

sub readwrite_deadlock_d($c, $rw1, $rw2) {
    my $al = new AutoReadLock($rw2);
    $c.dec();
    $c.waitForZero();
    try {
	$rw1.readLock();
	$rw1.readUnlock();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
}

sub test_thread_resources() {
    my $n = new Mutex();
    $n.lock();
    try {
	throwThreadResourceExceptions();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
    $n = new Gate();
    $n.enter();
    try {
	throwThreadResourceExceptions();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
    my $rw = new RWLock();
    $rw.readLock();
    $rw.readLock();
    try {
	throwThreadResourceExceptions();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
    $rw.writeLock();
    try {
	throwThreadResourceExceptions();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
}

sub cond_test($c, $cond, $m) {
    $m.lock();
    $c.dec();
    try {
	$cond.wait($m);
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
}

sub rwl_cond_test($c, $cond, $rwl) {
    $rwl.readLock();
    $c.dec();
    try {
	$cond.wait($rwl);
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
}

sub counter_test($c) {
    try {
	$c.waitForZero();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
}

sub queue_test($q) {
    try {
	$q.get();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
}

sub main() {
    # internal deadlock with synchronized subroutines
    my $c = new Counter(2);
    background internal_deadlock_a($c);
    internal_deadlock_b($c);

    # deadlock tests with qore classes and explicit locking
    my $m = new Mutex();
    my $g = new Gate();

    # increment counter for synchronization
    $c.inc();
    $c.inc();
    background mutex_gate_deadlock_a($c, $m, $g);
    mutex_gate_deadlock_b($c, $m, $g);

    # deadlock tests with other classes
    my $rw1 = new RWLock();
    my $rw2 = new RWLock();

    # increment counter for synchronization
    $c.inc();
    $c.inc();
    background readwrite_deadlock_c($c, $rw1, $rw2);
    readwrite_deadlock_d($c, $rw1, $rw2);

    # mutex tests
    $m.lock();
    try {
	$m.lock();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
    try {
	$m.unlock();
	$m.unlock();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
    try {
	delete $m;
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }

    # Gate tests
    try {
	$g.exit();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
    $g.enter();
    try {
	delete $g;
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }

    # RWLock tests
    try {
	$rw1.writeUnlock();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
    try {
	$rw1.readUnlock();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
    # RWLock tests
    try {
	$rw1.writeLock();
	$rw1.readUnlock();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
    $rw1.writeUnlock();
    try {
	$rw1.readLock();
	$rw1.writeUnlock();
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }
    $rw1.readUnlock();

    # make sure threads sleeping on Condition variable wake up with an exception
    # if the mutex object is deleted in another thread
    my $cond = new Condition();
    $m = new Mutex();
    # increment counter for synchronization
    $c.inc();
    $c.inc();
    background cond_test($c, $cond, $m);
    background cond_test($c, $cond, $m);
    $c.waitForZero();
    # lock and unlock to ensure until there are 2 condition variables waiting on this Mutex
    $m.lock();
    $m.unlock();
    try {
	delete $m;
	throw "NO-EXCEPTION-ERROR";
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }

    # try it with a RWLock object
    $c.inc();
    $c.inc();
    background rwl_cond_test($c, $cond, $rw1);
    background rwl_cond_test($c, $cond, $rw1);
    $c.waitForZero();
    # lock and unlock to ensure until there are 2 condition variables waiting on this Mutex
    $rw1.writeLock();
    $rw1.writeUnlock();
    try {
	delete $rw1;
	throw "NO-EXCEPTION-ERROR";
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }

    # make sure threads sleeping on a counter wake up with an exception
    # when the counter is deleted
    my $c1 = new Counter();
    $c1.inc();
    background counter_test($c1);
    background counter_test($c1);
    # sleep until there are 2 counter variables waiting on this Mutex
    while ($c1.getWaiting() != 2)
	usleep(100ms);
    try {
	delete $c1;
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }


    # make sure threads sleeping on a Queue wake up with an exception
    # when the Queue is deleted
    my $q = new Queue();
    background queue_test($q);
    background queue_test($q);
    # sleep until there are 2 threads waiting on this Queue
    while ($q.getWaiting() != 2)
	usleep(100ms);
    try {
	delete $q;
    }
    catch ($ex) {
	printf("%s: %s\n", $ex.err, $ex.desc);
    }

    # test thread resource tracking checks
    background test_thread_resources();
}

main();