


## pthread-unit-test.pkg
#
# Unit/regression test functionality for
#
# src/lib/std/src/pthread.pkg#
# (Multiple posix threads sharing the Mythryl
# heap and executing Mythryl code.)
# Compiled by:
# src/lib/test/unit-tests.lib# Run by:
# src/lib/test/all-unit-tests.pkg###############################################################################
# To-do / status:
#
# o XXX SUCKO FIXME: verify_that_basic_condition_variable_stuff_works () hangs if we delete the pthread::pthread_exit ();
# o XXX SUCKO FIXME: verify_that_basic_condition_variable_stuff_works () hangs any subthread throws an exception instead of calling pthread::pthread_exit ();
###############################################################################
stipulate
package mtx = winix_file_io_mutex; # winix_file_io_mutex is from src/lib/std/src/io/winix-file-io-mutex.pkg package pth = pthread; # pthread is from src/lib/std/src/pthread.pkgherein
package pthread_unit_test {
#
include unit_test; # unit_test is from src/lib/src/unit-test.pkg fun pline line_fn # Define a pthread-safe function to output lines.
= # "pline" is mnemonic for for "print_line" but also "parallel_print_line" and "pthread_safe_print_line" and such. :-)
pth::with_mutex_do mtx::mutex .{
#
line = line_fn () + "\n";
#
file::write (file::stdout, line );
};
package redblack_tree_torture_test {
#
include int_red_black_map; # int_red_black_map is from src/lib/src/int-red-black-map.pkg # When debugging uncomment the following lines and
# add more log_if calls as appropriate:
#
# file::set_logger_to (file::LOG_TO_FILE "xyzzy.log");
# log_if = file::log_if file::compiler_logging;
# log_if .{ "Top of script"; };
# These values don't constitute
# a torture test, they are just
# intended as insurance against
# bitrot:
#
loops = 3; # For a real torture test, 10 to 100 is a good range.
limit = 100; # For a real torture test, >10,000 is a good size range.
fun subpthread_fn id ()
=
{
for (loop = 0; loop < loops; ++loop) {
#
# pline .{ sprintf "loop %d thread %d" loop id; }; # Print narration line with proper mutual-exclusion vs other pthreads.
# Commented out at present to make 'make check' output look neater.
# Create a map by successive appends:
#
my test_map
=
for (m = empty, i = 0; i < limit; ++i; m) {
m = set (m, i, i);
assert (all_invariants_hold m );
assert (not (is_empty m) );
assert (the (first_val_else_null m) == 0 );
assert ( vals_count m == i+1 );
assert (#1 (the (first_keyval_else_null m)) == 0 );
assert (#2 (the (first_keyval_else_null m)) == 0 );
};
# Check resulting map's contents:
#
for (i = 0; i < limit; ++i) {
#
assert ((the (get (test_map, i))) == i );
};
# Try removing at all possible positions in map:
#
for (map' = test_map, i = 0; i < limit; ++i) {
my (map'', value) = drop (map', i);
assert (all_invariants_hold map'' );
};
assert (is_empty empty );
};
pthread::pthread_exit ();
};
fun run ()
=
{
# heap_debug::breakpoint_1 ();
# Should write logic to automatically adapt
# to number of cores on host machine: # XXX SUCKO FIXME
subpthread0 = pthread::spawn_pthread (subpthread_fn 0);
subpthread1 = pthread::spawn_pthread (subpthread_fn 1);
# subpthread2 = pthread::spawn_pthread (subpthread_fn 2);
# subpthread3 = pthread::spawn_pthread (subpthread_fn 3);
# subpthread4 = pthread::spawn_pthread (subpthread_fn 4);
# subpthread5 = pthread::spawn_pthread (subpthread_fn 5);
pthread::join_pthread subpthread0;
pthread::join_pthread subpthread1;
# pthread::join_pthread subpthread2;
# pthread::join_pthread subpthread3;
# pthread::join_pthread subpthread4;
# pthread::join_pthread subpthread5;
};
};
#
name = "src/lib/std/src/pthread-unit-test.pkg";
log_if = file::log_if file::compiler_logging;
fun verify_that_basic_spawn_and_join_are_working ()
=
{ foo = REF 0;
#
fun subthread_fn ()
=
{ makelib::scripting_globals::sleep 0.01; # Give main thread a good chance to finish early if join_pthread is totally broken.
#
foo := 1; # Give main thread visible evidence that we've run.
#
pthread::pthread_exit (); # Die.
};
subthread = pthread::spawn_pthread subthread_fn; # Spawn a subthread.
#
pthread::join_pthread subthread; # Wait for subthread to exit.
#
assert (*foo == 1); # Verify that subthread did what we expected.
};
fun verify_that_basic_mutex_stuff_is_working ()
=
{ foo = REF 0;
#
mutex = pthread::make_mutex ();
pthread::acquire_mutex mutex;
fun subthread_fn ()
=
{
pthread::acquire_mutex mutex;
#
foo := 1;
#
pthread::release_mutex mutex;
#
pthread::pthread_exit ();
};
pthread = pthread::spawn_pthread subthread_fn;
makelib::scripting_globals::sleep 0.01; # Give child a chance to run if mutex does not block properly.
assert (*foo == 0); # Verify that child has not run.
pthread::release_mutex mutex; # Unblock child.
pthread::join_pthread pthread; # Join child.
pthread::free_mutex mutex;
assert (*foo == 1); # Verify that child has now run.
};
fun verify_that_successful_trylock_works ()
=
{
mutex = pthread::make_mutex ();
#
fun subthread_fn ()
=
{
try_result = pthread::try_mutex mutex;
assert (try_result == pthread::ACQUIRED_MUTEX);
#
pthread::release_mutex mutex; # Why not.
pthread::pthread_exit ();
};
pthread = pthread::spawn_pthread subthread_fn;
pthread::join_pthread pthread;
pthread::free_mutex mutex;
};
fun verify_that_unsuccessful_trylock_works ()
=
{ mutex = pthread::make_mutex ();
#
pthread::acquire_mutex mutex;
fun subthread_fn ()
=
{ assert ((pthread::try_mutex mutex) == pthread::MUTEX_WAS_UNAVAILABLE);
#
pthread::pthread_exit ();
};
pthread = pthread::spawn_pthread subthread_fn;
makelib::scripting_globals::sleep 1.11;
pthread::release_mutex mutex;
pthread::join_pthread pthread;
pthread::free_mutex mutex;
};
fun verify_that_basic_barrier_wait_works ()
=
{ foo = REF 0;
#
mutex = pthread::make_mutex ();
barrier = pthread::make_barrier ();
pthread::set_barrier { barrier, threads => 3 };
fun subthread_fn ()
=
{
pthread::wait_on_barrier barrier;
#
pthread::acquire_mutex mutex;
#
foo := *foo + 1;
#
pthread::release_mutex mutex;
#
pthread::pthread_exit ();
};
subthread1 = pthread::spawn_pthread subthread_fn;
subthread2 = pthread::spawn_pthread subthread_fn;
makelib::scripting_globals::sleep 0.01;
assert (*foo == 0);
pthread::wait_on_barrier barrier;
pthread::join_pthread subthread1;
pthread::join_pthread subthread2;
assert (*foo == 2);
pthread::free_mutex mutex;
pthread::free_barrier barrier;
};
fun verify_barrier_wait_return_value ()
=
{
foo = REF 0; # Every pthread will increment this.
bar = REF 0; # Only the One True Pthread will increment this.
mutex = pthread::make_mutex ();
barrier = pthread::make_barrier ();
pthread::set_barrier { barrier, threads => 3 };
fun subthread_fn ()
=
{
if (pthread::wait_on_barrier barrier) # Exactly one pthread waiting on barrier should see a TRUE return value.
#
pthread::acquire_mutex mutex;
#
foo := *foo + 1;
bar := *bar + 1;
#
pthread::release_mutex mutex;
else
pthread::acquire_mutex mutex;
#
foo := *foo + 1;
#
pthread::release_mutex mutex;
fi;
#
pthread::pthread_exit ();
};
subthread1 = pthread::spawn_pthread subthread_fn;
subthread2 = pthread::spawn_pthread subthread_fn;
subthread3 = pthread::spawn_pthread subthread_fn;
pthread::join_pthread subthread1;
pthread::join_pthread subthread2;
pthread::join_pthread subthread3;
assert (*foo == 3);
assert (*bar == 1);
pthread::free_mutex mutex;
pthread::free_barrier barrier;
};
fun verify_that_basic_condition_variable_stuff_works ()
=
{ # When debugging uncomment the following lines and
# add more log_if calls as appropriate:
#
# file::set_logger_to (file::LOG_TO_FILE "xyzzy.log");
# log_if = file::log_if file::compiler_logging;
# log_if .{ "verify_that_basic_condition_variable_stuff_works"; };
loops = 10;
foo = REF 0;
last = REF 0;
mutex = pthread::make_mutex ();
condvar = pthread::make_condvar ();
fun subthread_fn id ()
=
{
pthread::acquire_mutex mutex;
#
for (i = 0; i < loops; ++i) {
# printf "subthread_fn(%d) loop d=%d\n" id i; file::flush file::stdout; log_if .{ sprintf "subthread_fn(%d)/CCC i d=%d" id i; };
for (((*last + 1) % 4) != id) {
#
pthread::wait_on_condvar (condvar, mutex);
};
foo := *foo + 1;
last := id;
pthread::broadcast_condvar condvar;
};
#
pthread::release_mutex mutex;
pthread::pthread_exit ();
};
subthread0 = pthread::spawn_pthread (subthread_fn 0);
subthread1 = pthread::spawn_pthread (subthread_fn 1);
subthread2 = pthread::spawn_pthread (subthread_fn 2);
subthread3 = pthread::spawn_pthread (subthread_fn 3);
pthread::join_pthread subthread0;
pthread::join_pthread subthread1;
pthread::join_pthread subthread2;
pthread::join_pthread subthread3;
pthread::free_mutex mutex;
pthread::free_condvar condvar;
assert TRUE; # printf "Script z DONE\n"; file::flush file::stdout; log_if .{ "Script z DONE."; };
}; # fun verify_that_basic_condition_variable_stuff_works
fun run ()
=
{ printf "\nDoing %s:\n" name;
#
assert( pthread::get_pthread_id() != 0 );
#
# This is mostly just to verify that src/c/lib/pthread/libmythryl-pthread.c
# and src/c/pthread/pthread-on-posix-threads.c
# were compiled and linked into our
# runtime executable.
verify_that_basic_spawn_and_join_are_working ();
verify_that_basic_mutex_stuff_is_working ();
verify_that_successful_trylock_works ();
verify_that_unsuccessful_trylock_works ();
verify_that_basic_barrier_wait_works ();
verify_barrier_wait_return_value ();
verify_that_basic_condition_variable_stuff_works ();
#
redblack_tree_torture_test::run ();
#
summarize_unit_tests name;
};
};
end;
##########################################################################
# Script version of above fn verify_that_basic_condition_variable_stuff_works.c
# This is useful when debugging:
#
# #!/usr/bin/mythryl
#
# {
# # When debugging uncomment the following lines and
# # add more log_if calls as appropriate:
# #
# # file::set_logger_to (file::LOG_TO_FILE "xyzzy.log");
# # log_if = file::log_if file::compiler_logging;
# # log_if .{ "Top of script z"; };
#
# loops = 10;
#
# foo = REF 0;
# last = REF 0;
#
# mutex = pthread::make_mutex ();
# condvar = pthread::make_condvar ();
#
# pthread::set_up_mutex (mutex, NULL);
# pthread::set_up_condvar (condvar, NULL);
#
# fun subthread_fn id ()
# =
# {
# pthread::acquire_mutex mutex;
# #
# for (i = 0; i < loops; ++i) {
# # printf "subthread_fn(%d) loop d=%d\n" id i; file::flush file::stdout; log_if .{ sprintf "subthread_fn(%d)/CCC i d=%d" id i; };
# for (((*last + 1) % 4) != id) {
#
# pthread::wait_on_condvar (condvar, mutex);
# };
#
# foo := *foo + 1;
# last := id;
#
# pthread::broadcast_condvar condvar;
# };
# #
# pthread::release_mutex mutex;
#
# pthread::pthread_exit ();
# };
#
# subthread0 = pthread::spawn_pthread (subthread_fn 0);
# subthread1 = pthread::spawn_pthread (subthread_fn 1);
# subthread2 = pthread::spawn_pthread (subthread_fn 2);
# subthread3 = pthread::spawn_pthread (subthread_fn 3);
#
# pthread::join_pthread subthread0;
# pthread::join_pthread subthread1;
# pthread::join_pthread subthread2;
# pthread::join_pthread subthread3;
#
# pthread::free_mutex mutex;
# pthread::free_condvar condvar;
#
# # printf "Script z DONE\n"; file::flush file::stdout; log_if .{ "Script z DONE."; };
# };
##########################################################################
# verify-that-basic-condition-variable-stuff-works.c
#
# This is a pure-C version of the above fun verify_that_basic_condition_variable_stuff_works ()
# I wrote it during debugging to verify that the problem was in my
# Mythryl implementation, not the underlying <pthread.h> code; I
# leave it here in case such verification is needed again at some point. -- 2011-12-13 CrT
#
# // Compile via:
# //
# // gcc -O2 -std=c99 -Wall -m32 -D_REENTRANT -pthread -lpthread -o verify-that-basic-condition-variable-stuff-works verify-that-basic-condition-variable-stuff-works.c
#
# #include <stdio.h>
# #include <stdlib.h>
#
# #include <stdarg.h>
# #include <string.h>
#
# #include <unistd.h>
# #include <errno.h>
#
# #include <sys/time.h>
# #include <sys/types.h>
#
# #include <pthread.h>
#
# #define MAX_BUF 4096
#
# pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
# pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;
#
# int get_time_of_day (int* microseconds) {
# //===============
# //
# // Get time as (seconds,microseconds).
# //
#
# int c_sec;
# int c_usec;
#
# { struct timeval t;
# //
# gettimeofday (&t, NULL);
# c_sec = t.tv_sec;
# c_usec = t.tv_usec;
# }
#
# *microseconds = c_usec;
#
# return c_sec;
# }
#
# void log_if (const char * fmt, ...) {
#
# //
# int len;
# int seconds;
# int microseconds;
#
# char buf[ MAX_BUF ];
#
# va_list va;
#
# // Start by writing the timestamp into buf[].
# //
# // We match the timestamp formats in make_logstring in
# //
# // src/lib/src/lib/thread-kit/src/lib/logger.pkg# // and src/lib/std/src/io/winix-text-file-for-os-g.pkg# //
# // Making the gettimeofday() system call here
# // is a little bit risky in that the system
# // call might change the behavior being debugged,
# // but I think the tracelog timestamps are valuable
# // enough to justify the risk:
# //
# seconds = get_time_of_day( µseconds );
#
# // The intent here is
# //
# // 1) That doing unix 'sort' on a logfile will do the right thing:
# // sort first by time, then by process id, then by thread id.
# //
# // 2) To facilitate egrep/perl processing, e.g. doing stuff like
# // egrep 'pid=021456' logfile
# //
# // We fill in dummy tid= and (thread) name= values here to reduce
# // the need for special-case code when processing logfiles:
# //
# sprintf(buf,"time=%10d.%06d pid=%08d ptid=%08d tid=00000000 name=%-16s msg=", seconds, microseconds, getpid(), (int)(pthread_self()&0x3FFFFFFF), "none");
#
# // Now write the message proper into buf[],
# // right after the timestamp:
# //
# len = strlen( buf );
#
# // Drop leading blanks:
# //
# while (*fmt == ' ') ++fmt;
#
# va_start(va, fmt);
# vsnprintf(buf+len, MAX_BUF-len, fmt, va);
# va_end(va);
#
# // Append a newline to buffer:
# //
# strcpy( buf + strlen(buf), "\n" );
#
# puts( buf );
# }
#
# int loops = 10;
# int foo = 0;
# int last = 0;
#
#
#
# void set_up_mutex( void ) {
# //
# int err = pthread_mutex_init( &mutex, NULL );
# //
# switch (err) {
# //
# case 0: break;
# case ENOMEM: puts("Insufficient ram to initialize mutex\n"); exit(1);
# case EAGAIN: puts("Insufficient (non-ram) resources to initialize mutex\n"); exit(1);
# case EPERM: puts("Caller lacks privilege to initialize mutex\n"); exit(1);
# case EBUSY: puts("Attempt to reinitialize the object referenced by mutex, a previously initialized, but not yet destroyed, mutex.\n"); exit(1);
# case EINVAL: puts("Invalid attribute\n"); exit(1);
# default: puts("Undocumented error return from pthread_mutex_init()\n"); exit(1);
# }
# }
#
#
#
# void set_up_condvar( void ) {
# //
# int result = pthread_cond_init( &condvar, NULL );
# //
# if (result) { puts("pth__condvar_init: Unable to initialize condition variable.\n"); exit(1); }
# }
#
#
#
# void* subthread_fn( void* id_as_voidptr ) {
# int id = (int) id_as_voidptr; // Ick.
# log_if( "subthread_fn(%d)/AAA", id );
# { int err = pthread_mutex_lock( &mutex );
# switch (err) {
# //
# case 0: break;; // Success.
# case EINVAL: puts("pth__mutex_lock: Invalid mutex or mutex has PTHREAD_PRIO_PROTECT set and calling thread's priority is higher than mutex's priority ceiling.\n"); exit(1);
# case EBUSY: puts("pth__mutex_lock: Mutex was already locked.\n"); exit(1);
# case EAGAIN: puts("pth__mutex_lock: Recursive lock limit exceeded.\n"); exit(1);
# case EDEADLK: puts("pth__mutex_lock: Deadlock, or mutex already owned by thread.\n"); exit(1);
# default: puts("pth__mutex_lock: Undocumented error return from pthread_mutex_lock()\n"); exit(1);
# }
# }
# log_if("subthread_fn(%d)/BBB", id);
# //
# // for (i = 0; i < loops; ++i) {
# log_if("subthread_fn(%d)/CCC", id);
# while (((last + 1) % 4) != id) {
# log_if("subthread_fn(%d)/DDD *last d=%d\n", id, last);
#
# int result = pthread_cond_wait( &condvar, &mutex );
# if (result) { puts("pth__condvar_wait: Unable to wait on condition variable.\n"); exit(1); }
#
# log_if("subthread_fn(%d)/EEE", id);
# };
# log_if("subthread_fn(%d)/FFF", id);
# ++foo;
# log_if("subthread_fn(%d)/GGG", id);
# last = id;
# log_if("subthread_fn(%d)/HHH", id);
# int result = pthread_cond_broadcast( &condvar );
# if (result) { puts("pth__condvar_broadcast: Unable to broadcast on condition variable.\n"); exit(1); }
#
# log_if("subthread_fn(%d)/III", id);
# log_if("%d", id);
# // };
# //
# log_if("subthread_fn(%d)/JJJ", id);
# { int err = pthread_mutex_unlock( &mutex );
# switch (err) {
# //
# case 0: break; // Successfully released lock.
# case EINVAL: puts("pth__mutex_unlock: Mutex has PTHREAD_PRIO_PROTECT set and calling thread's priority is higher than mutex's priority ceiling.\n"); exit(1);
# case EBUSY: puts("pth__mutex_unlock: The mutex already locked.\n"); exit(1);
# default: puts("pth__mutex_unlock: Undocumented error returned by pthread_mutex_unlock().\n"); exit(1);
# }
# }
#
# log_if("subthread_fn(%d)/KKK", id);
# pthread_exit( NULL );
# return (void*) NULL;
# }
#
# pthread_t spawn_subthread( int id ) {
# //
# pthread_t tid;
# //
# int err = pthread_create( &tid, NULL, subthread_fn, (void*)id );
# //
# switch (err) {
# //
# case 0: break;
# case EAGAIN: puts("pth__pthread_create: Insufficient resources to create posix thread: May have reached PTHREAD_THREADS_MAX.\n"); exit(1);
# case EPERM: puts("pth__pthread_create: You lack permissions to set requested scheduling.\n"); exit(1);
# case EINVAL: puts("pth__pthread_create: Invalid attributes.\n"); exit(1);
# default: puts("pth__pthread_create: Undocumented error returned by pthread_create().\n"); exit(1);
# }
# return tid;
# }
#
# void join_subthread( pthread_t subthread_id ) {
# //
# int err = pthread_join( subthread_id, NULL );
# switch (err) {
# //
# case 0: break;
# case ESRCH: puts("pth__pthread_join: No such thread.\n"); exit(1);
# case EDEADLK: puts("pth__pthread_join: Attempt to join self (or other deadlock).\n"); exit(1);
# case EINVAL: puts("pth__pthread_join: Not a joinable thread.\n"); exit(1);
# default: puts("pth__pthread_join: Undocumented error.\n"); exit(1);
# }
# }
#
# int main( int argc, char** argv ) {
# //
# set_up_mutex();
# set_up_condvar();
# log_if("111");
# pthread_t subthread0 = spawn_subthread(0); log_if("222");
# pthread_t subthread1 = spawn_subthread(1); log_if("333");
# pthread_t subthread2 = spawn_subthread(2); log_if("444");
# pthread_t subthread3 = spawn_subthread(3); log_if("555");
#
# join_subthread( subthread0 ); log_if("666");
# join_subthread( subthread1 ); log_if("777");
# join_subthread( subthread2 ); log_if("888");
# join_subthread( subthread3 ); log_if("999");
# //
# printf("z.c: Done!\n");
# exit(0);
# }


