PreviousUpNext

15.4.1153  src/lib/std/src/pthread-unit-test.pkg

## 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.pkg
herein

    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( &microseconds );
#   
#       // 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);
#   }


Comments and suggestions to: bugs@mythryl.org

PreviousUpNext