/*
* Copyright (c) 2004, 2005 Sendmail, Inc. and its suppliers.
* All rights reserved.
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the sendmail distribution.
*/
#include "sm/generic.h"
SM_RCSID("@(#)$Id: t-wait-0.c,v 1.9 2006/04/02 06:58:36 ca Exp $")
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/heap.h"
#include "sm/test.h"
#include "sm/evthr.h"
#include "sm/queue.h"
#include <stdio.h>
/*
2004-10-23 priority: 8
libevthr: test: minthr=1, maxthr=2
start one task twice which
- starts another task that will eventually signal a cv
- returns "async" and waits for a cv
(as "sleep" task, 1s each?)
the "other" task should act like DNS lookups:
- create a request
- after a while answer the request
i.e., there is one task which reads requests from a queue and
invoke some callback function with some context;
creating a request means appending a structure to the list,
the struct contains the callback function and the context (some id?
in this case the cv should be sufficient)
2004-10-24
system can hang if "can block" is set for a task
because libevthr skips those when creating more threads
and it doesn't "remember" that another task (non-blocking)
wasn't created before?
*/
static int Verbose = 0;
static uint Dbglvl = 0;
/* "server" context */
struct s_ctx_S
{
pthread_mutex_t s_mutex;
timeval_T s_nextt;
TAILQ_HEAD(, r_req_S) s_head; /* list of requests */
};
typedef struct s_ctx_S s_ctx_T, *s_ctx_P;
/* "wait" context */
struct w_ctx_S
{
s_ctx_P w_s_ctx; /* pointer to "server" context */
int w_count;
int w_nresults;
timeval_T w_nextt;
};
typedef struct w_ctx_S w_ctx_T, *w_ctx_P;
/* result context */
struct a_res_S
{
pthread_mutex_t a_mutex;
pthread_cond_t a_cond;
int a_result;
};
typedef struct a_res_S a_res_T, *a_res_P;
/* requst list */
struct r_req_S
{
a_res_P r_a_res; /* pointer to result context */
TAILQ_ENTRY(r_req_S) r_link;
};
typedef struct r_req_S r_req_T, *r_req_P;
static sm_ret_T
w_new(w_ctx_P *pw_ctx)
{
w_ctx_P w_ctx;
SM_REQUIRE(pw_ctx != NULL);
w_ctx = (w_ctx_P) sm_zalloc(sizeof(*w_ctx));
if (w_ctx == NULL)
return sm_error_temp(SM_EM_EVTHR, ENOMEM);
*pw_ctx = w_ctx;
return SM_SUCCESS;
}
static sm_ret_T
a_new(a_res_P *pa_res)
{
int r;
a_res_P a_res;
SM_REQUIRE(pa_res != NULL);
a_res = (a_res_P) sm_zalloc(sizeof(*a_res));
if (a_res == NULL)
return sm_error_temp(SM_EM_EVTHR, ENOMEM);
r = pthread_mutex_init(&a_res->a_mutex, NULL);
SM_TEST(r == 0);
if (r != 0)
goto error;
r = pthread_cond_init(&a_res->a_cond, NULL);
SM_TEST(r == 0);
if (r != 0)
goto error;
*pa_res = a_res;
return SM_SUCCESS;
error:
return sm_error_perm(SM_EM_EVTHR, r);
}
static sm_ret_T
s_new(s_ctx_P *ps_ctx)
{
int r;
s_ctx_P s_ctx;
SM_REQUIRE(ps_ctx != NULL);
s_ctx = (s_ctx_P) sm_zalloc(sizeof(*s_ctx));
if (s_ctx == NULL)
return sm_error_temp(SM_EM_EVTHR, ENOMEM);
r = pthread_mutex_init(&s_ctx->s_mutex, NULL);
SM_TEST(r == 0);
if (r != 0)
goto error;
TAILQ_INIT(&s_ctx->s_head);
*ps_ctx = s_ctx;
return SM_SUCCESS;
error:
return sm_error_perm(SM_EM_EVTHR, r);
}
static sm_ret_T
r_new(r_req_P *pr_req)
{
r_req_P r_req;
SM_REQUIRE(pr_req != NULL);
r_req = (r_req_P) sm_zalloc(sizeof(*r_req));
if (r_req == NULL)
return sm_error_temp(SM_EM_EVTHR, ENOMEM);
*pr_req = r_req;
return SM_SUCCESS;
}
static sm_ret_T
notify(a_res_P a_res)
{
sm_ret_T ret;
int r;
SM_REQUIRE(a_res != NULL);
ret = SM_SUCCESS;
if (Verbose > 2)
fprintf(stderr, "func=notify, a_res=%p\n", a_res);
r = pthread_mutex_lock(&a_res->a_mutex);
if (Verbose > 2)
fprintf(stderr, "func=notify, a_res=%p, lock=%d\n", a_res, r);
SM_TEST(r == 0);
if (r != 0)
return sm_error_perm(SM_EM_EVTHR, r);
r = pthread_cond_signal(&a_res->a_cond);
if (Verbose > 0)
fprintf(stderr, "func=notify, a_res=%p, cond_signal=%d\n",
a_res, r);
SM_TEST(r == 0);
r = pthread_mutex_unlock(&a_res->a_mutex);
SM_TEST(r == 0);
return ret;
}
static sm_ret_T
get_result(a_res_P a_res)
{
int r;
if (Verbose > 2)
fprintf(stderr, "func=get_result, a_res=%p\n", a_res);
r = pthread_mutex_lock(&a_res->a_mutex);
if (r != 0)
{
/* LOG something! */
return sm_error_perm(SM_EM_EVTHR, r);
}
if (a_res->a_result == 0)
{
while (a_res->a_result == 0)
{
if (Verbose > 1)
fprintf(stderr,
"func=get_result, a_res=%p, where=before_cond_wait\n"
, a_res);
r = pthread_cond_wait(&a_res->a_cond,
&a_res->a_mutex);
if (Verbose > 1)
fprintf(stderr,
"func=get_result, a_res=%p, wait=%d\n"
, a_res, r);
if (r == EINVAL)
break;
if (r == 0)
{
r = pthread_mutex_unlock(&a_res->a_mutex);
SM_ASSERT(r == 0);
}
}
}
else
{
r = pthread_mutex_unlock(&a_res->a_mutex);
SM_ASSERT(r == 0);
}
if (r == 0)
return SM_SUCCESS;
return sm_error_perm(SM_EM_EVTHR, r);
}
static sm_ret_T
add_request(s_ctx_P s_ctx, a_res_P a_res)
{
sm_ret_T ret;
int r;
r_req_P r_req;
SM_REQUIRE(s_ctx != NULL);
SM_REQUIRE(a_res != NULL);
ret = r_new(&r_req);
SM_TEST(ret == SM_SUCCESS);
if (sm_is_err(ret))
return ret;
r_req->r_a_res = a_res;
if (Verbose > 2)
fprintf(stderr, "func=add_request, s_ctx=%p, a_res=%p\n"
, s_ctx, a_res);
r = pthread_mutex_lock(&s_ctx->s_mutex);
SM_TEST(r == 0);
if (r != 0)
return sm_error_perm(SM_EM_EVTHR, r);
TAILQ_INSERT_TAIL(&s_ctx->s_head, r_req, r_link);
r = pthread_mutex_unlock(&s_ctx->s_mutex);
SM_TEST(r == 0);
return ret;
}
static sm_ret_T
fctw(sm_evthr_task_P tsk)
{
int r;
sm_ret_T ret;
w_ctx_P w_ctx;
timeval_T sleept, delay;
SM_ASSERT(tsk != NULL);
#if 0
/* may not be true because task is appended to waitq and started again */
SM_TEST(evthr_got_slp(tsk));
#endif
w_ctx = (w_ctx_P) tsk->evthr_t_actx;
SM_ASSERT(w_ctx != NULL);
++w_ctx->w_count;
r = gettimeofday(&sleept, NULL);
SM_TEST(r == 0);
if (Verbose > 2)
fprintf(stderr, "func=fctw, now=%ld, w_ctx=%p, count=%d\n"
, sleept.tv_sec, w_ctx, w_ctx->w_count);
delay.tv_sec = 0;
delay.tv_usec = 0;
#if 0
timeradd(&sleept, &delay, &tsk->evthr_t_sleep);
#endif
if (w_ctx->w_count < 3)
{
a_res_P a_res;
ret = a_new(&a_res);
SM_TEST(sm_is_success(ret));
if (sm_is_err(ret))
return ret;
ret = add_request(w_ctx->w_s_ctx, a_res);
SM_TEST(sm_is_success(ret));
if (Verbose > 4)
fprintf(stderr, "func=fctw, now=%ld, w_ctx=%p"
", a_res=%p"
", evthr_t_rqevf=%X"
", evthr_t_evocc=%X"
", evthr_t_state=%X"
", evthr_t_flags=%X"
", evthr_t_sleep=%ld"
"\n"
, sleept.tv_sec
, w_ctx
, a_res
, tsk->evthr_t_rqevf
, tsk->evthr_t_evocc
, tsk->evthr_t_state
, tsk->evthr_t_flags
, tsk->evthr_t_sleep.tv_sec
);
ret = evthr_waitq_app(tsk);
SM_TEST(sm_is_success(ret));
if (Verbose > 2)
fprintf(stderr,
"func=fctw, now=%ld, w_ctx=%p, evthr_waitq_app=%X\n"
, sleept.tv_sec, w_ctx, ret);
ret = evthr_new_sl(tsk, sleept, true);
if (Verbose > 2)
fprintf(stderr,
"func=fctw, now=%ld, w_ctx=%p, evthr_new_sl=%X\n"
, sleept.tv_sec, w_ctx, ret);
sleep(10);
ret = get_result(a_res);
SM_TEST(sm_is_success(ret));
++w_ctx->w_nresults;
r = gettimeofday(&sleept, NULL);
SM_TEST(r == 0);
if (Verbose > 0)
fprintf(stderr,
"func=fctw, now=%ld, w_ctx=%p, get_result=%X, a_result=%d\n"
, sleept.tv_sec, w_ctx, ret, a_res->a_result);
if (w_ctx->w_nresults >= 2)
exit(0); /* HACK... */
return EVTHR_OK;
}
delay.tv_sec = 20;
delay.tv_usec = 0;
timeradd(&sleept, &delay, &tsk->evthr_t_sleep);
w_ctx->w_nextt = tsk->evthr_t_sleep;
if (Verbose > 2)
fprintf(stderr,
"func=fctw, now=%ld, w_ctx=%p, next=%lds\n"
, sleept.tv_sec, w_ctx, delay.tv_sec);
return EVTHR_SLPQ;
}
static sm_ret_T
fctsrv(sm_evthr_task_P tsk)
{
int r;
s_ctx_P s_ctx;
timeval_T sleept, delay;
r_req_P r_req;
static int result = 0;
SM_ASSERT(tsk != NULL);
SM_TEST(evthr_got_slp(tsk));
s_ctx = (s_ctx_P) tsk->evthr_t_actx;
SM_ASSERT(s_ctx != NULL);
r = gettimeofday(&sleept, NULL);
SM_TEST(r == 0);
if (Verbose > 2)
fprintf(stderr, "func=fctsrv, now=%ld, s_ctx=%p\n"
, sleept.tv_sec, s_ctx);
r = pthread_mutex_lock(&s_ctx->s_mutex);
SM_TEST(r == 0);
if (r == 0 && !TAILQ_EMPTY(&s_ctx->s_head))
{
r_req = TAILQ_FIRST(&s_ctx->s_head);
TAILQ_REMOVE(&s_ctx->s_head, r_req, r_link);
r = pthread_mutex_unlock(&s_ctx->s_mutex);
SM_TEST(r == 0);
if (Verbose > 1)
fprintf(stderr,
"func=fctsrv, now=%ld, s_ctx=%p, r_req=%p\n"
, sleept.tv_sec, s_ctx, r_req);
r_req->r_a_res->a_result = ++result;
notify(r_req->r_a_res);
}
r = gettimeofday(&sleept, NULL);
SM_TEST(r == 0);
delay.tv_sec = 2;
delay.tv_usec = 0;
timeradd(&sleept, &delay, &tsk->evthr_t_sleep);
s_ctx->s_nextt = tsk->evthr_t_sleep;
return EVTHR_SLPQ;
}
static sm_ret_T
fctstop(sm_evthr_task_P tsk)
{
int r;
timeval_T sleept;
SM_ASSERT(tsk != NULL);
SM_ASSERT(evthr_got_slp(tsk));
r = gettimeofday(&sleept, NULL);
SM_TEST(r == 0);
fprintf(stderr, "func=fctstop, now=%ld, state=ERROR\n", sleept.tv_sec);
exit(1);
}
static void
testwait(bool hard, bool canblock)
{
sm_ret_T ret;
int r;
sm_evthr_ctx_P evthr_ctx;
sm_evthr_task_P task1, task2, task3;
w_ctx_P w_ctx1;
s_ctx_P s_ctx1;
timeval_T sleept, nextt, delay;
evthr_ctx = NULL;
ret = thr_init();
SM_TEST(sm_is_success(ret));
sm_memzero(&sleept, sizeof(sleept));
ret = evthr_init(&evthr_ctx, 1, 2, 2);
SM_TEST(sm_is_success(ret));
SM_TEST(evthr_ctx != NULL);
if (sm_is_err(ret))
goto err2;
evthr_set_dbglvl(evthr_ctx, Dbglvl);
if (hard)
{
ret = evthr_set_max_h(evthr_ctx, 3);
SM_TEST(sm_is_success(ret));
}
/* "request server" function: sleep 20s before doing something */
ret = s_new(&s_ctx1);
SM_TEST(sm_is_success(ret));
SM_TEST(s_ctx1 != NULL);
if (sm_is_err(ret))
goto error;
r = gettimeofday(&sleept, NULL);
SM_TEST(r == 0);
sleept.tv_sec += 20;
s_ctx1->s_nextt = sleept;
ret = evthr_task_new(evthr_ctx, &task2, EVTHR_EV_SL, -1, &sleept,
fctsrv, (void *) s_ctx1);
SM_TEST(sm_is_success(ret));
SM_TEST(task2 != NULL);
if (sm_is_err(ret))
goto error;
/* "lock" function: sleep 1s before doing something */
ret = w_new(&w_ctx1);
SM_TEST(sm_is_success(ret));
SM_TEST(w_ctx1 != NULL);
if (sm_is_err(ret))
goto error;
w_ctx1->w_s_ctx = s_ctx1;
r = gettimeofday(&sleept, NULL);
SM_TEST(r == 0);
delay.tv_sec = 0;
delay.tv_usec = 1000;
timeradd(&sleept, &delay, &nextt);
w_ctx1->w_nextt = nextt;
ret = evthr_task_new(evthr_ctx, &task1, EVTHR_EV_SL, -1, &nextt,
fctw, (void *) w_ctx1);
SM_TEST(sm_is_success(ret));
SM_TEST(task1 != NULL);
if (sm_is_err(ret))
goto error;
if (canblock)
{
EVTHRT_SET_FLAG(task1, EVTHRT_FL_BLOCK);
if (Verbose > 2)
fprintf(stderr, "func=testwait, task=%p, flags=%x\n",
task1, task1->evthr_t_flags);
}
/* "stop" function: sleep 60s before stopping system (emergency stop) */
r = gettimeofday(&sleept, NULL);
SM_TEST(r == 0);
delay.tv_sec = 60;
delay.tv_usec = 0;
timeradd(&sleept, &delay, &nextt);
ret = evthr_task_new(evthr_ctx, &task3, EVTHR_EV_SL, -1, &nextt,
fctstop, (void *) NULL);
SM_TEST(sm_is_success(ret));
SM_TEST(task3 != NULL);
if (sm_is_err(ret))
goto error;
ret = evthr_loop(evthr_ctx);
SM_TEST(sm_is_success(ret));
if (!sm_is_success(ret))
fprintf(stderr, "evthr_loop=%x\n", ret);
error:
if (evthr_ctx != NULL)
{
ret = evthr_stop(evthr_ctx);
SM_TEST(sm_is_success(ret));
if (!sm_is_success(ret))
fprintf(stderr, "evthr_stop=%x\n", ret);
evthr_ctx = NULL;
}
err2:
ret = thr_stop();
SM_TEST(sm_is_success(ret));
}
int
main(int argc, char *argv[])
{
int c;
bool hard, canblock;
hard = false;
canblock = false;
while ((c = getopt(argc, argv, "bd:hV")) != -1)
{
switch (c)
{
case 'b':
canblock = true;
break;
case 'd':
Dbglvl = atoi(optarg);
break;
case 'h':
hard = true;
break;
case 'V':
Verbose++;
break;
#if 0
default:
usage(argv[0]);
return(1);
#endif /* 0 */
}
}
sm_test_begin(argc, argv, "test wait with deadlock avoidance");
testwait(hard, canblock);
return sm_test_end();
}
syntax highlighted by Code2HTML, v. 0.9.1