Skip to content

File unittests_lf.c

File List > cubrid > src > executables > unittests_lf.c

Go to the documentation of this file

/*
 * Copyright 2008 Search Solution Corporation
 * Copyright 2016 CUBRID Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

/*
 * unittest_lf.c : unit tests for latch free primitives
 */

#include "porting.h"
#include "lock_free.h"
#include <stdio.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#include <assert.h>

#define strlen(s1) ((int) strlen(s1))

/* wait-free random number array */
#define RAND_BLOCKS 64
#define RAND_BLOCK_SIZE 1000000
#define RAND_SIZE   RAND_BLOCKS * RAND_BLOCK_SIZE
static int random_numbers[RAND_SIZE];

#define PTHREAD_ABORT_AND_EXIT(code) \
  do \
    { \
      int rc = (code); \
      abort (); \
      pthread_exit (&rc); \
    } \
  while (0)

static void
generate_random ()
{
  int i = 0;

  srand (time (NULL));

  for (i = 0; i < RAND_SIZE; i++)
    {
      random_numbers[i] = rand ();
    }
}

/* hash entry type definition */
typedef struct xentry XENTRY;
struct xentry
{
  XENTRY *next;         /* used by hash and freelist */
  XENTRY *stack;        /* used by freelist */
  UINT64 del_tran_id;       /* used by freelist */

  pthread_mutex_t mutex;    /* entry mutex (where applicable) */

  int key;
  unsigned long long int data;
};

// *INDENT-OFF*
using my_hashmap = lf_hash_table_cpp<int, xentry>;
using my_hashmap_iterator = my_hashmap::iterator;
// *INDENT-ON*

/* entry manipulation functions */
static void *
xentry_alloc ()
{
  XENTRY *ptr = (XENTRY *) malloc (sizeof (XENTRY));
  if (ptr != NULL)
    {
      pthread_mutex_init (&ptr->mutex, NULL);
      ptr->data = 0;
    }
  return ptr;
}

static int
xentry_free (void *entry)
{
  pthread_mutex_destroy (&((XENTRY *) entry)->mutex);
  free (entry);
  return NO_ERROR;
}

static int
xentry_init (void *entry)
{
  if (entry != NULL)
    {
      ((XENTRY *) entry)->data = 0;
      return NO_ERROR;
    }
  else
    {
      return ER_FAILED;
    }
}

static int
xentry_uninit (void *entry)
{
  if (entry != NULL)
    {
      ((XENTRY *) entry)->data = -1;
      return NO_ERROR;
    }
  else
    {
      return ER_FAILED;
    }

}

static unsigned int
xentry_hash (void *key, int htsize)
{
  int *ikey = (int *) key;
  return (*ikey) % htsize;
}

static int
xentry_key_compare (void *k1, void *k2)
{
  int *ik1 = (int *) k1, *ik2 = (int *) k2;
  return !(*ik1 == *ik2);
}

static int
xentry_key_copy (void *src, void *dest)
{
  int *isrc = (int *) src, *idest = (int *) dest;

  *idest = *isrc;

  return NO_ERROR;
}

/* hash entry descriptors */
static LF_ENTRY_DESCRIPTOR xentry_desc = {
  /* signature */
  offsetof (XENTRY, stack),
  offsetof (XENTRY, next),
  offsetof (XENTRY, del_tran_id),
  offsetof (XENTRY, key),
  offsetof (XENTRY, mutex),

  /* mutex flags */
  LF_EM_NOT_USING_MUTEX,
  LF_ENTRY_DESCRIPTOR_MAX_ALLOC,

  /* functions */
  xentry_alloc,
  xentry_free,
  xentry_init,
  xentry_uninit,
  xentry_key_copy,
  xentry_key_compare,
  xentry_hash,
  NULL
};

/* print function */
static struct timeval start_time;

static void
begin (const char *test_name)
{
#define MSG_LEN   40
  int i;

  printf ("Testing %s", test_name);
  for (i = 0; i < MSG_LEN - strlen (test_name); i++)
    {
      putchar (' ');
    }
  printf ("...");

  gettimeofday (&start_time, NULL);

#undef MSG_LEN
}

static int
fail (const char *message)
{
  printf (" %s: %s\n", "FAILED", message);
  abort ();
  return ER_FAILED;
}

static int
success ()
{
  struct timeval end_time;
  long long int elapsed_msec = 0;

  gettimeofday (&end_time, NULL);

  elapsed_msec = (end_time.tv_usec - start_time.tv_usec) / 1000;
  elapsed_msec += (end_time.tv_sec - start_time.tv_sec) * 1000;

  printf (" %s [%9.3f sec]\n", "OK", (float) elapsed_msec / 1000.0f);
  return NO_ERROR;
}

/* thread entry functions */
void *test_freelist_proc (void *param);

void *
test_freelist_proc (void *param)
{
#define NOPS      1000000   /* 1M */

  LF_FREELIST *freelist = (LF_FREELIST *) param;
  LF_TRAN_SYSTEM *ts = freelist->tran_system;
  LF_TRAN_ENTRY *te;
  XENTRY *entry = NULL;
  int i;

  te = lf_tran_request_entry (ts);
  if (te == NULL)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }

  for (i = 0; i < NOPS; i++)
    {
      lf_tran_start_with_mb (te, true);

      if (i % 2 == 0)
    {
      entry = (XENTRY *) lf_freelist_claim (te, freelist);
      if (entry == NULL)
        {
          PTHREAD_ABORT_AND_EXIT (ER_FAILED);
        }
    }
      else
    {
      if (lf_freelist_retire (te, freelist, (void *) entry) != NO_ERROR)
        {
          PTHREAD_ABORT_AND_EXIT (ER_FAILED);
        }
    }

      lf_tran_end_with_mb (te);
    }

  lf_tran_return_entry (te);

  pthread_exit (NO_ERROR);

#undef NOPS
}

void *test_freelist_proc_local_tran (void *param);

void *
test_freelist_proc_local_tran (void *param)
{
#define NOPS      1000000   /* 1M */

  LF_FREELIST *freelist = (LF_FREELIST *) param;
  LF_TRAN_SYSTEM *ts = freelist->tran_system;
  LF_TRAN_ENTRY *te;
  XENTRY *entry = NULL;
  int i;

  te = lf_tran_request_entry (ts);
  if (te == NULL)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }

  for (i = 0; i < NOPS; i++)
    {
      /* Test freelist without transaction */
      if (i % 2 == 0)
    {
      entry = (XENTRY *) lf_freelist_claim (te, freelist);
      if (entry == NULL)
        {
          PTHREAD_ABORT_AND_EXIT (ER_FAILED);
        }
    }
      else
    {
      if (lf_freelist_retire (te, freelist, (void *) entry) != NO_ERROR)
        {
          PTHREAD_ABORT_AND_EXIT (ER_FAILED);
        }
    }
    }

  lf_tran_return_entry (te);

  pthread_exit (NO_ERROR);

#undef NOPS
}

void *test_hash_proc_1 (void *param);

void *
test_hash_proc_1 (void *param)
{
#define NOPS  1000000

  my_hashmap *hashmap = (my_hashmap *) param;
  LF_HASH_TABLE *hash = &hashmap->get_hash_table ();
  LF_TRAN_SYSTEM *ts = hash->freelist->tran_system;
  LF_TRAN_ENTRY *te;
  XENTRY *entry;
  int i, rand_base, key;

  te = lf_tran_request_entry (ts);
  if (te == NULL)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }

  if (te->entry_idx >= RAND_BLOCKS || te->entry_idx < 0)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }
  else
    {
      rand_base = te->entry_idx * RAND_BLOCK_SIZE;
    }

  for (i = 0; i < NOPS; i++)
    {
      key = random_numbers[rand_base + i] % 1000;

      if (i % 10 < 5)
    {
      entry = NULL;
      (void) hashmap->find_or_insert (te, key, entry);
      hashmap->unlock (te, entry);
    }
      else
    {
      (void) hashmap->erase (te, key);
    }
    }

  lf_tran_return_entry (te);

  pthread_exit (NO_ERROR);

#undef NOPS
}

void *test_hash_proc_2 (void *param);

void *
test_hash_proc_2 (void *param)
{
#define NOPS  1000000

  my_hashmap *hashmap = (my_hashmap *) param;
  LF_HASH_TABLE *hash = &hashmap->get_hash_table ();
  LF_TRAN_SYSTEM *ts = hash->freelist->tran_system;
  LF_TRAN_ENTRY *te;
  XENTRY *entry;
  int i, rand_base, key;

  te = lf_tran_request_entry (ts);
  if (te == NULL)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }

  if (te->entry_idx >= RAND_BLOCKS || te->entry_idx < 0)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }
  else
    {
      rand_base = te->entry_idx * RAND_BLOCK_SIZE;
    }

  for (i = 0; i < NOPS; i++)
    {
      key = random_numbers[rand_base + i] % 1000;

      if (i % 10 < 5)
    {
      (void) hashmap->find_or_insert (te, key, entry);
      if (entry == NULL)
        {
          PTHREAD_ABORT_AND_EXIT (ER_FAILED);
        }

      if (te->locked_mutex != &entry->mutex)
        {
          abort ();
        }
      te->locked_mutex = NULL;
      hashmap->unlock (te, entry);
    }
      else
    {
      (void) hashmap->erase (te, key);
    }

      assert (te->locked_mutex == NULL);
    }

  lf_tran_return_entry (te);

  pthread_exit (NO_ERROR);

#undef NOPS
}

static int del_op_count = -1;

void *test_hash_proc_3 (void *param);

void *
test_hash_proc_3 (void *param)
{
#define NOPS  1000000

  my_hashmap *hashmap = (my_hashmap *) param;
  LF_HASH_TABLE *hash = &hashmap->get_hash_table ();
  LF_TRAN_SYSTEM *ts = hash->freelist->tran_system;
  LF_TRAN_ENTRY *te;
  XENTRY *entry;
  int i, rand_base, key, local_del_op_count = 0;

  te = lf_tran_request_entry (ts);
  if (te == NULL)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }

  if (te->entry_idx >= RAND_BLOCKS || te->entry_idx < 0)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }
  else
    {
      rand_base = te->entry_idx * RAND_BLOCK_SIZE;
    }

  for (i = 0; i < NOPS; i++)
    {
      key = random_numbers[rand_base + i] % 1000;

      (void) hashmap->find_or_insert (te, key, entry);
      if (entry == NULL)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }
      if (entry->key != key)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }

      entry->data++;

      if (entry->data >= 10)
    {
      local_del_op_count += entry->data;
      bool success = hashmap->erase_locked (te, key, entry);
      if (!success)
        {
          PTHREAD_ABORT_AND_EXIT (ER_FAILED);
        }
    }
      else
    {
      if (te->locked_mutex != &entry->mutex)
        {
          abort ();
        }
      te->locked_mutex = NULL;
      hashmap->unlock (te, entry);
    }

      assert (te->locked_mutex == NULL);
    }

  lf_tran_return_entry (te);

  ATOMIC_INC_32 (&del_op_count, local_del_op_count);
  pthread_exit (NO_ERROR);

#undef NOPS
}

void *test_clear_proc_1 (void *param);

void *
test_clear_proc_1 (void *param)
{
#define NOPS  1000000

  my_hashmap *hashmap = (my_hashmap *) param;
  LF_HASH_TABLE *hash = &hashmap->get_hash_table ();
  LF_TRAN_SYSTEM *ts = hash->freelist->tran_system;
  LF_TRAN_ENTRY *te;
  XENTRY *entry;
  int i, rand_base, key;

  te = lf_tran_request_entry (ts);
  if (te == NULL)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }

  if (te->entry_idx >= RAND_BLOCKS || te->entry_idx < 0)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }
  else
    {
      rand_base = te->entry_idx * RAND_BLOCK_SIZE;
    }

  for (i = 0; i < NOPS; i++)
    {
      key = random_numbers[rand_base + i] % 1000;
      key = i % 100;

      if (i % 1000 != 999)
    {
      if (i % 10 < 8)
        {
          entry = NULL;
          (void) hashmap->find_or_insert (te, key, entry);
          hashmap->unlock (te, entry);
        }
      else if (i % 1000 < 999)
        {
          (void) hashmap->erase (te, key);
        }
    }
      else
    {
      hashmap->clear (te);
    }
    }

  lf_tran_return_entry (te);

  pthread_exit (NO_ERROR);

#undef NOPS
}

void *test_clear_proc_2 (void *param);

void *
test_clear_proc_2 (void *param)
{
#define NOPS  1000000

  my_hashmap *hashmap = (my_hashmap *) param;
  LF_HASH_TABLE *hash = &hashmap->get_hash_table ();
  LF_TRAN_SYSTEM *ts = hash->freelist->tran_system;
  LF_TRAN_ENTRY *te;
  XENTRY *entry;
  int i, rand_base, key;

  te = lf_tran_request_entry (ts);
  if (te == NULL)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }

  if (te->entry_idx >= RAND_BLOCKS || te->entry_idx < 0)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }
  else
    {
      rand_base = te->entry_idx * RAND_BLOCK_SIZE;
    }

  for (i = 0; i < NOPS; i++)
    {
      key = random_numbers[rand_base + i] % 1000;

      if (i % 1000 < 999)
    {
      if (i % 10 < 5)
        {
          (void) hashmap->find_or_insert (te, key, entry);
          if (entry == NULL)
        {
          PTHREAD_ABORT_AND_EXIT (ER_FAILED);
        }

          if (te->locked_mutex != &entry->mutex)
        {
          abort ();
        }
          te->locked_mutex = NULL;
          hashmap->unlock (te, entry);
        }
      else
        {
          (void) hashmap->erase (te, key);
        }
    }
      else
    {
      hashmap->clear (te);
    }
      assert (te->locked_mutex == NULL);
    }

  lf_tran_return_entry (te);

  pthread_exit (NO_ERROR);

#undef NOPS
}

void *test_clear_proc_3 (void *param);

void *
test_clear_proc_3 (void *param)
{
#define NOPS  1000000

  my_hashmap *hashmap = (my_hashmap *) param;
  LF_HASH_TABLE *hash = &hashmap->get_hash_table ();
  LF_TRAN_SYSTEM *ts = hash->freelist->tran_system;
  LF_TRAN_ENTRY *te;
  XENTRY *entry;
  int i, rand_base, key;

  te = lf_tran_request_entry (ts);
  if (te == NULL)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }

  if (te->entry_idx >= RAND_BLOCKS || te->entry_idx < 0)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }
  else
    {
      rand_base = te->entry_idx * RAND_BLOCK_SIZE;
    }

  for (i = 0; i < NOPS; i++)
    {
      key = random_numbers[rand_base + i] % 1000;

      if (i % 1000 == 999)
    {
      hashmap->clear (te);
      continue;
    }

      (void) hashmap->find_or_insert (te, key, entry);
      if (entry == NULL)
    {
      PTHREAD_ABORT_AND_EXIT (ER_FAILED);
    }

      entry->data++;

      if (entry->data >= 10)
    {
      bool success = hashmap->erase_locked (te, key, entry);
      if (!success)
        {
          /* cleared in the meantime */
          if (te->locked_mutex != &entry->mutex)
        {
          abort ();
        }
          te->locked_mutex = NULL;
          hashmap->unlock (te, entry);
        }
    }
      else
    {
      if (te->locked_mutex != &entry->mutex)
        {
          abort ();
        }
      te->locked_mutex = NULL;
      hashmap->unlock (te, entry);
    }

      assert (te->locked_mutex == NULL);
    }

  lf_tran_return_entry (te);

  pthread_exit (NO_ERROR);

#undef NOPS
}

/* test functions */
static int
test_freelist (LF_ENTRY_DESCRIPTOR * edesc, int nthreads, bool test_local_tran)
{
#define MAX_THREADS 64

  static LF_FREELIST freelist;
  static LF_TRAN_SYSTEM ts;
  pthread_t threads[MAX_THREADS];
  char msg[256];
  int i;

  sprintf (msg, "freelist (transaction=%s, %d threads)", test_local_tran ? "n" : "y", nthreads);
  begin (msg);

  /* initialization */
  if (nthreads > MAX_THREADS)
    {
      return fail ("too many threads");
    }

  if (lf_tran_system_init (&ts, nthreads) != NO_ERROR)
    {
      return fail ("transaction system init");
    }

  if (lf_freelist_init (&freelist, 100, 100, edesc, &ts) != NO_ERROR)
    {
      return fail ("freelist init");
    }

  /* multithreaded test */
  for (i = 0; i < nthreads; i++)
    {
      if (pthread_create (&threads[i], NULL, (test_local_tran ? test_freelist_proc_local_tran : test_freelist_proc),
              (void *) &freelist) != NO_ERROR)
    {
      return fail ("thread create");
    }
    }

  for (i = 0; i < nthreads; i++)
    {
      void *retval;

      pthread_join (threads[i], &retval);
      if (retval != NO_ERROR)
    {
      return fail ("thread proc error");
    }
    }

  /* results */
  {
    volatile XENTRY *e, *a;
    volatile int active, retired, _a, _r, _t;

    a = (XENTRY *) VOLATILE_ACCESS (freelist.available, void *);

    _a = VOLATILE_ACCESS (freelist.available_cnt, int);
    _r = VOLATILE_ACCESS (freelist.retired_cnt, int);
    _t = VOLATILE_ACCESS (freelist.alloc_cnt, int);

    active = 0;
    retired = 0;
    for (e = (XENTRY *) a; e != NULL; e = e->stack)
      {
    active++;
      }
    for (i = 0; i < ts.entry_count; i++)
      {
    for (e = (XENTRY *) ts.entries[i].retired_list; e != NULL; e = e->stack)
      {
        retired++;
      }
      }

    if ((_t - active - retired) != 0)
      {
    sprintf (msg, "leak problem (lost %d entries)", _t - active + retired);
    return fail (msg);
      }

    if ((active != _a) || (retired != _r))
      {
    sprintf (msg, "counting problem (%d != %d) or (%d != %d)", active, _a, retired, _r);
    return fail (msg);
      }
  }

  /* uninit */
  lf_freelist_destroy (&freelist);
  lf_tran_system_destroy (&ts);

  return success ();

#undef MAX_THREADS
}

static int
test_hash_table (LF_ENTRY_DESCRIPTOR * edesc, int nthreads, void *(*proc) (void *))
{
#define MAX_THREADS   1024
#define HASH_SIZE     113

  static LF_TRAN_SYSTEM ts;
  static my_hashmap hashmap;
  // *INDENT-OFF*
  lf_hash_table &hash = hashmap.get_hash_table ();
  lf_freelist &freelist = hashmap.get_freelist ();
  // *INDENT-ON*
  pthread_t threads[MAX_THREADS];
  char msg[256];
  int i;
  XENTRY *e = NULL;

  sprintf (msg, "hash (mutex=%s, %d threads)", edesc->using_mutex ? "y" : "n", nthreads);
  begin (msg);

  lf_reset_counters ();

  /* initialization */
  if (nthreads > MAX_THREADS)
    {
      return fail ("too many threads");
    }

  if (lf_tran_system_init (&ts, nthreads) != NO_ERROR)
    {
      return fail ("transaction system init");
    }

  hashmap.init (ts, HASH_SIZE, 100, 100, *edesc);

  /* multithreaded test */
  for (i = 0; i < nthreads; i++)
    {
      if (pthread_create (&threads[i], NULL, proc, (void *) &hashmap) != NO_ERROR)
    {
      return fail ("thread create");
    }
    }

  for (i = 0; i < nthreads; i++)
    {
      void *retval;

      pthread_join (threads[i], &retval);
      if (retval != NO_ERROR)
    {
      return fail ("thread proc error");
    }
    }

  for (i = 0; i < HASH_SIZE; i++)
    {
      for (e = (XENTRY *) hash.buckets[i]; e != NULL; e = e->next)
    {
      if (edesc->f_hash (&e->key, HASH_SIZE) != (unsigned int) i)
        {
          sprintf (msg, "hash (%d) = %d != %d", e->key, edesc->f_hash (&e->key, HASH_SIZE), i);
          return fail (msg);
        }
    }
    }

  /* count operations */
  if (edesc->using_mutex)
    {
      int nondel_op_count = 0;

      for (i = 0; i < HASH_SIZE; i++)
    {
      for (e = (XENTRY *) hash.buckets[i]; e != NULL; e = e->next)
        {
          nondel_op_count += e->data;
        }
    }

      if (del_op_count != -1)
    {
      /* we're counting delete ops */
      if (nondel_op_count + del_op_count != nthreads * 1000000)
        {
          sprintf (msg, "op count fail (%d + %d != %d)", nondel_op_count, del_op_count, nthreads * 1000000);
          return fail (msg);
        }
    }
    }

  /* count entries */
  {
    XENTRY *e;
    int ecount = 0, acount = 0, rcount = 0;

    for (i = 0; i < HASH_SIZE; i++)
      {
    for (e = (XENTRY *) hash.buckets[i]; e != NULL; e = e->next)
      {
        ecount++;
      }
      }

    for (e = (XENTRY *) freelist.available; e != NULL; e = e->stack)
      {
    acount++;
      }
    for (i = 0; i < ts.entry_count; i++)
      {
    for (e = (XENTRY *) ts.entries[i].retired_list; e != NULL; e = e->stack)
      {
        rcount++;
      }
    if (ts.entries[i].temp_entry != NULL)
      {
        ecount++;
      }
      }

    if (freelist.available_cnt != acount)
      {
    sprintf (msg, "counting fail (available %d != %d)", freelist.available_cnt, acount);
    return fail (msg);
      }
    if (freelist.retired_cnt != rcount)
      {
    sprintf (msg, "counting fail (retired %d != %d)", freelist.retired_cnt, rcount);
    return fail (msg);
      }

    if (ecount + freelist.available_cnt + freelist.retired_cnt != freelist.alloc_cnt)
      {
    sprintf (msg, "leak check fail (%d + %d + %d = %d != %d)", ecount, freelist.available_cnt, freelist.retired_cnt,
         ecount + freelist.available_cnt + freelist.retired_cnt, freelist.alloc_cnt);
    return fail (msg);
      }
  }

  /* uninit */
  hashmap.destroy ();
  lf_tran_system_destroy (&ts);

  return success ();
#undef HASH_SIZE
#undef MAX_THREADS
}

static int
test_hash_iterator ()
{
#define HASH_SIZE 200
#define HASH_POPULATION HASH_SIZE * 5
#define NUM_THREADS 16

  static LF_TRAN_SYSTEM ts;
  static my_hashmap hashmap;
  LF_HASH_TABLE hash = hashmap.get_hash_table ();
  LF_FREELIST freelist = hashmap.get_freelist ();
  static LF_TRAN_ENTRY *te;
  int i;

  begin ("hash table iterator");

  /* initialization */
  if (lf_tran_system_init (&ts, NUM_THREADS) != NO_ERROR)
    {
      return fail ("transaction system init");
    }

  te = lf_tran_request_entry (&ts);
  if (te == NULL)
    {
      return fail ("failed to fetch tran entry");
    }

  hashmap.init (ts, HASH_SIZE, 100, 100, xentry_desc);

  /* static (single threaded) test */
  for (i = 0; i < HASH_POPULATION; i++)
    {
      XENTRY *entry;

      (void) hashmap.find_or_insert (te, i, entry);
      if (entry == NULL)
    {
      return fail ("null insert error");
    }
      else
    {
      entry->data = i;
      /* end transaction */
      hashmap.unlock (te, entry);
    }
    }

  {
    // *INDENT-OFF*
    my_hashmap_iterator it { te, hashmap};
    // *INDENT-ON*
    XENTRY *curr = NULL;
    char msg[256];
    int sum = 0;

    for (curr = it.iterate (); curr != NULL; curr = it.iterate ())
      {
    sum += curr->data;
      }

    if (sum != ((HASH_POPULATION - 1) * HASH_POPULATION) / 2)
      {
    sprintf (msg, "counting error (%d != %d)", sum, (HASH_POPULATION - 1) * HASH_POPULATION / 2);
    return fail (msg);
      }
  }

  /* reset */
  hashmap.clear (te);

  /* multi-threaded test */
  /* TODO TODO TODO */

  /* uninit */
  lf_tran_return_entry (te);
  hashmap.destroy ();
  lf_tran_system_destroy (&ts);
  return success ();
#undef HASH_SIZE
#undef HASH_POPULATION
#undef NUM_THREADS
}

/* program entry */
int
main (int argc, char **argv)
{
  int i;
  bool test_local_tran;

  /* generate random number array for non-blocking access */
  generate_random ();

  /* circular queue */
  /* temporarily disabled */
  /* for (i = 1; i <= 64; i *= 2) { if (test_circular_queue (i) != NO_ERROR) { goto fail; } } */

  /* freelist */
  test_local_tran = false;
  for (i = 1; i <= 64; i *= 2)
    {
      if (test_freelist (&xentry_desc, i, test_local_tran) != NO_ERROR)
    {
      goto fail;
    }
    }

  test_local_tran = true;
  for (i = 1; i <= 64; i *= 2)
    {
      if (test_freelist (&xentry_desc, i, test_local_tran) != NO_ERROR)
    {
      goto fail;
    }
    }

  /* test hash table iterator */
  if (test_hash_iterator () != NO_ERROR)
    {
      goto fail;
    }

  /* hash table - no entry mutex */
  for (i = 1; i <= 64; i *= 2)
    {
      if (test_hash_table (&xentry_desc, i, test_hash_proc_1) != NO_ERROR)
    {
      goto fail;
    }
      if (test_hash_table (&xentry_desc, i, test_clear_proc_1) != NO_ERROR)
    {
      goto fail;
    }
    }

  /* hash table - entry mutex, no lock between find and delete */
  xentry_desc.using_mutex = LF_EM_USING_MUTEX;
  for (i = 1; i <= 64; i *= 2)
    {
      if (test_hash_table (&xentry_desc, i, test_hash_proc_2) != NO_ERROR)
    {
      goto fail;
    }
      if (test_hash_table (&xentry_desc, i, test_clear_proc_2) != NO_ERROR)
    {
      goto fail;
    }

    }

  /* hash table - entry mutex, hold lock between find and delete */
  xentry_desc.using_mutex = LF_EM_USING_MUTEX;
  for (i = 1; i <= 64; i *= 2)
    {
      /* test_hash_proc_3 uses global del_op_count */
      del_op_count = 0;
      if (test_hash_table (&xentry_desc, i, test_hash_proc_3) != NO_ERROR)
    {
      goto fail;
    }
      del_op_count = -1;
      if (test_hash_table (&xentry_desc, i, test_clear_proc_3) != NO_ERROR)
    {
      goto fail;
    }

    }

  /* all ok */
  return 0;

fail:
  printf ("Unit tests failed!\n");
  return ER_FAILED;
}