Add code to handle timer_delete(). The timer wrapper code is completely
rewritten, now timers created with same sigev_notify_attributes will run in same thread, this allows user to organize which timers can run in same thread to save some thread resource.
This commit is contained in:
parent
5df2705458
commit
53bbdf8646
@ -179,6 +179,7 @@ global:
|
||||
_system;
|
||||
_tcdrain;
|
||||
_timer_create;
|
||||
_timer_delete;
|
||||
_usleep;
|
||||
_vfork;
|
||||
_wait;
|
||||
@ -345,6 +346,7 @@ global:
|
||||
system;
|
||||
tcdrain;
|
||||
timer_create;
|
||||
timer_delete;
|
||||
usleep;
|
||||
vfork;
|
||||
wait;
|
||||
|
@ -389,6 +389,7 @@ init_private(void)
|
||||
_thr_umtx_init(&_thr_event_lock);
|
||||
_thr_spinlock_init();
|
||||
_thr_list_init();
|
||||
_thr_timer_init();
|
||||
|
||||
/*
|
||||
* Avoid reinitializing some things if they don't need to be,
|
||||
|
@ -728,14 +728,15 @@ void _thr_signal_unblock(struct pthread *);
|
||||
void _thr_signal_init(void);
|
||||
void _thr_signal_deinit(void);
|
||||
int _thr_send_sig(struct pthread *, int sig);
|
||||
void _thr_list_init();
|
||||
void _thr_list_init(void);
|
||||
void _thr_hash_add(struct pthread *);
|
||||
void _thr_hash_remove(struct pthread *);
|
||||
struct pthread *_thr_hash_find(struct pthread *);
|
||||
void _thr_link(struct pthread *curthread, struct pthread *thread);
|
||||
void _thr_unlink(struct pthread *curthread, struct pthread *thread);
|
||||
void _thr_suspend_check(struct pthread *curthread);
|
||||
void _thr_assert_lock_level() __dead2;
|
||||
void _thr_assert_lock_level(void) __dead2;
|
||||
void _thr_timer_init(void);
|
||||
void _thr_report_creation(struct pthread *curthread,
|
||||
struct pthread *newthread);
|
||||
void _thr_report_death(struct pthread *curthread);
|
||||
|
@ -28,31 +28,73 @@
|
||||
*/
|
||||
|
||||
#include <time.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/signalvar.h>
|
||||
|
||||
#include "thr_private.h"
|
||||
|
||||
struct timer {
|
||||
int signo;
|
||||
union sigval value;
|
||||
void (*function)(union sigval *);
|
||||
struct thread_node {
|
||||
struct pthread_attr attr;
|
||||
TAILQ_ENTRY(thread_node) link;
|
||||
pthread_t thread;
|
||||
int refcount;
|
||||
int exit;
|
||||
int timerid;
|
||||
umtx_t lock;
|
||||
jmp_buf jbuf;
|
||||
struct timer *curtmr;
|
||||
};
|
||||
|
||||
extern int __sys_timer_create(clockid_t clockid, struct sigevent *evp,
|
||||
timer_t *timerid);
|
||||
struct timer {
|
||||
union sigval value;
|
||||
void (*function)(union sigval *, int);
|
||||
int timerid;
|
||||
long flags;
|
||||
int gen;
|
||||
struct thread_node *tn;
|
||||
};
|
||||
|
||||
static void *timer_loop(void *arg);
|
||||
static struct timer **timer_list;
|
||||
static int timer_gen;
|
||||
static int timer_max;
|
||||
static umtx_t timer_list_lock;
|
||||
static TAILQ_HEAD(,thread_node) timer_threads;
|
||||
static umtx_t timer_threads_lock;
|
||||
|
||||
static void *service_loop(void *);
|
||||
static int register_timer(struct timer *);
|
||||
static struct thread_node *create_timer_thread(pthread_attr_t);
|
||||
static void release_timer_thread(struct thread_node *);
|
||||
|
||||
extern int __sys_timer_create(clockid_t, struct sigevent *, timer_t *);
|
||||
extern int __sys_timer_delete(timer_t);
|
||||
|
||||
__weak_reference(__timer_create, timer_create);
|
||||
__weak_reference(__timer_create, _timer_create);
|
||||
__weak_reference(__timer_delete, timer_delete);
|
||||
__weak_reference(__timer_delete, _timer_delete);
|
||||
|
||||
#define SIGTIMER SIGCANCEL /* Reuse SIGCANCEL */
|
||||
|
||||
#define WORKING 0x01
|
||||
#define WANTED 0x02
|
||||
|
||||
#define TIMERS_LOCK(t) THR_UMTX_LOCK((t), &timer_list_lock)
|
||||
#define TIMERS_UNLOCK(t) THR_UMTX_UNLOCK((t), &timer_list_lock)
|
||||
|
||||
#define THREADS_LOCK(t) THR_UMTX_LOCK((t), &timer_threads_lock)
|
||||
#define THREADS_UNLOCK(t) THR_UMTX_UNLOCK((t), &timer_threads_lock)
|
||||
|
||||
void
|
||||
_thr_timer_init(void)
|
||||
{
|
||||
_thr_umtx_init(&timer_list_lock);
|
||||
_thr_umtx_init(&timer_threads_lock);
|
||||
TAILQ_INIT(&timer_threads);
|
||||
timer_list = NULL;
|
||||
timer_max = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Purpose of the function is to implement POSIX timer's
|
||||
* SEGEV_THREAD notification mechanism.
|
||||
@ -60,12 +102,9 @@ __weak_reference(__timer_create, _timer_create);
|
||||
int
|
||||
__timer_create(clockid_t clockid, struct sigevent *evp, timer_t *timerid)
|
||||
{
|
||||
struct pthread *curthread = _get_curthread();
|
||||
pthread_attr_t attr;
|
||||
struct sigevent ev;
|
||||
struct timer *tmr;
|
||||
pthread_t newtd;
|
||||
sigset_t set, oset;
|
||||
int ret;
|
||||
|
||||
/* Call syscall directly if it is not SIGEV_THREAD */
|
||||
@ -78,93 +117,250 @@ __timer_create(clockid_t clockid, struct sigevent *evp, timer_t *timerid)
|
||||
errno = EAGAIN;
|
||||
return (-1);
|
||||
}
|
||||
tmr->signo = SIGTIMER;
|
||||
tmr->value = evp->sigev_value;
|
||||
tmr->function = evp->sigev_notify_function;
|
||||
tmr->exit = 0;
|
||||
/* XXX
|
||||
* Here we pass second parameter an overrun count, this is
|
||||
* not required by POSIX.
|
||||
*/
|
||||
tmr->function = (void (*)(union sigval *, int))
|
||||
evp->sigev_notify_function;
|
||||
tmr->flags = 0;
|
||||
tmr->timerid = -1;
|
||||
_thr_umtx_init(&tmr->lock);
|
||||
pthread_attr_init(&attr);
|
||||
if (evp->sigev_notify_attributes != NULL) {
|
||||
*attr = **(pthread_attr_t *)(evp->sigev_notify_attributes);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
||||
}
|
||||
/*
|
||||
* Lock the mutex, so new thread can not continue until
|
||||
* we have fully setup it.
|
||||
*/
|
||||
THR_UMTX_LOCK(curthread, &tmr->lock);
|
||||
|
||||
/*
|
||||
* Block signal the timer will fire for new thread, new thread
|
||||
* will use sigwaitinfo, signal action should not be invoked.
|
||||
*
|
||||
* Application user: if you want set signal mask for the
|
||||
* background thread, please call sigprocmask for current before
|
||||
* calling timer_create, this way, the signal mask will be inherited
|
||||
* by new thread.
|
||||
*/
|
||||
SIGEMPTYSET(set);
|
||||
SIGADDSET(set, tmr->signo);
|
||||
__sys_sigprocmask(SIG_BLOCK, &set, &oset);
|
||||
ret = _pthread_create(&newtd, &attr, timer_loop, tmr);
|
||||
__sys_sigprocmask(SIG_SETMASK, &oset, NULL);
|
||||
pthread_attr_destroy(&attr);
|
||||
if (__predict_false(ret != 0)) {
|
||||
THR_UMTX_UNLOCK(curthread, &tmr->lock);
|
||||
tmr->gen = atomic_fetchadd_int(&timer_gen, 1);
|
||||
tmr->tn = create_timer_thread(attr);
|
||||
if (tmr->tn == NULL) {
|
||||
free(tmr);
|
||||
errno = ret;
|
||||
errno = EAGAIN;
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Build a new sigevent, and tell kernel to deliver
|
||||
* SIGTIMER signal to the new thread.
|
||||
*/
|
||||
ev.sigev_notify = SIGEV_THREAD_ID;
|
||||
ev.sigev_signo = tmr->signo;
|
||||
ev.sigev_notify_thread_id = (int)newtd->tid;
|
||||
ev.sigev_value.sigval_ptr = tmr;
|
||||
ret = __sys_timer_create(clockid, &ev, timerid);
|
||||
if (ret != 0) {
|
||||
ev.sigev_signo = SIGTIMER;
|
||||
ev.sigev_notify_thread_id = (lwpid_t)tmr->tn->thread->tid;
|
||||
ev.sigev_value.sigval_int = tmr->gen;
|
||||
ret = __sys_timer_create(clockid, &ev, &tmr->timerid);
|
||||
if (ret != 0 || register_timer(tmr) != 0) {
|
||||
ret = errno;
|
||||
tmr->exit = 1;
|
||||
THR_UMTX_UNLOCK(curthread, &tmr->lock);
|
||||
pthread_join(newtd, NULL);
|
||||
release_timer_thread(tmr->tn);
|
||||
free(tmr);
|
||||
errno = ret;
|
||||
return (-1);
|
||||
}
|
||||
tmr->timerid = *timerid;
|
||||
/*
|
||||
* As specification says, the service thread should run in
|
||||
* detached state, so you lose control of the thread!
|
||||
*/
|
||||
pthread_detach(newtd);
|
||||
THR_UMTX_UNLOCK(curthread, &tmr->lock);
|
||||
*timerid = tmr->timerid;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Thread function to serve SEGEV_THREAD notifcation. */
|
||||
static void *
|
||||
timer_loop(void *arg)
|
||||
int
|
||||
__timer_delete(timer_t timerid)
|
||||
{
|
||||
struct pthread *curthread = _get_curthread();
|
||||
struct timer *tmr = arg;
|
||||
struct timer *tmr = NULL;
|
||||
long flags;
|
||||
|
||||
TIMERS_LOCK(curthread);
|
||||
/*
|
||||
* Check if this is a SIGEV_THREAD timer by looking up
|
||||
* it in the registered list.
|
||||
*/
|
||||
if (timerid >= 0 && timerid < timer_max &&
|
||||
(tmr = timer_list[timerid]) != NULL) {
|
||||
/* Take it from timer list */
|
||||
timer_list[timerid] = NULL;
|
||||
/* If the timer is servicing, allow it to complete. */
|
||||
while ((flags = tmr->flags) & WORKING) {
|
||||
tmr->flags |= WANTED;
|
||||
TIMERS_UNLOCK(curthread);
|
||||
_thr_umtx_wait(&tmr->flags, flags, NULL);
|
||||
TIMERS_LOCK(curthread);
|
||||
}
|
||||
TIMERS_UNLOCK(curthread);
|
||||
/*
|
||||
* Drop reference count of servicing thread,
|
||||
* may free the the thread.
|
||||
*/
|
||||
release_timer_thread(tmr->tn);
|
||||
} else
|
||||
TIMERS_UNLOCK(curthread);
|
||||
if (tmr != NULL)
|
||||
free(tmr);
|
||||
return (__sys_timer_delete(timerid));
|
||||
}
|
||||
|
||||
static struct thread_node *
|
||||
create_timer_thread(pthread_attr_t attr)
|
||||
{
|
||||
struct pthread *curthread = _get_curthread();
|
||||
struct thread_node *tn;
|
||||
int ret;
|
||||
|
||||
THREADS_LOCK(curthread);
|
||||
/* Search a thread matching the required pthread_attr. */
|
||||
TAILQ_FOREACH(tn, &timer_threads, link) {
|
||||
if (attr->stackaddr_attr == NULL) {
|
||||
if (attr->sched_policy == tn->attr.sched_policy &&
|
||||
attr->sched_inherit == tn->attr.sched_inherit &&
|
||||
attr->prio == tn->attr.prio &&
|
||||
attr->stacksize_attr == tn->attr.stacksize_attr &&
|
||||
attr->guardsize_attr == tn->attr.guardsize_attr &&
|
||||
((attr->flags & PTHREAD_SCOPE_SYSTEM) ==
|
||||
(tn->attr.flags & PTHREAD_SCOPE_SYSTEM)))
|
||||
break;
|
||||
} else {
|
||||
/*
|
||||
* Reuse the thread if it has same stack address,
|
||||
* because two threads can not run on same stack.
|
||||
*/
|
||||
if (attr->stackaddr_attr == tn->attr.stackaddr_attr)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tn != NULL) {
|
||||
tn->refcount++;
|
||||
THREADS_UNLOCK(curthread);
|
||||
return (tn);
|
||||
}
|
||||
tn = malloc(sizeof(*tn));
|
||||
tn->refcount = 1;
|
||||
tn->exit = 0;
|
||||
tn->attr = *attr;
|
||||
tn->curtmr = NULL;
|
||||
_thr_signal_block(curthread); /* SIGTIMER is also blocked. */
|
||||
TAILQ_INSERT_TAIL(&timer_threads, tn, link);
|
||||
ret = _pthread_create(&tn->thread, &attr, service_loop, tn);
|
||||
_thr_signal_unblock(curthread);
|
||||
if (ret != 0) {
|
||||
TAILQ_REMOVE(&timer_threads, tn, link);
|
||||
free(tn);
|
||||
tn = NULL;
|
||||
}
|
||||
THREADS_UNLOCK(curthread);
|
||||
return (tn);
|
||||
}
|
||||
|
||||
static void
|
||||
release_timer_thread(struct thread_node *tn)
|
||||
{
|
||||
struct pthread *curthread = _get_curthread();
|
||||
struct pthread *th;
|
||||
|
||||
THREADS_LOCK(curthread);
|
||||
if (--tn->refcount == 0) {
|
||||
/*
|
||||
* If I am the last user, current implement kills the
|
||||
* service thread, is this allowed by POSIX ? does
|
||||
* this hurt performance ?
|
||||
*/
|
||||
tn->exit = 1;
|
||||
th = tn->thread;
|
||||
_thr_send_sig(th, SIGTIMER);
|
||||
pthread_join(th, NULL);
|
||||
TAILQ_REMOVE(&timer_threads, tn, link);
|
||||
}
|
||||
THREADS_UNLOCK(curthread);
|
||||
}
|
||||
|
||||
/* Register a SIGEV_THREAD timer. */
|
||||
static int
|
||||
register_timer(struct timer *tmr)
|
||||
{
|
||||
struct pthread *curthread = _get_curthread();
|
||||
struct timer **list;
|
||||
int count;
|
||||
|
||||
while ((count = timer_max) <= tmr->timerid) {
|
||||
if (count < 32)
|
||||
count = 32;
|
||||
while (count <= tmr->timerid)
|
||||
count <<= 1;
|
||||
list = malloc(count * sizeof(void *));
|
||||
memset(list, 0, count * sizeof(void *));
|
||||
if (list == NULL)
|
||||
return (-1);
|
||||
TIMERS_LOCK(curthread);
|
||||
if (timer_max >= count) {
|
||||
TIMERS_UNLOCK(curthread);
|
||||
free(list);
|
||||
continue;
|
||||
}
|
||||
memcpy(timer_list, list, timer_max * sizeof(void *));
|
||||
timer_list = list;
|
||||
timer_max = count;
|
||||
THR_UMTX_UNLOCK(curthread, &timer_list_lock);
|
||||
}
|
||||
TIMERS_LOCK(curthread);
|
||||
timer_list[tmr->timerid] = tmr;
|
||||
TIMERS_UNLOCK(curthread);
|
||||
return (0);
|
||||
}
|
||||
|
||||
static void
|
||||
cleanup_thread(void *arg)
|
||||
{
|
||||
struct pthread *curthread = _get_curthread();
|
||||
struct thread_node *tn = arg;
|
||||
|
||||
if (tn->exit == 0) {
|
||||
/* broken usercode is killing us. */
|
||||
if (tn->curtmr) {
|
||||
TIMERS_LOCK(curthread);
|
||||
tn->curtmr->flags &= ~WORKING;
|
||||
if (tn->curtmr->flags & WANTED)
|
||||
_thr_umtx_wake(&tn->curtmr->flags, INT_MAX);
|
||||
TIMERS_UNLOCK(curthread);
|
||||
}
|
||||
atomic_clear_int(&curthread->cancelflags, THR_CANCEL_EXITING);
|
||||
longjmp(tn->jbuf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void *
|
||||
service_loop(void *arg)
|
||||
{
|
||||
struct pthread *curthread = _get_curthread();
|
||||
struct thread_node *tn = arg;
|
||||
struct timer *tmr;
|
||||
siginfo_t si;
|
||||
sigset_t set;
|
||||
|
||||
THR_CLEANUP_PUSH(curthread, free, tmr);
|
||||
THR_UMTX_LOCK(curthread, &tmr->lock);
|
||||
THR_UMTX_UNLOCK(curthread, &tmr->lock);
|
||||
SIGEMPTYSET(set);
|
||||
SIGADDSET(set, tmr->signo);
|
||||
while (tmr->exit == 0) {
|
||||
if (__sys_sigwaitinfo(&set, &si) != -1) {
|
||||
if (si.si_code == SI_TIMER &&
|
||||
si.si_timerid == tmr->timerid)
|
||||
tmr->function(&tmr->value);
|
||||
/*
|
||||
* service thread should not be killed by callback, if user
|
||||
* tries to do so, the thread will be restarted.
|
||||
*/
|
||||
setjmp(tn->jbuf);
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
|
||||
sigemptyset(&set);
|
||||
sigaddset(&set, SIGTIMER);
|
||||
THR_CLEANUP_PUSH(curthread, cleanup_thread, tn);
|
||||
while (tn->exit == 0) {
|
||||
if (__predict_false(__sys_sigwaitinfo(&set, &si) == -1 ||
|
||||
si.si_code != SI_TIMER))
|
||||
continue;
|
||||
TIMERS_LOCK(curthread);
|
||||
if (si.si_timerid >= 0 && si.si_timerid < timer_max &&
|
||||
(tmr = timer_list[si.si_timerid]) != NULL &&
|
||||
si.si_value.sigval_int == tmr->gen) {
|
||||
tmr->flags |= WORKING;
|
||||
TIMERS_UNLOCK(curthread);
|
||||
tn->curtmr = tmr;
|
||||
tmr->function(&tmr->value, si.si_overrun);
|
||||
tn->curtmr = NULL;
|
||||
TIMERS_LOCK(curthread);
|
||||
tmr->flags &= ~WORKING;
|
||||
if (tmr->flags & WANTED)
|
||||
_thr_umtx_wake(&tmr->flags, INT_MAX);
|
||||
}
|
||||
TIMERS_UNLOCK(curthread);
|
||||
}
|
||||
THR_CLEANUP_POP(curthread, 0);
|
||||
free(tmr);
|
||||
THR_CLEANUP_POP(curthread, 1);
|
||||
return (0);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user