/* ``The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved via the world wide web at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Initial Developer of the Original Code is Ericsson Utvecklings AB.
* Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
* AB. All Rights Reserved.''
*
* $Id$
*/
#ifndef __WIN32__
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#endif
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "testcase_driver.h"
#include "allocator_test.h"
#ifdef __WIN32__
#undef HAVE_VSNPRINTF
#define HAVE_VSNPRINTF 1
#define vsnprintf _vsnprintf
#endif
#ifndef HAVE_VSNPRINTF
#define HAVE_VSNPRINTF 0
#endif
#define NO_OF_ALLOC_SEQS 6
#define NO_OF_THREADS (18)
#define NO_OF_BLOCKS 10
#define NO_OF_OPS_PER_BL 200
#define SBC_THRESHOLD 8192
#define BLOCK_ID(TN, BN) ((TN) << 4 | (BN))
#define ERR_BUF_SZ 4096
static char err_buf[ERR_BUF_SZ];
static volatile int tc_failed;
static int dead_thread_no;
static erts_mutex tc_mutex;
static erts_cond tc_cond;
static void exit_thread(int t_no, int do_lock)
{
if (do_lock)
THR_MTX_LOCK(tc_mutex);
while (dead_thread_no >= 0)
THR_COND_WAIT(tc_cond, tc_mutex);
dead_thread_no = t_no;
THR_COND_BCAST(tc_cond);
THR_MTX_UNLOCK(tc_mutex);
THR_EXIT(NULL);
}
static void fail(int t_no, char *frmt, ...)
{
char buf[10];
size_t bufsz = sizeof(buf);
va_list va;
THR_MTX_LOCK(tc_mutex);
va_start(va, frmt);
#if HAVE_VSNPRINTF
vsnprintf(err_buf, ERR_BUF_SZ, frmt, va);
#else
vsprintf(err_buf, frmt, va);
#endif
va_end(va);
tc_failed = 1;
if (erl_drv_getenv("ERL_ABORT_ON_FAILURE", buf, &bufsz) == 0
&& strcmp("true", buf) == 0) {
fprintf(stderr, "Testcase \"%s\" failed: %s\n",
testcase_name(), err_buf);
abort();
}
exit_thread(t_no, 0);
}
static Allctr_t *alloc_not_ts = NULL;
static Allctr_t *alloc_ts_1 = NULL;
static Allctr_t *alloc_ts_2 = NULL;
static void stop_allocators(void)
{
if (alloc_not_ts) {
STOP_ALC(alloc_not_ts);
alloc_not_ts = NULL;
}
if (alloc_ts_1) {
STOP_ALC(alloc_ts_1);
alloc_ts_1 = NULL;
}
if (alloc_ts_2) {
STOP_ALC(alloc_ts_2);
alloc_ts_2 = NULL;
}
}
void *thread_func(void *arg);
typedef struct {
Allctr_t *a;
int t_no;
int no_ops_per_bl;
} ThreadData;
char *
testcase_name(void)
{
return "threads";
}
void
testcase_cleanup(TestCaseState_t *tcs)
{
stop_allocators();
}
void
testcase_run(TestCaseState_t *tcs)
{
struct {
erts_thread tid;
ThreadData arg;
} threads[NO_OF_THREADS+1] = {{0}};
int no_threads;
int i;
char sbct_buf[10];
char *argv_org[] = {"-tasaobf", "-tmmsbc5000", "-tmmmbc5000", "-tsbct",
&sbct_buf[0], NULL};
char *argv[sizeof(argv_org)/sizeof(argv_org[0])];
if (!IS_THREADS_ENABLED)
testcase_skipped(tcs, "Threads not enabled");
alloc_not_ts = NULL;
alloc_ts_1 = NULL;
alloc_ts_2 = NULL;
err_buf[0] = '\0';
sprintf(sbct_buf, "%d", SBC_THRESHOLD/1024);
memcpy((void *) argv, argv_org, sizeof(argv_org));
alloc_not_ts = START_ALC("threads_not_ts", 0, argv);
ASSERT(tcs, alloc_not_ts);
memcpy((void *) argv, argv_org, sizeof(argv_org));
alloc_ts_1 = START_ALC("threads_ts_1", 1, argv);
ASSERT(tcs, alloc_ts_1);
memcpy((void *) argv, argv_org, sizeof(argv_org));
alloc_ts_2 = START_ALC("threads_ts_2", 1, argv);
ASSERT(tcs, alloc_ts_2);
ASSERT(tcs, !IS_ALLOC_THREAD_SAFE(alloc_not_ts));
ASSERT(tcs, IS_ALLOC_THREAD_SAFE(alloc_ts_1));
ASSERT(tcs, IS_ALLOC_THREAD_SAFE(alloc_ts_2));
tc_mutex = THR_MTX_CREATE();
tc_cond = THR_COND_CREATE();
THR_MTX_LOCK(tc_mutex);
dead_thread_no = -1;
no_threads = 0;
for(i = 1; i <= NO_OF_THREADS; i++) {
char *alc;
int res;
threads[i].arg.no_ops_per_bl = NO_OF_OPS_PER_BL;
if (i == 1) {
alc = "threads_not_ts";
threads[i].arg.no_ops_per_bl *= 2;
threads[i].arg.a = alloc_not_ts;
}
else if (i % 2 == 0) {
alc = "threads_ts_1";
threads[i].arg.a = alloc_ts_1;
}
else {
alc = "threads_ts_2";
threads[i].arg.a = alloc_ts_2;
}
threads[i].arg.t_no = i;
threads[i].tid = THR_CREATE(thread_func, (void *) &threads[i].arg);
if (threads[i].tid) {
testcase_printf(tcs, "Successfully created thread %d "
"using %s_alloc\n", i, alc);
no_threads++;
}
else {
tc_failed = 1;
sprintf(err_buf, "Failed to create thread %d\n", i);
break;
}
}
while (no_threads) {
THR_COND_WAIT(tc_cond, tc_mutex);
if (dead_thread_no >= 0) {
no_threads--;
THR_JOIN(threads[dead_thread_no].tid);
testcase_printf(tcs, "Thread %d died\n", dead_thread_no);
dead_thread_no = -1;
THR_COND_BCAST(tc_cond);
}
}
THR_MTX_UNLOCK(tc_mutex);
THR_MTX_DESTROY(tc_mutex);
THR_COND_DESTROY(tc_cond);
stop_allocators();
if (tc_failed)
testcase_failed(tcs, "%s", err_buf);
}
Ulong alloc_seq_1[] = {
17,
SBC_THRESHOLD*2,
SBC_THRESHOLD*20,
SBC_THRESHOLD*2,
17,
0
};
Ulong alloc_seq_2[] = {
SBC_THRESHOLD*20,
SBC_THRESHOLD*2,
17,
SBC_THRESHOLD*2,
SBC_THRESHOLD*20,
0
};
Ulong alloc_seq_3[] = {
1,
SBC_THRESHOLD/10,
SBC_THRESHOLD/9,
SBC_THRESHOLD/8,
SBC_THRESHOLD/7,
SBC_THRESHOLD/6,
SBC_THRESHOLD/5,
SBC_THRESHOLD/4,
SBC_THRESHOLD/3,
SBC_THRESHOLD/2,
SBC_THRESHOLD/1,
SBC_THRESHOLD*1,
SBC_THRESHOLD*2,
SBC_THRESHOLD*3,
SBC_THRESHOLD*4,
SBC_THRESHOLD*5,
SBC_THRESHOLD*6,
SBC_THRESHOLD*7,
SBC_THRESHOLD*8,
SBC_THRESHOLD*9,
SBC_THRESHOLD*10,
SBC_THRESHOLD*9,
SBC_THRESHOLD*8,
SBC_THRESHOLD*7,
SBC_THRESHOLD*6,
SBC_THRESHOLD*5,
SBC_THRESHOLD*4,
SBC_THRESHOLD*3,
SBC_THRESHOLD*2,
SBC_THRESHOLD*1,
SBC_THRESHOLD/2,
SBC_THRESHOLD/3,
SBC_THRESHOLD/4,
SBC_THRESHOLD/5,
SBC_THRESHOLD/6,
SBC_THRESHOLD/7,
SBC_THRESHOLD/8,
SBC_THRESHOLD/9,
SBC_THRESHOLD/10,
1,
0
};
Ulong alloc_seq_4[] = {
SBC_THRESHOLD*2,
SBC_THRESHOLD*3,
SBC_THRESHOLD*7,
SBC_THRESHOLD*8,
SBC_THRESHOLD*5,
SBC_THRESHOLD*6,
SBC_THRESHOLD*1,
SBC_THRESHOLD*10,
SBC_THRESHOLD*4,
SBC_THRESHOLD*2,
0
};
Ulong alloc_seq_5[] = {
SBC_THRESHOLD/2,
SBC_THRESHOLD/3,
SBC_THRESHOLD/7,
SBC_THRESHOLD/8,
SBC_THRESHOLD/5,
SBC_THRESHOLD/6,
SBC_THRESHOLD/1,
SBC_THRESHOLD/10,
SBC_THRESHOLD/4,
SBC_THRESHOLD/2,
SBC_THRESHOLD/3,
SBC_THRESHOLD/7,
SBC_THRESHOLD/8,
SBC_THRESHOLD/5,
SBC_THRESHOLD/6,
SBC_THRESHOLD/1,
SBC_THRESHOLD/10,
SBC_THRESHOLD/4,
SBC_THRESHOLD/2,
0
};
Ulong alloc_seq_6[] = {
1, 50, 100, 50, 23, 46, 2345, 23, 54, 2, 0
};
Ulong *alloc_seqs[NO_OF_ALLOC_SEQS] = {
alloc_seq_1,
alloc_seq_2,
alloc_seq_3,
alloc_seq_4,
alloc_seq_5,
alloc_seq_6
};
typedef struct {
unsigned char *p;
Ulong s;
int i;
Ulong *as;
} block;
#define CHECK_BLOCK_DATA(T, P, S, D) \
check_block_data(__FILE__, __LINE__, (T), (P), (S), (D))
static void
check_block_data(char *file, int line, int t_no,
unsigned char *p, Ulong sz, int d)
{
Ulong i;
for (i = 0; i < sz; i++)
if (p[i] != (unsigned char) d)
fail(t_no, "%s:%d: Thread no %d found clobbered data! "
"found id=%d; expected id=%d\n",
file, line, t_no, (int) p[i], d);
}
static void
alloc_op(int t_no, Allctr_t *a, block *bp, int id, int clean_up)
{
if (tc_failed)
exit_thread(t_no, 1);
if(bp->p)
CHECK_BLOCK_DATA(t_no, bp->p, bp->s, id);
if(bp->as[bp->i] == 0 || clean_up) {
FREE(a, bp->p);
bp->p = NULL;
bp->s = 0;
bp->i = 0; /* start from the beginning again */
return;
}
if(!bp->p) {
bp->s = bp->as[bp->i];
bp->p = (unsigned char *) ALLOC(a, bp->s);
if(!bp->p)
fail(t_no, "ALLOC(%lu) failed [id=%d])\n", bp->s, id);
memset((void *) bp->p, id, (size_t) bp->s);
}
else {
unsigned char *p = (unsigned char *) REALLOC(a, bp->p, bp->as[bp->i]);
if(!p)
fail(t_no, "REALLOC(0x%lx, %lu) failed [id=%d]\n",
(Ulong) bp->p, bp->as[bp->i], id);
if(bp->s < bp->as[bp->i]) {
CHECK_BLOCK_DATA(t_no, p, bp->s, id);
memset((void *) p, id, (size_t) bp->as[bp->i]);
}
else
CHECK_BLOCK_DATA(t_no, p, bp->as[bp->i], id);
bp->s = bp->as[bp->i];
bp->p = p;
}
bp->i++;
}
void *
thread_func(void *arg)
{
int i, j;
ThreadData *td = ((ThreadData *) arg);
block bs[NO_OF_BLOCKS];
for(i = 0; i < NO_OF_BLOCKS; i++) {
bs[i].p = NULL;
bs[i].s = 0;
bs[i].i = 0;
bs[i].as = alloc_seqs[i % NO_OF_ALLOC_SEQS];
}
for(i = 0; i < td->no_ops_per_bl; i++) {
for(j = 0; j < NO_OF_BLOCKS; j++)
alloc_op(td->t_no, td->a, &bs[j], BLOCK_ID(td->t_no, j), 0);
}
for(j = 0; j < NO_OF_BLOCKS; j++)
alloc_op(td->t_no, td->a, &bs[j], BLOCK_ID(td->t_no, j), 1);
exit_thread(td->t_no, 1);
return NULL;
}