/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by The HDF Group.                                               *
 * All rights reserved.                                                      *
 *                                                                           *
 * This file is part of HDF5.  The full HDF5 copyright notice, including     *
 * terms governing use, modification, and redistribution, is contained in    *
 * the COPYING file, which can be found at the root of the source code       *
 * distribution tree, or in https://support.hdfgroup.org/ftp/HDF5/releases.  *
 * If you do not have access to either file, you may request a copy from     *
 * help@hdfgroup.org.                                                        *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/********************************************************************
 *
 * Test the correctness of the recursive R/W lock in the HDF5 library
 * -------------------------------------------------------------
 *
 * Test the recursive R/W lock in isolation, using a combination of
 * error return values and statistics collected by the recursive
 * R/W lock to detect any failures.
 *
 * No file is created.
 *
 * Multiple threads are created, and allowed to compete for the lock.
 * The number of threads, and the number of times they obtain the
 * lock depends on the express test level.
 *
 * Created: Sept. 3, 2020
 * Programmer: John Mainzer
 *
 ********************************************************************/

#include "ttsafe.h"

/* Include library header files */
#include "H5ACprivate.h"

#ifdef H5_USE_RECURSIVE_WRITER_LOCKS

#ifdef H5_HAVE_THREADSAFE

#define MAX_NUM_THREADS 32
#define MAX_LOCK_CYCLES 1000000

/* structure used to configure test threads in the recurive
 * R/W/ lock tests.
 */
/***********************************************************************
 *
 * Structure rec_rw_lock_test_udata_t
 *
 * Arrays of instances of rec_rw_lock_test_udata_t are used to configure
 * the threads used to test the recursive R/W lock, and to collect
 * statistics on their behaviour.  These statistics are aggregated and
 * used to cross check the statistics collected by the recursive R/W
 * lock proper.
 *
 * The fields of the structure are discussed below:
 *
 * rw_lock: Pointer to the recursive R/W under test.
 *
 * id:          ID assigned to the target thread.  Used primarily for
 *              sanity checking.
 *
 * target_rd_lock_cycles: The number of times the test thread is
 *              required to obtain and drop the read lock.  Note
 *              that this value restricts the number of initial
 *              read locks only.  Additional recursive locks are
 *              possible -- see max_recursive_lock_depth below.
 *
 * target_wr_lock_cycles: The number of times the test thread is
 *              required to obtain and drop the write lock.  Note
 *              that this value restricts the number of initial
 *              write locks only.  Additional recursive locks are
 *              possible -- see max_recursive_lock_depth below.
 *
 * max_recursive_lock_depth: Once a test thread gains a lock, it does
 *              random recursive leocks and unlocks until it happens
 *              to drop the lock.  The max_recursive_lock_depth
 *              places an upper bound on the net number of locks.
 *              Any attempt exceed this limit is converted into
 *              an unlock.
 *
 * The remaining fields are used for statistics collection.  They are
 * thread specific versions of the fields of the same name in
 * H5TS_rw_lock_stats_t.  See the header comment for that
 * structure (in H5TSprivate.h) for further details.
 *
 *                                         JRM -- 9/3/20
 *
 ***********************************************************************/
typedef struct rec_rw_lock_test_udata_t {

    /* thread control fields */
    H5TS_rw_lock_t *rw_lock;
    int32_t         id;
    int32_t         target_rd_lock_cycles;
    int32_t         target_wr_lock_cycles;
    int32_t         max_recursive_lock_depth;

    /* thread stats fields */
    int64_t read_locks_granted;
    int64_t read_locks_released;
    int64_t real_read_locks_granted;
    int64_t real_read_locks_released;
    int64_t write_locks_granted;
    int64_t write_locks_released;
    int64_t real_write_locks_granted;
    int64_t real_write_locks_released;

} rec_rw_lock_test_udata_t;

void *tts_rw_lock_smoke_check_test_thread(void *_udata);

/*
 **********************************************************************
 * tts_rec_rw_lock_smoke_check_1
 *
 * Single thread test to verify basic functionality and error
 * rejection of the recursive R/W lock.
 *
 *  1) Initialize an instance of the recursive R/W lock.
 *
 *  2) Obtain a read lock.
 *
 *  3) Drop the read lock.
 *
 *  4) Verify the expected stats, and then reset them.
 *
 *  5) Obtain a read lock.
 *
 *  6) Obtain the read lock a second time.
 *
 *  7) Drop the read lock.
 *
 *  8) Drop the read lock a second time.
 *
 *  9) Verify the expected stats, and then reset them.
 *
 * 10) Obtain a write lock.
 *
 * 11) Drop the write lock.
 *
 * 12) Verify the expected stats, and then reset them.
 *
 * 13) Obtain a write lock.
 *
 * 14) Obtain the write lock a second time.
 *
 * 15) Drop the write lock.
 *
 * 16) Drop the write lock a second time.
 *
 * 17) Verify the expected stats, and then reset them.
 *
 * 18) Obtain a write lock.
 *
 * 19) Attempt to obtain a read lock -- should fail.
 *
 * 20) Drop the write lock.
 *
 * 21) Obtain a read lock.
 *
 * 22) Attempt to obtain a write lock -- should fail.
 *
 * 23) Drop the read lock.
 *
 * 24) Verify the expected stats, and then reset them.
 *
 * 25) Shut down the recursive R/W lock.
 *
 * Created Sept. 3. 2020.
 *
 * Programmer: John Mainzer
 *
 **********************************************************************
 */
void
tts_rec_rw_lock_smoke_check_1(void)
{
    herr_t                      result;
    struct H5TS_rw_lock_stats_t stats;
    struct H5TS_rw_lock_t       rec_rw_lock;

    /* 1) Initialize an instance of the recursive R/W lock. */
    result = H5TS_rw_lock_init(&rec_rw_lock, H5TS_RW_LOCK_POLICY_FAVOR_WRITERS);
    CHECK_I(result, "H5TS_rw_lock_init");

    /* 2) Obtain a read lock. */
    result = H5TS_rw_rdlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_rdlock -- 1");

    /* 3) Drop the read lock. */
    result = H5TS_rw_unlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_unlock -- 1");

    /* 4) Verify the expected stats, and then reset them. */
    result = H5TS_rw_lock_get_stats(&rec_rw_lock, &stats);
    CHECK_I(result, "H5TS_rw_lock_get_stats -- 1");

    result = H5TS_rw_lock_reset_stats(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_lock_reset_stats -- 1");

    /* clang-format makes this conditional unreadable, so turn it off. */
    /* clang-format off */
    if ( ( stats.read_locks_granted             != 1 ) ||
         ( stats.read_locks_released            != 1 ) ||
         ( stats.real_read_locks_granted        != 1 ) ||
         ( stats.real_read_locks_released       != 1 ) ||
         ( stats.max_read_locks                 != 1 ) ||
         ( stats.max_read_lock_recursion_depth  != 1 ) ||
         ( stats.read_locks_delayed             != 0 ) ||
         ( stats.max_read_locks_pending         != 0 ) ||
         ( stats.write_locks_granted            != 0 ) ||
         ( stats.write_locks_released           != 0 ) ||
         ( stats.real_write_locks_granted       != 0 ) ||
         ( stats.real_write_locks_released      != 0 ) ||
         ( stats.max_write_locks                != 0 ) ||
         ( stats.max_write_lock_recursion_depth != 0 ) ||
         ( stats.write_locks_delayed            != 0 ) ||
         ( stats.max_write_locks_pending        != 0 ) ) {

        TestErrPrintf("Unexpected recursive R/W lock stats -- 1");
        H5TS_rw_lock_print_stats("Actual stats", &stats);
    }
    /* clang-format on */

    /* 5) Obtain a read lock. */
    result = H5TS_rw_rdlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_rdlock -- 2");

    /* 6) Obtain the read lock a second time. */
    result = H5TS_rw_rdlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_rdlock -- 3");

    /* 7) Drop the read lock. */
    result = H5TS_rw_unlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_unlock -- 2");

    /* 8) Drop the read lock a second time. */
    result = H5TS_rw_unlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_unlock -- 3");

    /* 9) Verify the expected stats, and then reset them. */
    result = H5TS_rw_lock_get_stats(&rec_rw_lock, &stats);
    CHECK_I(result, "H5TS_rw_lock_get_stats -- 2");

    result = H5TS_rw_lock_reset_stats(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_lock_reset_stats -- 2");

    /* clang-format makes this conditional unreadable, so turn it off. */
    /* clang-format off */
    if ( ( stats.read_locks_granted             != 2 ) ||
         ( stats.read_locks_released            != 2 ) ||
         ( stats.real_read_locks_granted        != 1 ) ||
         ( stats.real_read_locks_released       != 1 ) ||
         ( stats.max_read_locks                 != 1 ) ||
         ( stats.max_read_lock_recursion_depth  != 2 ) ||
         ( stats.read_locks_delayed             != 0 ) ||
         ( stats.max_read_locks_pending         != 0 ) ||
         ( stats.write_locks_granted            != 0 ) ||
         ( stats.write_locks_released           != 0 ) ||
         ( stats.real_write_locks_granted       != 0 ) ||
         ( stats.real_write_locks_released      != 0 ) ||
         ( stats.max_write_locks                != 0 ) ||
         ( stats.max_write_lock_recursion_depth != 0 ) ||
         ( stats.write_locks_delayed            != 0 ) ||
         ( stats.max_write_locks_pending        != 0 ) ) {

        TestErrPrintf("Unexpected recursive R/W lock stats -- 2");
        H5TS_rw_lock_print_stats("Actual stats", &stats);
    }
    /* clang-format on */

    /* 10) Obtain a write lock. */
    result = H5TS_rw_wrlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_wrlock -- 1");

    /* 11) Drop the write lock. */
    result = H5TS_rw_unlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_unlock -- 4");

    /* 12) Verify the expected stats, and then reset them. */
    result = H5TS_rw_lock_get_stats(&rec_rw_lock, &stats);
    CHECK_I(result, "H5TS_rw_lock_get_stats -- 3");

    result = H5TS_rw_lock_reset_stats(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_lock_reset_stats -- 3");

    /* clang-format makes this conditional unreadable, so turn it off. */
    /* clang-format off */
    if ( ( stats.read_locks_granted             != 0 ) ||
         ( stats.read_locks_released            != 0 ) ||
         ( stats.real_read_locks_granted        != 0 ) ||
         ( stats.real_read_locks_released       != 0 ) ||
         ( stats.max_read_locks                 != 0 ) ||
         ( stats.max_read_lock_recursion_depth  != 0 ) ||
         ( stats.read_locks_delayed             != 0 ) ||
         ( stats.max_read_locks_pending         != 0 ) ||
         ( stats.write_locks_granted            != 1 ) ||
         ( stats.write_locks_released           != 1 ) ||
         ( stats.real_write_locks_granted       != 1 ) ||
         ( stats.real_write_locks_released      != 1 ) ||
         ( stats.max_write_locks                != 1 ) ||
         ( stats.max_write_lock_recursion_depth != 1 ) ||
         ( stats.write_locks_delayed            != 0 ) ||
         ( stats.max_write_locks_pending        != 0 ) ) {

        TestErrPrintf("Unexpected recursive R/W lock stats -- 3");
        H5TS_rw_lock_print_stats("Actual stats", &stats);
    }
    /* clang-format on */

    /* 13) Obtain a write lock. */
    result = H5TS_rw_wrlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_wrlock -- 2");

    /* 14) Obtain the write lock a second time. */
    result = H5TS_rw_wrlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_wrlock -- 3");

    /* 15) Drop the write lock. */
    result = H5TS_rw_unlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_unlock -- 5");

    /* 16) Drop the write lock a second time. */
    result = H5TS_rw_unlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_unlock -- 6");

    /* 17) Verify the expected stats, and then reset them. */
    result = H5TS_rw_lock_get_stats(&rec_rw_lock, &stats);
    CHECK_I(result, "H5TS_rw_lock_get_stats -- 4");

    result = H5TS_rw_lock_reset_stats(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_lock_reset_stats -- 4");

    /* clang-format makes this conditional unreadable, so turn it off. */
    /* clang-format off */
    if ( ( stats.read_locks_granted             != 0 ) ||
         ( stats.read_locks_released            != 0 ) ||
         ( stats.real_read_locks_granted        != 0 ) ||
         ( stats.real_read_locks_released       != 0 ) ||
         ( stats.max_read_locks                 != 0 ) ||
         ( stats.max_read_lock_recursion_depth  != 0 ) ||
         ( stats.read_locks_delayed             != 0 ) ||
         ( stats.max_read_locks_pending         != 0 ) ||
         ( stats.write_locks_granted            != 2 ) ||
         ( stats.write_locks_released           != 2 ) ||
         ( stats.real_write_locks_granted       != 1 ) ||
         ( stats.real_write_locks_released      != 1 ) ||
         ( stats.max_write_locks                != 1 ) ||
         ( stats.max_write_lock_recursion_depth != 2 ) ||
         ( stats.write_locks_delayed            != 0 ) ||
         ( stats.max_write_locks_pending        != 0 ) ) {

        TestErrPrintf("Unexpected recursive R/W lock stats -- 4");
        H5TS_rw_lock_print_stats("Actual stats", &stats);
    }
    /* clang-format on */

    /* 18) Obtain a write lock. */
    result = H5TS_rw_wrlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_wrlock -- 4");

    /* 19) Attempt to obtain a read lock -- should fail. */
    result = H5TS_rw_rdlock(&rec_rw_lock);
    VERIFY(result, FAIL, "H5TS_rw_rdlock -- 4");

    /* 20) Drop the write lock. */
    result = H5TS_rw_unlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_unlock -- 6");

    /* 21) Obtain a read lock. */
    result = H5TS_rw_rdlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_rdlock -- 5");

    /* 22) Attempt to obtain a write lock -- should fail. */
    result = H5TS_rw_wrlock(&rec_rw_lock);
    VERIFY(result, FAIL, "H5TS_rw_wrlock -- 5");

    /* 23) Drop the read lock. */
    result = H5TS_rw_unlock(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_unlock -- 6");

    /* 24) Verify the expected stats, and then reset them. */
    result = H5TS_rw_lock_get_stats(&rec_rw_lock, &stats);
    CHECK_I(result, "H5TS_rw_lock_get_stats -- 5");

    result = H5TS_rw_lock_reset_stats(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_lock_reset_stats -- 5");

    /* clang-format makes this conditional unreadable, so turn it off. */
    /* clang-format off */
    if ( ( stats.read_locks_granted             != 1 ) ||
         ( stats.read_locks_released            != 1 ) ||
         ( stats.real_read_locks_granted        != 1 ) ||
         ( stats.real_read_locks_released       != 1 ) ||
         ( stats.max_read_locks                 != 1 ) ||
         ( stats.max_read_lock_recursion_depth  != 1 ) ||
         ( stats.read_locks_delayed             != 0 ) ||
         ( stats.max_read_locks_pending         != 0 ) ||
         ( stats.write_locks_granted            != 1 ) ||
         ( stats.write_locks_released           != 1 ) ||
         ( stats.real_write_locks_granted       != 1 ) ||
         ( stats.real_write_locks_released      != 1 ) ||
         ( stats.max_write_locks                != 1 ) ||
         ( stats.max_write_lock_recursion_depth != 1 ) ||
         ( stats.write_locks_delayed            != 0 ) ||
         ( stats.max_write_locks_pending        != 0 ) ) {

        TestErrPrintf("Unexpected recursive R/W lock stats -- 5");
        H5TS_rw_lock_print_stats("Actual stats", &stats);
    }
    /* clang-format on */

    /* 25) Shut down the recursive R/W lock. */
    result = H5TS_rw_lock_destroy(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_lock_destroy");

    return;

} /* end tts_rec_rw_lock_smoke_check_1() */

void
cleanup_rec_rw_lock_smoke_check_1(void)
{
    /* nothing to do */
    return;
}

/*
 **********************************************************************
 * tts_rw_lock_smoke_check_test_thread
 *
 * Perform a sequence of recursive read and/or write locks on the
 * target recursive R/W lock as directed by the supplied user data.
 * Record all operations in the user data for later cross checking
 * with the statistics maintained by the recursive R/W lock.
 *
 * Note that while number of read and/or write locks is fixed, the
 * number of recursive lock and unlock calls is random, as is the
 * order of read and write locks if both are enabled.
 *
 * Created Sept. 3. 2020.
 *
 * Programmer: John Mainzer
 *
 **********************************************************************
 */

void *
tts_rw_lock_smoke_check_test_thread(void *_udata)
{
    hbool_t                          read;
    int32_t                          rec_lock_depth = 0;
    int32_t                          max_rec_lock_depth;
    int32_t                          rd_locks_remaining;
    int32_t                          wr_locks_remaining;
    herr_t                           result;
    H5TS_rw_lock_t                  *rw_lock;
    struct rec_rw_lock_test_udata_t *udata;

    HDassert(_udata);

    udata = (struct rec_rw_lock_test_udata_t *)_udata;

    rd_locks_remaining = udata->target_rd_lock_cycles;
    wr_locks_remaining = udata->target_wr_lock_cycles;
    max_rec_lock_depth = udata->max_recursive_lock_depth;
    rw_lock            = udata->rw_lock;

    while ((rd_locks_remaining > 0) || (wr_locks_remaining > 0)) {

        if (wr_locks_remaining == 0) {

            read = TRUE;
        }
        else if (rd_locks_remaining == 0) {

            read = FALSE;
        }
        else {

            if ((HDrand() % 2) == 0) {

                read = TRUE;
            }
            else {

                read = FALSE;
            }
        }

        if (read) {

            result = H5TS_rw_rdlock(rw_lock);
            CHECK_I(result, "H5TS_rw_rdlock -- 1");

            udata->read_locks_granted++;
            udata->real_read_locks_granted++;
            rd_locks_remaining--;
            rec_lock_depth = 1;

            while (rec_lock_depth > 0) {

                if ((rec_lock_depth >= max_rec_lock_depth) || ((HDrand() % 2) == 0)) {

                    result = H5TS_rw_unlock(rw_lock);
                    CHECK_I(result, "H5TS_rw_unlock -- 1");

                    rec_lock_depth--;
                    udata->read_locks_released++;
                }
                else {

                    result = H5TS_rw_rdlock(rw_lock);
                    CHECK_I(result, "H5TS_rw_rdlock -- 2");

                    rec_lock_depth++;
                    udata->read_locks_granted++;
                }
            }

            udata->real_read_locks_released++;
        }
        else {

            result = H5TS_rw_wrlock(rw_lock);
            CHECK_I(result, "H5TS_rw_wrlock -- 1");

            udata->write_locks_granted++;
            udata->real_write_locks_granted++;
            wr_locks_remaining--;
            rec_lock_depth = 1;

            while (rec_lock_depth > 0) {

                if ((rec_lock_depth >= max_rec_lock_depth) || ((HDrand() % 2) == 0)) {

                    result = H5TS_rw_unlock(rw_lock);
                    CHECK_I(result, "H5TS_rw_unlock -- 2");

                    rec_lock_depth--;
                    udata->write_locks_released++;
                }
                else {

                    result = H5TS_rw_wrlock(rw_lock);
                    CHECK_I(result, "H5TS_rw_wrlock -- 2");

                    rec_lock_depth++;
                    udata->write_locks_granted++;
                }
            }

            udata->real_write_locks_released++;
        }
    }

    return NULL;

} /* end tts_rw_lock_smoke_check_test_thread() */

/*
 **********************************************************************
 * tts_rec_rw_lock_smoke_check_2 -- mob of readers
 *
 * Multi-thread test to check management of multiple readers ONLY by
 * the recursive R/W lock.  Test proceeds as follows:
 *
 *  1) Initialize an instance of the recursive R/W lock.
 *
 *  2) Setup the user data to be passed to each reader test thread.
 *
 *  3) Create the reader threads, each with its own user data.
 *     Activities of the reader threads is discussed in the header
 *     comment to tts_rw_lock_smoke_check_test_thread().
 *
 *  4) Wait for all threads to complete.
 *
 *  5) Examine the user data from the threads, to determine the
 *     total number of real and recursive read locks and un-lock.
 *
 *  6) Obtain the stats from the recursive R/W lock, and compare
 *     with the data gathered above.
 *
 *  7) Shut down the recursive R/W lock.
 *
 * The reader threads obtain and drop the read lock a specified
 * number of times.  Once a reader has a read lock, it does random
 * recursive read locks / unlocks until drops the read lock, and then
 * repeats the process until the specified number of read locks have
 * been acquired and dropped.
 *
 * Created Sept. 3. 2020.
 *
 * Programmer: John Mainzer
 *
 **********************************************************************
 */

void
tts_rec_rw_lock_smoke_check_2(void)
{
    hbool_t                          verbose = FALSE;
    herr_t                           result;
    int                              express_test;
    int                              i;
    int                              num_threads                 = MAX_NUM_THREADS;
    int                              lock_cycles                 = MAX_LOCK_CYCLES;
    int32_t                          total_target_rd_lock_cycles = 0;
    int32_t                          total_target_wr_lock_cycles = 0;
    H5TS_thread_t                    threads[MAX_NUM_THREADS];
    struct rec_rw_lock_test_udata_t *udata = NULL;
    struct H5TS_rw_lock_stats_t      stats;
    struct H5TS_rw_lock_stats_t      expected = {/* Initialize all fields to zero -- we will construct
                                             * the expected stats from the thread udata after
                                             * completion.
                                             */
                                            /* read_locks_granted             = */ 0,
                                            /* read_locks_released            = */ 0,
                                            /* real_read_locks_granted        = */ 0,
                                            /* real_read_locks_released       = */ 0,
                                            /* max_read_locks                 = */ 0,
                                            /* max_read_lock_recursion_depth  = */ 0,
                                            /* read_locks_delayed             = */ 0,
                                            /* max_read_locks_pending         = */ 0,
                                            /* write_locks_granted            = */ 0,
                                            /* write_locks_released           = */ 0,
                                            /* real_write_locks_granted       = */ 0,
                                            /* real_write_locks_released      = */ 0,
                                            /* max_write_locks                = */ 0,
                                            /* max_write_lock_recursion_depth = */ 0,
                                            /* write_locks_delayed            = */ 0,
                                            /* max_write_locks_pending        = */ 0};
    struct H5TS_rw_lock_t rec_rw_lock;

    /* Allocate the udata */
    udata = HDmalloc(sizeof(*udata) * MAX_NUM_THREADS);

    if (udata == NULL) {

        TestErrPrintf("thread udata allocation failed.\n");

        /* We can't do anything without the udata, so just return */
        return;
    }

    express_test = GetTestExpress();

    if (express_test >= 1) {

        num_threads /= 2;
        lock_cycles /= 10;
    }

    if (express_test >= 2) {

        num_threads /= 2;
        lock_cycles /= 10;
    }

    if (express_test >= 3) {

        num_threads /= 2;
        lock_cycles /= 10;
    }

    /* 1) Initialize an instance of the recursive R/W lock. */
    result = H5TS_rw_lock_init(&rec_rw_lock, H5TS_RW_LOCK_POLICY_FAVOR_WRITERS);
    CHECK_I(result, "H5TS_rw_lock_init");

    /* 2) Setup the user data to be passed to each reader test thread. */
    for (i = 0; i < MAX_NUM_THREADS; i++) {

        udata[i].rw_lock                   = &rec_rw_lock;
        udata[i].id                        = i;
        udata[i].target_rd_lock_cycles     = lock_cycles;
        udata[i].target_wr_lock_cycles     = 0;
        udata[i].max_recursive_lock_depth  = 10;
        udata[i].read_locks_granted        = 0;
        udata[i].read_locks_released       = 0;
        udata[i].real_read_locks_granted   = 0;
        udata[i].real_read_locks_released  = 0;
        udata[i].write_locks_granted       = 0;
        udata[i].write_locks_released      = 0;
        udata[i].real_write_locks_granted  = 0;
        udata[i].real_write_locks_released = 0;
    }

    /* 3) Create the reader threads, each with its own user data. */
    for (i = 0; i < num_threads; i++) {

        threads[i] = H5TS_create_thread(tts_rw_lock_smoke_check_test_thread, NULL, &(udata[i]));
    }

    /* 4) Wait for all threads to complete. */
    for (i = 0; i < num_threads; i++) {

        H5TS_wait_for_thread(threads[i]);
    }

    /* 5) Examine the user data from the threads, to determine the
     *    total number of real and recursive read locks and un-lock.
     *
     *    First, tally up the lock entries and exits from the test threads,
     *    and store this data in the expected recursive R/W/ lock stats..
     *    In passing, verify that each thread has done the expected number
     *    of locks and unlocks.  Do these as asserts -- will run checks on
     *    aggregate data shortly.
     */

    for (i = 0; i < num_threads; i++) {

        HDassert(udata[i].id == i);
        HDassert(udata[i].target_rd_lock_cycles == udata[i].real_read_locks_granted);
        HDassert(udata[i].target_rd_lock_cycles == udata[i].real_read_locks_released);
        HDassert(udata[i].target_wr_lock_cycles == udata[i].real_write_locks_granted);
        HDassert(udata[i].target_wr_lock_cycles == udata[i].real_write_locks_released);

        total_target_rd_lock_cycles += udata[i].target_rd_lock_cycles;
        total_target_wr_lock_cycles += udata[i].target_wr_lock_cycles;

        expected.read_locks_granted += udata[i].read_locks_granted;
        expected.read_locks_released += udata[i].read_locks_released;
        expected.real_read_locks_granted += udata[i].real_read_locks_granted;
        expected.real_read_locks_released += udata[i].real_read_locks_released;
        expected.write_locks_granted += udata[i].write_locks_granted;
        expected.write_locks_released += udata[i].write_locks_released;
        expected.real_write_locks_granted += udata[i].real_write_locks_granted;
        expected.real_write_locks_released += udata[i].real_write_locks_released;
    }

    /* Verify that the threads executed the expected number of read and write
     * lock cycles.  If they didn't, some thread probably encountered an error
     * and exited early.
     */
    if ((total_target_rd_lock_cycles != expected.real_read_locks_granted) ||
        (total_target_rd_lock_cycles != expected.real_read_locks_released) ||
        (total_target_wr_lock_cycles != expected.real_write_locks_granted) ||
        (total_target_wr_lock_cycles != expected.real_write_locks_released)) {

        TestErrPrintf("Threads reported unexpected number of locks/unlocks.\n");
    }

    /* initialize remaining non-zero fields in the expected stats */
    expected.max_read_locks                = num_threads;
    expected.max_read_lock_recursion_depth = 10;

    /* 6) Obtain the stats from the recursive R/W lock, and compare
     *     with the data gathered above.
     */

    result = H5TS_rw_lock_get_stats(&rec_rw_lock, &stats);
    CHECK_I(result, "H5TS_rw_lock_get_stats -- 1");

    /* turn off clang-format for readability */
    /* clang-format off */
    if ((stats.read_locks_granted              != expected.read_locks_granted) ||
        (stats.read_locks_released             != expected.read_locks_released) ||
        (stats.real_read_locks_granted         != expected.real_read_locks_granted) ||
        (stats.real_read_locks_released        != expected.real_read_locks_released) ||
        (stats.max_read_locks                   > expected.max_read_locks) || 
        (stats.max_read_locks                   < 1) ||
        (stats.max_read_lock_recursion_depth    > expected.max_read_lock_recursion_depth) ||
        (stats.max_read_lock_recursion_depth    < 1) ||
        (stats.read_locks_delayed              != expected.read_locks_delayed) ||
        (stats.max_read_locks_pending          != expected.max_read_locks_pending) ||
        (stats.write_locks_granted             != expected.write_locks_granted) ||
        (stats.write_locks_released            != expected.write_locks_released) ||
        (stats.real_write_locks_granted        != expected.real_write_locks_granted) ||
        (stats.real_write_locks_released       != expected.real_write_locks_released) ||
        (stats.max_write_locks                 != expected.max_write_locks) ||
        (stats.max_write_lock_recursion_depth  != expected.max_write_lock_recursion_depth) ||
        (stats.write_locks_delayed             != expected.write_locks_delayed) ||
        (stats.max_write_locks_pending         != expected.max_write_locks_pending)) {

        TestErrPrintf("Unexpected recursive R/W lock stats -- 1");
        H5TS_rw_lock_print_stats("Actual stats", &stats);
        H5TS_rw_lock_print_stats("Expected stats", &expected);
    }
    /* clang-format on */

    if (verbose) {

        H5TS_rw_lock_print_stats("mob of readers stats", &stats);
    }

    /* 7) Shut down the recursive R/W lock. */
    result = H5TS_rw_lock_destroy(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_lock_destroy");

    /* discard the udata if it exists */
    if (udata) {

        HDfree(udata);
    }

    return;

} /* end tts_rec_rw_lock_smoke_check_2() */

void
cleanup_rec_rw_lock_smoke_check_2(void)
{
    /* nothing to do */
    return;
}

/*
 **********************************************************************
 * tts_rec_rw_lock_smoke_check_3 -- mob of writers
 *
 * Multi-thread test to check management of multiple writers ONLY by
 * the recursive R/W lock.  Test proceeds as follows:
 *
 *  1) Initialize an instance of the recursive R/W lock.
 *
 *  2) Setup the user data to be passed to each writer test thread.
 *
 *  3) Create the writer threads, each with its own user data.
 *     Activities of the writer threads is discussed in the header
 *     comment to tts_rw_lock_smoke_check_test_thread().
 *
 *  4) Wait for all threads to complete.
 *
 *  5) Examine the user data from the threads, to determine the
 *     total number of real and recursive read locks and un-lock.
 *
 *  6) Obtain the stats from the recursive R/W lock, and compare
 *     with the data gathered above.
 *
 *  7) Shut down the recursive R/W lock.
 *
 * The writer threads obtain and drop the read lock a specified
 * number of times.  Once a writeer has a write lock, it does random
 * recursive write locks / unlocks until drops the write lock, and then
 * repeats the process until the specified number of write locks have
 * been acquired and dropped.
 *
 * Created Sept. 3. 2020.
 *
 * Programmer: John Mainzer
 *
 **********************************************************************
 */

void
tts_rec_rw_lock_smoke_check_3(void)
{
    hbool_t                          verbose = FALSE;
    herr_t                           result;
    int                              i;
    int                              express_test;
    int                              num_threads                 = MAX_NUM_THREADS;
    int                              lock_cycles                 = MAX_LOCK_CYCLES;
    int32_t                          total_target_rd_lock_cycles = 0;
    int32_t                          total_target_wr_lock_cycles = 0;
    H5TS_thread_t                    threads[MAX_NUM_THREADS];
    struct rec_rw_lock_test_udata_t *udata = NULL;
    struct H5TS_rw_lock_stats_t      stats;
    struct H5TS_rw_lock_stats_t      expected = {/* Initialize all fields to zero -- we will construct
                                             * the expected stats from the thread udata after
                                             * completion.
                                             */
                                            /* read_locks_granted             = */ 0,
                                            /* read_locks_released            = */ 0,
                                            /* real_read_locks_granted        = */ 0,
                                            /* real_read_locks_released       = */ 0,
                                            /* max_read_locks                 = */ 0,
                                            /* max_read_lock_recursion_depth  = */ 0,
                                            /* read_locks_delayed             = */ 0,
                                            /* max_read_locks_pending         = */ 0,
                                            /* write_locks_granted            = */ 0,
                                            /* write_locks_released           = */ 0,
                                            /* real_write_locks_granted       = */ 0,
                                            /* real_write_locks_released      = */ 0,
                                            /* max_write_locks                = */ 0,
                                            /* max_write_lock_recursion_depth = */ 0,
                                            /* write_locks_delayed            = */ 0,
                                            /* max_write_locks_pending        = */ 0};
    struct H5TS_rw_lock_t rec_rw_lock;

    /* Allocate the udata */
    udata = HDmalloc(sizeof(*udata) * MAX_NUM_THREADS);

    if (udata == NULL) {

        TestErrPrintf("thread udata allocation failed.\n");

        /* We can't do anything without the udata, so just return */
        return;
    }

    express_test = GetTestExpress();

    if (express_test >= 1) {

        num_threads /= 2;
        lock_cycles /= 10;
    }

    if (express_test >= 2) {

        num_threads /= 2;
        lock_cycles /= 10;
    }

    if (express_test >= 3) {

        num_threads /= 2;
        lock_cycles /= 10;
    }

    /* 1) Initialize an instance of the recursive R/W lock. */
    result = H5TS_rw_lock_init(&rec_rw_lock, H5TS_RW_LOCK_POLICY_FAVOR_WRITERS);
    CHECK_I(result, "H5TS_rw_lock_init");

    /* 2) Setup the user data to be passed to each writer test thread. */
    for (i = 0; i < MAX_NUM_THREADS; i++) {

        udata[i].rw_lock                   = &rec_rw_lock;
        udata[i].id                        = i;
        udata[i].target_rd_lock_cycles     = 0;
        udata[i].target_wr_lock_cycles     = lock_cycles;
        udata[i].max_recursive_lock_depth  = 10;
        udata[i].read_locks_granted        = 0;
        udata[i].read_locks_released       = 0;
        udata[i].real_read_locks_granted   = 0;
        udata[i].real_read_locks_released  = 0;
        udata[i].write_locks_granted       = 0;
        udata[i].write_locks_released      = 0;
        udata[i].real_write_locks_granted  = 0;
        udata[i].real_write_locks_released = 0;
    }

    /* 3) Create the writer threads, each with its own user data. */
    for (i = 0; i < num_threads; i++) {

        threads[i] = H5TS_create_thread(tts_rw_lock_smoke_check_test_thread, NULL, &(udata[i]));
    }

    /* 4) Wait for all threads to complete. */
    for (i = 0; i < num_threads; i++) {

        H5TS_wait_for_thread(threads[i]);
    }

    /* 5) Examine the user data from the threads, to determine the
     *    total number of real and recursive read locks and un-lock.
     *
     *    First, tally up the lock entries and exits from the test threads,
     *    and store this data in the expected recursive R/W/ lock stats..
     *    In passing, verify that each thread has done the expected number
     *    of locks and unlocks.  Do these as asserts -- will run checks on
     *    aggregate data shortly.
     */

    for (i = 0; i < num_threads; i++) {

        HDassert(udata[i].id == i);
        HDassert(udata[i].target_rd_lock_cycles == udata[i].real_read_locks_granted);
        HDassert(udata[i].target_rd_lock_cycles == udata[i].real_read_locks_released);
        HDassert(udata[i].target_wr_lock_cycles == udata[i].real_write_locks_granted);
        HDassert(udata[i].target_wr_lock_cycles == udata[i].real_write_locks_released);

        total_target_rd_lock_cycles += udata[i].target_rd_lock_cycles;
        total_target_wr_lock_cycles += udata[i].target_wr_lock_cycles;

        expected.read_locks_granted += udata[i].read_locks_granted;
        expected.read_locks_released += udata[i].read_locks_released;
        expected.real_read_locks_granted += udata[i].real_read_locks_granted;
        expected.real_read_locks_released += udata[i].real_read_locks_released;
        expected.write_locks_granted += udata[i].write_locks_granted;
        expected.write_locks_released += udata[i].write_locks_released;
        expected.real_write_locks_granted += udata[i].real_write_locks_granted;
        expected.real_write_locks_released += udata[i].real_write_locks_released;
    }

    /* Verify that the threads executed the expected number of read and write
     * lock cycles.  If they didn't, some thread probably encountered an error
     * and exited early.
     */
    if ((total_target_rd_lock_cycles != expected.real_read_locks_granted) ||
        (total_target_rd_lock_cycles != expected.real_read_locks_released) ||
        (total_target_wr_lock_cycles != expected.real_write_locks_granted) ||
        (total_target_wr_lock_cycles != expected.real_write_locks_released)) {

        TestErrPrintf("Threads reported unexpected number of locks/unlocks.\n");
    }

    /* initialize remaining non-zero fields in the expected stats */
    expected.max_write_locks                = 1;
    expected.max_write_lock_recursion_depth = 10;
    expected.max_write_locks_pending        = num_threads - 1;

    /* 6) Obtain the stats from the recursive R/W lock, and compare
     *     with the data gathered above.
     */
    result = H5TS_rw_lock_get_stats(&rec_rw_lock, &stats);
    CHECK_I(result, "H5TS_rw_lock_get_stats -- 1");

    /* turn off clang-format for readability */
    /* clang-format off */
    if ((stats.read_locks_granted             != expected.read_locks_granted) ||
        (stats.read_locks_released            != expected.read_locks_released) ||
        (stats.real_read_locks_granted        != expected.real_read_locks_granted) ||
        (stats.real_read_locks_released       != expected.real_read_locks_released) ||
        (stats.max_read_locks                 != expected.max_read_locks) ||
        (stats.max_read_lock_recursion_depth  != expected.max_read_lock_recursion_depth) ||
        (stats.read_locks_delayed             != expected.read_locks_delayed) ||
        (stats.max_read_locks_pending         != expected.max_read_locks_pending) ||
        (stats.write_locks_granted            != expected.write_locks_granted) ||
        (stats.write_locks_released           != expected.write_locks_released) ||
        (stats.real_write_locks_granted       != expected.real_write_locks_granted) ||
        (stats.real_write_locks_released      != expected.real_write_locks_released) ||
        (stats.max_write_locks                != expected.max_write_locks) ||
        (stats.max_write_lock_recursion_depth  > expected.max_write_lock_recursion_depth) ||
        (stats.max_write_lock_recursion_depth  < 1) ||
        (stats.write_locks_delayed             < expected.write_locks_delayed) ||
        (stats.max_write_locks_pending         > expected.max_write_locks_pending)) {

        TestErrPrintf("Unexpected recursive R/W lock stats -- 1");
        H5TS_rw_lock_print_stats("Actual stats", &stats);
        H5TS_rw_lock_print_stats("Expected stats", &expected);
    }
    /* clang-format on */

    if (verbose) {

        H5TS_rw_lock_print_stats("Actual stats", &stats);
    }

    /* 7) Shut down the recursive R/W lock. */
    result = H5TS_rw_lock_destroy(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_lock_destroy");

    /* discard the udata if it exists */
    if (udata) {

        HDfree(udata);
    }

    return;

} /* end tts_rec_rw_lock_smoke_check_3() */

void
cleanup_rec_rw_lock_smoke_check_3(void)
{
    /* nothing to do */
    return;
}

/*
 **********************************************************************
 * tts_rec_rw_lock_smoke_check_4 -- mixed mob
 *
 * Multi-thread test to check management of multiple readers and
 * writers by the recursive R/W lock.  Test proceeds as follows:
 *
 *  1) Initialize an instance of the recursive R/W lock.
 *
 *  2) Setup the user data to be passed to each writer test thread.
 *
 *  3) Create the reader / writer threads, each with its own user data.
 *     Activities of the reader / writer threads is discussed in the
 *     header comment to tts_rw_lock_smoke_check_test_thread().
 *
 *  4) Wait for all threads to complete.
 *
 *  5) Examine the user data from the threads, to determine the
 *     total number of real and recursive read & write locks and
 *     un-lock.
 *
 *  6) Obtain the stats from the recursive R/W lock, and compare
 *     with the data gathered above.
 *
 *  7) Shut down the recursive R/W lock.
 *
 * The reader / writer threads obtain and drop the read or write
 * locks a specified number of times.  Once a thread has a lock, it
 * does random recursive locks / unlocks until drops the lock, and then
 * repeats the process until the specified number of locks have
 * been acquired and dropped.
 *
 * Created Sept. 3. 2020.
 *
 * Programmer: John Mainzer
 *
 **********************************************************************
 */

void
tts_rec_rw_lock_smoke_check_4(void)
{
    hbool_t                          verbose = FALSE;
    herr_t                           result;
    int                              i;
    int                              express_test;
    int                              num_threads                 = MAX_NUM_THREADS;
    int                              lock_cycles                 = MAX_LOCK_CYCLES;
    int32_t                          total_target_rd_lock_cycles = 0;
    int32_t                          total_target_wr_lock_cycles = 0;
    H5TS_thread_t                    threads[MAX_NUM_THREADS];
    struct rec_rw_lock_test_udata_t *udata = NULL;
    struct H5TS_rw_lock_stats_t      stats;
    struct H5TS_rw_lock_stats_t      expected = {/* Initialize all fields to zero -- we will construct
                                             * the expected stats from the thread udata after
                                             * completion.
                                             */
                                            /* read_locks_granted             = */ 0,
                                            /* read_locks_released            = */ 0,
                                            /* real_read_locks_granted        = */ 0,
                                            /* real_read_locks_released       = */ 0,
                                            /* max_read_locks                 = */ 0,
                                            /* max_read_lock_recursion_depth  = */ 0,
                                            /* read_locks_delayed             = */ 0,
                                            /* max_read_locks_pending         = */ 0,
                                            /* write_locks_granted            = */ 0,
                                            /* write_locks_released           = */ 0,
                                            /* real_write_locks_granted       = */ 0,
                                            /* real_write_locks_released      = */ 0,
                                            /* max_write_locks                = */ 0,
                                            /* max_write_lock_recursion_depth = */ 0,
                                            /* write_locks_delayed            = */ 0,
                                            /* max_write_locks_pending        = */ 0};
    struct H5TS_rw_lock_t rec_rw_lock;

    /* Allocate the udata */
    udata = HDmalloc(sizeof(*udata) * MAX_NUM_THREADS);

    if (udata == NULL) {

        TestErrPrintf("thread udata allocation failed.\n");

        /* We can't do anything without the udata, so just return */
        return;
    }

    express_test = GetTestExpress();

    if (express_test >= 1) {

        lock_cycles /= 10;
    }

    if (express_test >= 2) {

        num_threads /= 2;
        lock_cycles /= 10;
    }

    if (express_test >= 3) {

        num_threads /= 2;
        lock_cycles /= 10;
    }

    /* 1) Initialize an instance of the recursive R/W lock. */
    result = H5TS_rw_lock_init(&rec_rw_lock, H5TS_RW_LOCK_POLICY_FAVOR_WRITERS);
    CHECK_I(result, "H5TS_rw_lock_init");

    /* 2) Setup the user data to be passed to each writer test thread. */
    for (i = 0; i < MAX_NUM_THREADS; i++) {

        udata[i].rw_lock                   = &rec_rw_lock;
        udata[i].id                        = i;
        udata[i].target_rd_lock_cycles     = lock_cycles;
        udata[i].target_wr_lock_cycles     = lock_cycles;
        udata[i].max_recursive_lock_depth  = 10;
        udata[i].read_locks_granted        = 0;
        udata[i].read_locks_released       = 0;
        udata[i].real_read_locks_granted   = 0;
        udata[i].real_read_locks_released  = 0;
        udata[i].write_locks_granted       = 0;
        udata[i].write_locks_released      = 0;
        udata[i].real_write_locks_granted  = 0;
        udata[i].real_write_locks_released = 0;
    }

    /* 3) Create the reader threads, each with its own user data. */
    for (i = 0; i < num_threads; i++) {

        threads[i] = H5TS_create_thread(tts_rw_lock_smoke_check_test_thread, NULL, &(udata[i]));
    }

    /* 4) Wait for all threads to complete. */
    for (i = 0; i < num_threads; i++) {

        H5TS_wait_for_thread(threads[i]);
    }

    /* 5) Examine the user data from the threads, to determine the
     *    total number of real and recursive read locks and un-lock.
     *
     *    First, tally up the lock entries and exits from the test threads,
     *    and store this data in the expected recursive R/W/ lock stats..
     *    In passing, verify that each thread has done the expected number
     *    of locks and unlocks.  Do these as asserts -- will run checks on
     *    aggregate data shortly.
     */

    for (i = 0; i < num_threads; i++) {

        HDassert(udata[i].id == i);
        HDassert(udata[i].target_rd_lock_cycles == udata[i].real_read_locks_granted);
        HDassert(udata[i].target_rd_lock_cycles == udata[i].real_read_locks_released);
        HDassert(udata[i].target_wr_lock_cycles == udata[i].real_write_locks_granted);
        HDassert(udata[i].target_wr_lock_cycles == udata[i].real_write_locks_released);

        total_target_rd_lock_cycles += udata[i].target_rd_lock_cycles;
        total_target_wr_lock_cycles += udata[i].target_wr_lock_cycles;

        expected.read_locks_granted += udata[i].read_locks_granted;
        expected.read_locks_released += udata[i].read_locks_released;
        expected.real_read_locks_granted += udata[i].real_read_locks_granted;
        expected.real_read_locks_released += udata[i].real_read_locks_released;
        expected.write_locks_granted += udata[i].write_locks_granted;
        expected.write_locks_released += udata[i].write_locks_released;
        expected.real_write_locks_granted += udata[i].real_write_locks_granted;
        expected.real_write_locks_released += udata[i].real_write_locks_released;
    }

    /* Verify that the threads executed the expected number of read and write
     * lock cycles.  If they didn't, some thread probably encountered an error
     * and exited early.
     */
    if ((total_target_rd_lock_cycles != expected.real_read_locks_granted) ||
        (total_target_rd_lock_cycles != expected.real_read_locks_released) ||
        (total_target_wr_lock_cycles != expected.real_write_locks_granted) ||
        (total_target_wr_lock_cycles != expected.real_write_locks_released)) {

        TestErrPrintf("Threads reported unexpected number of locks/unlocks.\n");
    }

    /* initialize remaining non-zero fields in the expected stats */
    expected.max_read_locks                 = num_threads;
    expected.max_read_lock_recursion_depth  = 10;
    expected.max_read_locks_pending         = num_threads - 1;
    expected.max_write_locks                = 1;
    expected.max_write_lock_recursion_depth = 10;
    expected.max_write_locks_pending        = num_threads - 1;

    /* 6) Obtain the stats from the recursive R/W lock, and compare
     *     with the data gathered above.
     */
    result = H5TS_rw_lock_get_stats(&rec_rw_lock, &stats);
    CHECK_I(result, "H5TS_rw_lock_get_stats -- 1");

    /* turn off clang-format for readability */
    /* clang-format off */
    if ((stats.read_locks_granted             != expected.read_locks_granted) ||
        (stats.read_locks_released            != expected.read_locks_released) ||
        (stats.real_read_locks_granted        != expected.real_read_locks_granted) ||
        (stats.real_read_locks_released       != expected.real_read_locks_released) ||
        (stats.max_read_locks                  > expected.max_read_locks) || 
        (stats.max_read_locks                  < 1) ||
        (stats.max_read_lock_recursion_depth   > expected.max_read_lock_recursion_depth) ||
        (stats.read_locks_delayed              < expected.read_locks_delayed) ||
        (stats.max_read_locks_pending          > expected.max_read_locks_pending) ||
        (stats.write_locks_granted            != expected.write_locks_granted) ||
        (stats.write_locks_released           != expected.write_locks_released) ||
        (stats.real_write_locks_granted       != expected.real_write_locks_granted) ||
        (stats.real_write_locks_released      != expected.real_write_locks_released) ||
        (stats.max_write_locks                != expected.max_write_locks) ||
        (stats.max_write_lock_recursion_depth  > expected.max_write_lock_recursion_depth) ||
        (stats.max_write_lock_recursion_depth  < 1) ||
        (stats.write_locks_delayed             < expected.write_locks_delayed) ||
        (stats.max_write_locks_pending         > expected.max_write_locks_pending)) {

        TestErrPrintf("Unexpected recursive R/W lock stats -- 1");
        H5TS_rw_lock_print_stats("Actual stats", &stats);
        H5TS_rw_lock_print_stats("Expected stats", &expected);
    }
    /* clang-format on */

    if (verbose) {

        H5TS_rw_lock_print_stats("Actual stats", &stats);
    }

    /* 7) Shut down the recursive R/W lock. */
    result = H5TS_rw_lock_destroy(&rec_rw_lock);
    CHECK_I(result, "H5TS_rw_lock_destroy");

    /* discard the udata if it exists */
    if (udata) {

        HDfree(udata);
    }

    return;

} /* end tts_rec_rw_lock_smoke_check_4() */

void
cleanup_rec_rw_lock_smoke_check_4(void)
{
    /* nothing to do */
    return;
}

#endif /* H5_USE_RECURSIVE_WRITER_LOCKS */

#endif /*H5_HAVE_THREADSAFE*/