/*
* Standalone mutex tester for Berkeley DB mutexes.
*/
#include "db_config.h"
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if defined(MUTEX_THREAD_TEST)
#include <pthread.h>
#endif
#include "db_int.h"
#ifndef HAVE_QNX
#define shm_open open
#define shm_unlink remove
#endif
void exec_proc(u_long, char *, char *);
void map_file(u_int8_t **, u_int8_t **, u_int8_t **, int *);
void tm_file_init(void);
void run_locker(u_long);
void *run_lthread(void *);
void run_wakeup(u_long);
void *run_wthread(void *);
void tm_mutex_destroy(void);
void tm_mutex_init(void);
void tm_mutex_stats(void);
void unmap_file(u_int8_t *, int);
int usage(void);
#define MT_FILE "mutex.file"
#define MT_FILE_QUIT "mutex.file.quit"
DB_ENV dbenv; /* Fake out DB. */
size_t len; /* Backing file size. */
int align; /* Mutex alignment in file. */
int maxlocks = 20; /* -l: Backing locks. */
int nlocks = 10000; /* -n: Locks per processes. */
int nprocs = 20; /* -p: Processes. */
int nthreads = 1; /* -t: Threads. */
int verbose; /* -v: Verbosity. */
typedef struct {
DB_MUTEX mutex; /* Mutex. */
u_long id; /* Holder's ID. */
#define MUTEX_WAKEME 0x01 /* Request to awake. */
u_int flags;
} TM;
int
main(argc, argv)
int argc;
char *argv[];
{
enum {LOCKER, WAKEUP, PARENT} rtype;
extern int optind;
extern char *optarg;
pid_t pid;
u_long id;
int ch, fd, eval, i, status;
char *p, *tmpath;
__os_spin(&dbenv); /* Fake out DB. */
rtype = PARENT;
id = 0;
tmpath = argv[0];
while ((ch = getopt(argc, argv, "l:n:p:T:t:v")) != EOF)
switch (ch) {
case 'l':
maxlocks = atoi(optarg);
break;
case 'n':
nlocks = atoi(optarg);
break;
case 'p':
nprocs = atoi(optarg);
break;
case 't':
if ((nthreads = atoi(optarg)) == 0)
nthreads = 1;
#if !defined(MUTEX_THREAD_TEST)
if (nthreads != 1) {
(void)fprintf(stderr,
"tm: thread support not available or not compiled for this platform.\n");
return (EXIT_FAILURE);
}
#endif
break;
case 'T':
if (!memcmp(optarg, "locker", sizeof("locker") - 1))
rtype = LOCKER;
else if (
!memcmp(optarg, "wakeup", sizeof("wakeup") - 1))
rtype = WAKEUP;
else
return (usage());
if ((p = strchr(optarg, '=')) == NULL)
return (usage());
id = atoi(p + 1);
break;
case 'v':
verbose = 1;
break;
case '?':
default:
return (usage());
}
argc -= optind;
argv += optind;
/*
* The file layout:
* TM[1] per-thread mutex array lock
* TM[nthreads] per-thread mutex array
* TM[maxlocks] per-lock mutex array
*/
align = DB_ALIGN(sizeof(TM), MUTEX_ALIGN);
len = align * (1 + nthreads * nprocs + maxlocks);
switch (rtype) {
case PARENT:
break;
case LOCKER:
run_locker(id);
return (EXIT_SUCCESS);
case WAKEUP:
run_wakeup(id);
return (EXIT_SUCCESS);
}
printf(
"tm: %d processes, %d threads/process, %d lock requests from %d locks\n",
nprocs, nthreads, nlocks, maxlocks);
printf(
"tm: mutex alignment %lu, structure alignment %d, backing file %lu bytes\n",
(u_long)MUTEX_ALIGN, align, (u_long)len);
tm_file_init(); /* Initialize backing file. */
tm_mutex_init(); /* Initialize file's mutexes. */
for (i = 0; i < nprocs; ++i) {
switch (fork()) {
case -1:
perror("fork");
return (EXIT_FAILURE);
case 0:
exec_proc(id, tmpath, "locker");
break;
default:
break;
}
id += nthreads;
}
(void)remove(MT_FILE_QUIT);
switch (fork()) {
case -1:
perror("fork");
return (EXIT_FAILURE);
case 0:
exec_proc(id, tmpath, "wakeup");
break;
default:
break;
}
++id;
/* Wait for locking threads. */
for (i = 0, eval = EXIT_SUCCESS; i < nprocs; ++i)
if ((pid = wait(&status)) != (pid_t)-1) {
fprintf(stderr,
"%lu: exited %d\n", (u_long)pid, WEXITSTATUS(status));
if (WEXITSTATUS(status) != 0)
eval = EXIT_FAILURE;
}
/* Signal wakeup thread to exit. */
if ((fd = open(MT_FILE_QUIT, O_WRONLY | O_CREAT, 0664)) == -1) {
fprintf(stderr, "tm: %s\n", strerror(errno));
status = EXIT_FAILURE;
}
(void)close(fd);
/* Wait for wakeup thread. */
if ((pid = wait(&status)) != (pid_t)-1) {
fprintf(stderr,
"%lu: exited %d\n", (u_long)pid, WEXITSTATUS(status));
if (WEXITSTATUS(status) != 0)
eval = EXIT_FAILURE;
}
(void)remove(MT_FILE_QUIT);
tm_mutex_stats(); /* Display run statistics. */
tm_mutex_destroy(); /* Destroy region. */
printf("tm: exit status: %s\n",
eval == EXIT_SUCCESS ? "success" : "failed!");
return (eval);
}
void
exec_proc(id, tmpath, typearg)
u_long id;
char *tmpath, *typearg;
{
char *argv[10], **ap, b_l[10], b_n[10], b_p[10], b_t[10], b_T[10];
ap = &argv[0];
*ap++ = "tm";
sprintf(b_l, "-l%d", maxlocks);
*ap++ = b_l;
sprintf(b_n, "-n%d", nlocks);
*ap++ = b_p;
sprintf(b_p, "-p%d", nprocs);
*ap++ = b_n;
sprintf(b_t, "-t%d", nthreads);
*ap++ = b_t;
sprintf(b_T, "-T%s=%lu", typearg, id);
*ap++ = b_T;
if (verbose)
*ap++ = "-v";
*ap = NULL;
execvp(tmpath, argv);
fprintf(stderr, "%s: %s\n", tmpath, strerror(errno));
exit(EXIT_FAILURE);
}
void
run_locker(id)
u_long id;
{
#if defined(MUTEX_THREAD_TEST)
pthread_t *kidsp;
int i;
void *retp;
#endif
int status;
__os_sleep(&dbenv, 3, 0); /* Let everyone catch up. */
srand((u_int)time(NULL) % getpid()); /* Initialize random numbers. */
#if defined(MUTEX_THREAD_TEST)
/*
* Spawn off threads. We have nthreads all locking and going to
* sleep, and one other thread cycling through and waking them up.
*/
if ((kidsp =
(pthread_t *)calloc(sizeof(pthread_t), nthreads)) == NULL) {
fprintf(stderr, "tm: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
for (i = 0; i < nthreads; i++)
if ((errno = pthread_create(
&kidsp[i], NULL, run_lthread, (void *)(id + i))) != 0) {
fprintf(stderr, "tm: failed spawning thread: %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
/* Wait for the threads to exit. */
status = EXIT_SUCCESS;
for (i = 0; i < nthreads; i++) {
pthread_join(kidsp[i], &retp);
if (retp != NULL) {
fprintf(stderr, "tm: thread exited with error\n");
status = EXIT_FAILURE;
}
}
free(kidsp);
#else
status = (int)run_lthread((void *)id);
#endif
exit(status);
}
void *
run_lthread(arg)
void *arg;
{
TM *gp, *mp, *tp;
u_long id, tid;
int fd, i, lock, nl, remap;
u_int8_t *gm_addr, *lm_addr, *tm_addr;
id = (int)arg;
#if defined(MUTEX_THREAD_TEST)
tid = (u_long)pthread_self();
#else
tid = 0;
#endif
printf("Locker: ID %03lu (PID: %lu; TID: %lx)\n",
id, (u_long)getpid(), tid);
nl = nlocks;
for (gm_addr = NULL, gp = tp = NULL, remap = 0;;) {
/* Map in the file as necessary. */
if (gm_addr == NULL) {
map_file(&gm_addr, &tm_addr, &lm_addr, &fd);
gp = (TM *)gm_addr;
tp = (TM *)(tm_addr + id * align);
if (verbose)
printf(
"%03lu: map threads @ %#lx; locks @ %#lx\n",
id, (u_long)tm_addr, (u_long)lm_addr);
remap = (rand() % 100) + 35;
}
/* Select and acquire a data lock. */
lock = rand() % maxlocks;
mp = (TM *)(lm_addr + lock * align);
if (verbose)
printf("%03lu: lock %d @ %#lx\n",
id, lock, (u_long)&mp->mutex);
if (__db_mutex_lock(&dbenv, &mp->mutex)) {
fprintf(stderr, "%03lu: never got lock %d: %s\n",
id, lock, strerror(errno));
return ((void *)EXIT_FAILURE);
}
if (mp->id != 0) {
fprintf(stderr,
"RACE! (%03lu granted lock %d held by %03lu)\n",
id, lock, mp->id);
return ((void *)EXIT_FAILURE);
}
mp->id = id;
/*
* Pretend to do some work, periodically checking to see if
* we still hold the mutex.
*/
for (i = 0; i < 3; ++i) {
__os_sleep(&dbenv, 0, rand() % 3);
if (mp->id != id) {
fprintf(stderr,
"RACE! (%03lu stole lock %d from %03lu)\n",
mp->id, lock, id);
return ((void *)EXIT_FAILURE);
}
}
/*
* Test self-blocking and unlocking by other threads/processes:
*
* acquire the global lock
* set our wakeup flag
* release the global lock
* acquire our per-thread lock
*
* The wakeup thread will wake us up.
*/
if (__db_mutex_lock(&dbenv, &gp->mutex)) {
fprintf(stderr,
"%03lu: global lock: %s\n", id, strerror(errno));
return ((void *)EXIT_FAILURE);
}
if (tp->id != 0 && tp->id != id) {
fprintf(stderr,
"%03lu: per-thread mutex isn't mine, owned by %03lu\n",
id, tp->id);
return ((void *)EXIT_FAILURE);
}
tp->id = id;
if (verbose)
printf("%03lu: self-blocking\n", id);
if (F_ISSET(tp, MUTEX_WAKEME)) {
fprintf(stderr,
"%03lu: wakeup flag incorrectly set\n", id);
return ((void *)EXIT_FAILURE);
}
F_SET(tp, MUTEX_WAKEME);
if (__db_mutex_unlock(&dbenv, &gp->mutex)) {
fprintf(stderr,
"%03lu: global unlock: %s\n", id, strerror(errno));
return ((void *)EXIT_FAILURE);
}
if (__db_mutex_lock(&dbenv, &tp->mutex)) {
fprintf(stderr, "%03lu: per-thread lock: %s\n",
id, strerror(errno));
return ((void *)EXIT_FAILURE);
}
/* Time passes... */
if (F_ISSET(tp, MUTEX_WAKEME)) {
fprintf(stderr, "%03lu: wakeup flag not cleared\n", id);
return ((void *)EXIT_FAILURE);
}
if (verbose)
printf("%03lu: release %d @ %#lx\n",
id, lock, (u_long)&mp->mutex);
/* Release the data lock. */
mp->id = 0;
if (__db_mutex_unlock(&dbenv, &mp->mutex)) {
fprintf(stderr,
"%03lu: lock release: %s\n", id, strerror(errno));
return ((void *)EXIT_FAILURE);
}
if (--nl % 100 == 0)
fprintf(stderr, "%03lu: %d\n", id, nl);
if (nl == 0 || --remap == 0) {
if (verbose)
printf("%03lu: re-mapping\n", id);
unmap_file(gm_addr, fd);
gm_addr = NULL;
if (nl == 0)
break;
__os_sleep(&dbenv, 0, rand() % 500);
}
}
return (NULL);
}
void
run_wakeup(id)
u_long id;
{
#if defined(MUTEX_THREAD_TEST)
pthread_t wakep;
int status;
void *retp;
#endif
__os_sleep(&dbenv, 3, 0); /* Let everyone catch up. */
srand((u_int)time(NULL) % getpid()); /* Initialize random numbers. */
#if defined(MUTEX_THREAD_TEST)
/*
* Spawn off wakeup thread.
*/
if ((errno = pthread_create(
&wakep, NULL, run_wthread, (void *)id)) != 0) {
fprintf(stderr, "tm: failed spawning wakeup thread: %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
/*
* run_locker will create a file when the wakeup thread is no
* longer needed.
*/
status = 0;
pthread_join(wakep, &retp);
if (retp != NULL) {
fprintf(stderr, "tm: wakeup thread exited with error\n");
status = EXIT_FAILURE;
}
exit(status);
#else
exit((int)run_wthread((void *)id));
#endif
}
/*
* run_wthread --
* Thread to wake up other threads that are sleeping.
*/
void *
run_wthread(arg)
void *arg;
{
struct stat sb;
TM *gp, *tp;
u_long id, tid;
int fd, check_id;
u_int8_t *gm_addr, *tm_addr;
id = (int)arg;
#if defined(MUTEX_THREAD_TEST)
tid = (u_long)pthread_self();
#else
tid = 0;
#endif
printf("Wakeup: ID %03lu (PID: %lu; TID: %lx)\n",
id, (u_long)getpid(), tid);
arg = NULL;
map_file(&gm_addr, &tm_addr, NULL, &fd);
if (verbose)
printf("%03lu: map threads @ %#lx\n", id, (u_long)tm_addr);
gp = (TM *)gm_addr;
/* Loop, waking up sleepers and periodically sleeping ourselves. */
for (check_id = 0;; ++check_id) {
/* Check to see if the locking threads have finished. */
if (stat(MT_FILE_QUIT, &sb) == 0)
break;
/* Check for ID wraparound. */
if (check_id == nthreads * nprocs)
check_id = 0;
/* Check for a thread that needs a wakeup. */
tp = (TM *)(tm_addr + check_id * align);
if (!F_ISSET(tp, MUTEX_WAKEME))
continue;
if (verbose)
printf("%03lu: wakeup thread %03lu @ %#lx\n",
id, tp->id, (u_long)&tp->mutex);
/* Acquire the global lock. */
if (__db_mutex_lock(&dbenv, &gp->mutex)) {
fprintf(stderr,
"wakeup: global lock: %s\n", strerror(errno));
return ((void *)EXIT_FAILURE);
}
F_CLR(tp, MUTEX_WAKEME);
if (__db_mutex_unlock(&dbenv, &tp->mutex)) {
fprintf(stderr,
"wakeup: unlock: %s\n", strerror(errno));
return ((void *)EXIT_FAILURE);
}
if (__db_mutex_unlock(&dbenv, &gp->mutex)) {
fprintf(stderr,
"wakeup: global unlock: %s\n", strerror(errno));
return ((void *)EXIT_FAILURE);
}
__os_sleep(&dbenv, 0, rand() % 3);
}
return (NULL);
}
/*
* tm_file_init --
* Initialize the backing file.
*/
void
tm_file_init()
{
int fd;
/* Initialize the backing file. */
if (verbose)
printf("Create the backing file.\n");
(void)shm_unlink(MT_FILE);
if ((fd = shm_open(
MT_FILE, O_CREAT | O_RDWR | O_TRUNC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) == -1) {
(void)fprintf(stderr,
"%s: open: %s\n", MT_FILE, strerror(errno));
exit(EXIT_FAILURE);
}
if (lseek(fd,
(off_t)len, SEEK_SET) != (off_t)len || write(fd, &fd, 1) != 1) {
(void)fprintf(stderr,
"%s: seek/write: %s\n", MT_FILE, strerror(errno));
exit(EXIT_FAILURE);
}
(void)close(fd);
}
/*
* tm_mutex_init --
* Initialize the mutexes.
*/
void
tm_mutex_init()
{
TM *mp;
int fd, i;
u_int8_t *gm_addr, *lm_addr, *tm_addr;
map_file(&gm_addr, &tm_addr, &lm_addr, &fd);
if (verbose)
printf("init: map threads @ %#lx; locks @ %#lx\n",
(u_long)tm_addr, (u_long)lm_addr);
if (verbose)
printf("Initialize the global mutex:\n");
mp = (TM *)gm_addr;
if (__db_mutex_init_int(&dbenv, &mp->mutex, 0, 0)) {
fprintf(stderr,
"__db_mutex_init (global): %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
if (verbose)
printf("\t@ %#lx\n", (u_long)&mp->mutex);
if (verbose)
printf(
"Initialize %d per-thread mutexes:\n", nthreads * nprocs);
for (i = 0; i < nthreads * nprocs; ++i) {
mp = (TM *)(tm_addr + i * align);
if (__db_mutex_init_int(
&dbenv, &mp->mutex, 0, MUTEX_SELF_BLOCK)) {
fprintf(stderr, "__db_mutex_init (per-thread %d): %s\n",
i, strerror(errno));
exit(EXIT_FAILURE);
}
if (__db_mutex_lock(&dbenv, &mp->mutex)) {
fprintf(stderr, "__db_mutex_lock (per-thread %d): %s\n",
i, strerror(errno));
exit(EXIT_FAILURE);
}
if (verbose)
printf("\t@ %#lx\n", (u_long)&mp->mutex);
}
if (verbose)
printf("Initialize %d per-lock mutexes:\n", maxlocks);
for (i = 0; i < maxlocks; ++i) {
mp = (TM *)(lm_addr + i * align);
if (__db_mutex_init_int(&dbenv, &mp->mutex, 0, 0)) {
fprintf(stderr, "__db_mutex_init (per-lock: %d): %s\n",
i, strerror(errno));
exit(EXIT_FAILURE);
}
if (verbose)
printf("\t@ %#lx\n", (u_long)&mp->mutex);
}
unmap_file(gm_addr, fd);
}
/*
* tm_mutex_destroy --
* Destroy the mutexes.
*/
void
tm_mutex_destroy()
{
TM *gp, *mp;
int fd, i;
u_int8_t *gm_addr, *lm_addr, *tm_addr;
map_file(&gm_addr, &tm_addr, &lm_addr, &fd);
if (verbose)
printf("Destroy the global mutex.\n");
gp = (TM *)gm_addr;
if (__db_mutex_destroy(&gp->mutex)) {
fprintf(stderr,
"__db_mutex_destroy (global): %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
if (verbose)
printf("Destroy the per-thread mutexes.\n");
for (i = 0; i < nthreads * nprocs; ++i) {
mp = (TM *)(tm_addr + i * align);
if (__db_mutex_destroy(&mp->mutex)) {
fprintf(stderr,
"__db_mutex_destroy (per-thread %d): %s\n",
i, strerror(errno));
exit(EXIT_FAILURE);
}
}
if (verbose)
printf("Destroy the per-lock mutexes.\n");
for (i = 0; i < maxlocks; ++i) {
mp = (TM *)(tm_addr + i * align);
if (__db_mutex_destroy(&mp->mutex)) {
fprintf(stderr,
"__db_mutex_destroy (per-lock: %d): %s\n",
i, strerror(errno));
exit(EXIT_FAILURE);
}
}
unmap_file(gm_addr, fd);
(void)shm_unlink(MT_FILE);
}
/*
* tm_mutex_stats --
* Display mutex statistics.
*/
void
tm_mutex_stats()
{
TM *mp;
int fd, i;
u_int8_t *gm_addr, *lm_addr;
map_file(&gm_addr, NULL, &lm_addr, &fd);
printf("Per-lock mutex statistics.\n");
for (i = 0; i < maxlocks; ++i) {
mp = (TM *)(lm_addr + i * align);
printf("mutex %2d: wait: %lu; no wait %lu\n", i,
(u_long)mp->mutex.mutex_set_wait,
(u_long)mp->mutex.mutex_set_nowait);
}
unmap_file(gm_addr, fd);
}
/*
* map_file --
* Map in the backing file.
*/
void
map_file(gm_addrp, tm_addrp, lm_addrp, fdp)
u_int8_t **gm_addrp, **tm_addrp, **lm_addrp;
int *fdp;
{
void *addr;
int fd;
#ifndef MAP_FAILED
#define MAP_FAILED (void *)-1
#endif
#ifndef MAP_FILE
#define MAP_FILE 0
#endif
if ((fd = shm_open(MT_FILE, O_RDWR, 0)) == -1) {
fprintf(stderr, "%s: open %s\n", MT_FILE, strerror(errno));
exit(EXIT_FAILURE);
}
addr = mmap(NULL, len,
PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, (off_t)0);
if (addr == MAP_FAILED) {
fprintf(stderr, "%s: mmap: %s\n", MT_FILE, strerror(errno));
exit(EXIT_FAILURE);
}
if (gm_addrp != NULL)
*gm_addrp = (u_int8_t *)addr;
addr = (u_int8_t *)addr + align;
if (tm_addrp != NULL)
*tm_addrp = (u_int8_t *)addr;
addr = (u_int8_t *)addr + align * (nthreads * nprocs);
if (lm_addrp != NULL)
*lm_addrp = (u_int8_t *)addr;
if (fdp != NULL)
*fdp = fd;
}
/*
* unmap_file --
* Discard backing file map.
*/
void
unmap_file(addr, fd)
u_int8_t *addr;
int fd;
{
if (munmap(addr, len) != 0) {
fprintf(stderr, "munmap: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
if (close(fd) != 0) {
fprintf(stderr, "close: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
}
/*
* usage --
*
*/
int
usage()
{
(void)fprintf(stderr, "%s\n\t%s\n",
"usage: tm [-v] [-l maxlocks]",
"[-n locks] [-p procs] [-T locker=ID|wakeup=ID] [-t threads]");
return (EXIT_FAILURE);
}
syntax highlighted by Code2HTML, v. 0.9.1