head 1.62; access; symbols PTH_2_0_7:1.61 PTH_2_0_6:1.60 PTH_2_0_5:1.59 PTH_2_0_4:1.59 PTH_2_0_3:1.58 PTH_2_0_2:1.57 PTH_2_0_1:1.57 PTH_2_0_0:1.56 PTH_2_0b2:1.55 PTH_2_0b1:1.54 PTH_2_0b0:1.54 PTH_1_4:1.47.0.2 PTH_1_4_1:1.47 PTH_1_4_0:1.46 PTH_1_3_7:1.42 PTH_1_4a3:1.44 PTH_1_3_6:1.42 PTH_1_4a2:1.44 PTH_1_3_5:1.42 PTH_1_4a1:1.44 PTH_1_3_4:1.42 PTH_1_3:1.42.0.2 PTH_1_3_3:1.42 PTH_1_3_2:1.39 PTH_1_3_1:1.39 PTH_1_3_0:1.39 PTH_1_3b3:1.39 PTH_1_2_3:1.28.2.2 PTH_1_3b2:1.37 PTH_1_3b1:1.35 PTH_1_3a5:1.35 PTH_1_3a4:1.34 PTH_1_3a3:1.34 PTH_1_2_2:1.28.2.2 PTH_1_3a2:1.32 PTH_1_2_1:1.28.2.1 PTH_1_3a1:1.29 PTH_1_2:1.28.0.2 PTH_1_2_0:1.28 PTH_1_2b8:1.27 PTH_1_2b7:1.27 PTH_1_1_6:1.26 PTH_1_2b6:1.27 PTH_1_2b5:1.27 PTH_1_2b4:1.27 PTH_1_2b3:1.27 PTH_1_2b2:1.26 PTH_1_2b1:1.26 PTH_1_1_5:1.26 PTH_1_0_6:1.18 PTH_1_0_5:1.18 PTH_1_0:1.18.0.2 PTH_1_1:1.26.0.2 PTH_1_1_4:1.26 PTH_1_1_3:1.26 PTH_1_1_2:1.24 PTH_1_1_1:1.23 PTH_1_1_0:1.22 PTH_1_1b7:1.21 PTH_1_1b6:1.21 PTH_1_1b5:1.21 PTH_1_1b4:1.20 PTH_1_1b3:1.20 PTH_1_1b2:1.20 PTH_1_1b1:1.20 PTH_1_0_4:1.18 PTH_1_0_3:1.18 PTH_1_0_2:1.17 PTH_1_0_1:1.17 PTH_1_0_0:1.16 PTH_1_0b8:1.14 PTH_1_0b7:1.10 PTH_1_0b6:1.10 PTH_1_0b5:1.10 PTH_1_0b4:1.9 PTH_1_0b3:1.7 PTH_1_0b2:1.6 PTH_1_0b1:1.2; locks; strict; comment @ * @; 1.62 date 2007.01.01.18.23.53; author rse; state Exp; branches; next 1.61; commitid 9DhdiirNzQPBIP0s; 1.61 date 2006.06.08.17.54.53; author rse; state Exp; branches; next 1.60; commitid x8N3mLVdQgkbdeAr; 1.60 date 2005.10.12.08.14.21; author rse; state Exp; branches; next 1.59; 1.59 date 2004.12.31.19.34.45; author rse; state Exp; branches; next 1.58; 1.58 date 2004.10.08.16.17.02; author rse; state Exp; branches; next 1.57; 1.57 date 2004.07.13.10.50.49; author rse; state Exp; branches; next 1.56; 1.56 date 2003.01.01.15.49.12; author rse; state Exp; branches; next 1.55; 1.55 date 2002.11.09.16.29.26; author rse; state Exp; branches; next 1.54; 1.54 date 2002.11.03.11.15.04; author rse; state Exp; branches; next 1.53; 1.53 date 2002.10.24.15.21.13; author rse; state Exp; branches; next 1.52; 1.52 date 2002.10.24.14.01.37; author rse; state Exp; branches; next 1.51; 1.51 date 2002.10.20.11.45.10; author rse; state Exp; branches; next 1.50; 1.50 date 2002.10.15.20.34.22; author rse; state Exp; branches; next 1.49; 1.49 date 2002.10.15.18.17.28; author rse; state Exp; branches; next 1.48; 1.48 date 2002.01.30.13.07.08; author rse; state Exp; branches; next 1.47; 1.47 date 2002.01.27.11.03.40; author rse; state Exp; branches; next 1.46; 1.46 date 2001.03.24.14.51.04; author rse; state Exp; branches; next 1.45; 1.45 date 2001.03.24.14.27.28; author rse; state Exp; branches; next 1.44; 1.44 date 2000.03.12.16.47.39; author rse; state Exp; branches; next 1.43; 1.43 date 2000.03.12.16.43.16; author rse; state Exp; branches; next 1.42; 1.42 date 2000.03.10.09.25.40; author rse; state Exp; branches; next 1.41; 1.41 date 2000.03.09.12.11.51; author rse; state Exp; branches; next 1.40; 1.40 date 2000.03.03.15.42.10; author rse; state Exp; branches; next 1.39; 1.39 date 2000.02.13.12.53.31; author rse; state Exp; branches; next 1.38; 1.38 date 2000.02.13.12.52.15; author rse; state Exp; branches; next 1.37; 1.37 date 2000.01.27.12.53.02; author rse; state Exp; branches; next 1.36; 1.36 date 2000.01.26.13.06.38; author rse; state Exp; branches; next 1.35; 1.35 date 2000.01.09.16.45.02; author rse; state Exp; branches; next 1.34; 1.34 date 2000.01.07.22.35.49; author rse; state Exp; branches; next 1.33; 1.33 date 2000.01.06.16.01.42; author rse; state Exp; branches; next 1.32; 1.32 date 99.12.30.21.59.00; author rse; state Exp; branches; next 1.31; 1.31 date 99.12.15.18.14.08; author rse; state Exp; branches; next 1.30; 1.30 date 99.11.09.08.11.31; author rse; state Exp; branches; next 1.29; 1.29 date 99.11.01.10.27.19; author rse; state Exp; branches; next 1.28; 1.28 date 99.10.31.11.46.12; author rse; state Exp; branches 1.28.2.1; next 1.27; 1.27 date 99.09.17.08.01.55; author rse; state Exp; branches; next 1.26; 1.26 date 99.08.27.15.28.15; author rse; state Exp; branches; next 1.25; 1.25 date 99.08.27.14.52.48; author rse; state Exp; branches; next 1.24; 1.24 date 99.08.23.12.00.53; author rse; state Exp; branches; next 1.23; 1.23 date 99.08.21.12.10.31; author rse; state Exp; branches; next 1.22; 1.22 date 99.08.19.15.08.52; author rse; state Exp; branches; next 1.21; 1.21 date 99.08.17.08.08.36; author rse; state Exp; branches; next 1.20; 1.20 date 99.08.05.14.13.10; author rse; state Exp; branches; next 1.19; 1.19 date 99.08.03.12.24.03; author rse; state Exp; branches; next 1.18; 1.18 date 99.07.30.07.13.59; author rse; state Exp; branches 1.18.2.1; next 1.17; 1.17 date 99.07.21.06.47.37; author rse; state Exp; branches; next 1.16; 1.16 date 99.07.16.14.10.56; author rse; state Exp; branches; next 1.15; 1.15 date 99.07.16.10.47.58; author rse; state Exp; branches; next 1.14; 1.14 date 99.07.16.08.16.49; author rse; state Exp; branches; next 1.13; 1.13 date 99.07.15.16.36.41; author rse; state Exp; branches; next 1.12; 1.12 date 99.07.15.16.15.39; author rse; state Exp; branches; next 1.11; 1.11 date 99.07.15.15.27.58; author rse; state Exp; branches; next 1.10; 1.10 date 99.07.08.12.28.09; author rse; state Exp; branches; next 1.9; 1.9 date 99.07.08.10.34.01; author rse; state Exp; branches; next 1.8; 1.8 date 99.07.08.10.19.11; author rse; state Exp; branches; next 1.7; 1.7 date 99.07.07.12.30.21; author rse; state Exp; branches; next 1.6; 1.6 date 99.07.04.12.05.35; author rse; state Exp; branches; next 1.5; 1.5 date 99.07.03.15.50.15; author rse; state Exp; branches; next 1.4; 1.4 date 99.07.03.12.47.38; author rse; state Exp; branches; next 1.3; 1.3 date 99.06.28.17.15.56; author rse; state Exp; branches; next 1.2; 1.2 date 99.06.28.11.36.26; author rse; state Exp; branches; next 1.1; 1.1 date 99.06.28.10.23.32; author rse; state Exp; branches; next ; 1.28.2.1 date 99.11.01.10.25.00; author rse; state Exp; branches; next 1.28.2.2; 1.28.2.2 date 2000.01.07.23.16.18; author rse; state Exp; branches; next ; 1.18.2.1 date 99.08.31.08.33.56; author rse; state Exp; branches; next ; desc @@ 1.62 log @Adjusted all copyright messages for new year 2007. @ text @/* ** GNU Pth - The GNU Portable Threads ** Copyright (c) 1999-2007 Ralf S. Engelschall ** ** This file is part of GNU Pth, a non-preemptive thread scheduling ** library which can be found at http://www.gnu.org/software/pth/. ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation; either ** version 2.1 of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ** USA, or contact Ralf S. Engelschall . ** ** pth_lib.c: Pth main library code */ /* ``It took me fifteen years to discover I had no talent for programming, but I couldn't give it up because by that time I was too famous.'' -- Unknown */ #include "pth_p.h" /* return the hexadecimal Pth library version number */ long pth_version(void) { return PTH_VERSION; } /* implicit initialization support */ intern int pth_initialized = FALSE; #if cpp #define pth_implicit_init() \ if (!pth_initialized) \ pth_init(); #endif #ifdef PTH_EX /* exception handling callback functions */ static ex_ctx_t *pth_ex_ctx(void) { return &(pth_current->ex_ctx); } static void pth_ex_terminate(ex_t *ex) { pth_exit(ex->ex_value); } #endif /* initialize the package */ int pth_init(void) { pth_attr_t t_attr; /* support for implicit initialization calls and to prevent multiple explict initialization, too */ if (pth_initialized) return pth_error(FALSE, EPERM); else pth_initialized = TRUE; pth_debug1("pth_init: enter"); /* initialize syscall wrapping */ pth_syscall_init(); /* initialize the scheduler */ if (!pth_scheduler_init()) { pth_shield { pth_syscall_kill(); } return pth_error(FALSE, EAGAIN); } #ifdef PTH_EX /* optional support for exceptional handling */ __ex_ctx = pth_ex_ctx; __ex_terminate = pth_ex_terminate; #endif /* spawn the scheduler thread */ t_attr = pth_attr_new(); pth_attr_set(t_attr, PTH_ATTR_PRIO, PTH_PRIO_MAX); pth_attr_set(t_attr, PTH_ATTR_NAME, "**SCHEDULER**"); pth_attr_set(t_attr, PTH_ATTR_JOINABLE, FALSE); pth_attr_set(t_attr, PTH_ATTR_CANCEL_STATE, PTH_CANCEL_DISABLE); pth_attr_set(t_attr, PTH_ATTR_STACK_SIZE, 64*1024); pth_attr_set(t_attr, PTH_ATTR_STACK_ADDR, NULL); pth_sched = pth_spawn(t_attr, pth_scheduler, NULL); if (pth_sched == NULL) { pth_shield { pth_attr_destroy(t_attr); pth_scheduler_kill(); pth_syscall_kill(); } return FALSE; } /* spawn a thread for the main program */ pth_attr_set(t_attr, PTH_ATTR_PRIO, PTH_PRIO_STD); pth_attr_set(t_attr, PTH_ATTR_NAME, "main"); pth_attr_set(t_attr, PTH_ATTR_JOINABLE, TRUE); pth_attr_set(t_attr, PTH_ATTR_CANCEL_STATE, PTH_CANCEL_ENABLE|PTH_CANCEL_DEFERRED); pth_attr_set(t_attr, PTH_ATTR_STACK_SIZE, 0 /* special */); pth_attr_set(t_attr, PTH_ATTR_STACK_ADDR, NULL); pth_main = pth_spawn(t_attr, (void *(*)(void *))(-1), NULL); if (pth_main == NULL) { pth_shield { pth_attr_destroy(t_attr); pth_scheduler_kill(); pth_syscall_kill(); } return FALSE; } pth_attr_destroy(t_attr); /* * The first time we've to manually switch into the scheduler to start * threading. Because at this time the only non-scheduler thread is the * "main thread" we will come back immediately. We've to also initialize * the pth_current variable here to allow the pth_spawn_trampoline * function to find the scheduler. */ pth_current = pth_sched; pth_mctx_switch(&pth_main->mctx, &pth_sched->mctx); /* came back, so let's go home... */ pth_debug1("pth_init: leave"); return TRUE; } /* kill the package internals */ int pth_kill(void) { if (!pth_initialized) return pth_error(FALSE, EINVAL); if (pth_current != pth_main) return pth_error(FALSE, EPERM); pth_debug1("pth_kill: enter"); pth_thread_cleanup(pth_main); pth_scheduler_kill(); pth_initialized = FALSE; pth_tcb_free(pth_sched); pth_tcb_free(pth_main); pth_syscall_kill(); #ifdef PTH_EX __ex_ctx = __ex_ctx_default; __ex_terminate = __ex_terminate_default; #endif pth_debug1("pth_kill: leave"); return TRUE; } /* scheduler control/query */ long pth_ctrl(unsigned long query, ...) { long rc; va_list ap; rc = 0; va_start(ap, query); if (query & PTH_CTRL_GETTHREADS) { if (query & PTH_CTRL_GETTHREADS_NEW) rc += pth_pqueue_elements(&pth_NQ); if (query & PTH_CTRL_GETTHREADS_READY) rc += pth_pqueue_elements(&pth_RQ); if (query & PTH_CTRL_GETTHREADS_RUNNING) rc += 1; /* pth_current only */ if (query & PTH_CTRL_GETTHREADS_WAITING) rc += pth_pqueue_elements(&pth_WQ); if (query & PTH_CTRL_GETTHREADS_SUSPENDED) rc += pth_pqueue_elements(&pth_SQ); if (query & PTH_CTRL_GETTHREADS_DEAD) rc += pth_pqueue_elements(&pth_DQ); } else if (query & PTH_CTRL_GETAVLOAD) { float *pload = va_arg(ap, float *); *pload = pth_loadval; } else if (query & PTH_CTRL_GETPRIO) { pth_t t = va_arg(ap, pth_t); rc = t->prio; } else if (query & PTH_CTRL_GETNAME) { pth_t t = va_arg(ap, pth_t); rc = (long)t->name; } else if (query & PTH_CTRL_DUMPSTATE) { FILE *fp = va_arg(ap, FILE *); pth_dumpstate(fp); } else if (query & PTH_CTRL_FAVOURNEW) { int favournew = va_arg(ap, int); pth_favournew = (favournew ? 1 : 0); } else rc = -1; va_end(ap); if (rc == -1) return pth_error(-1, EINVAL); return rc; } /* create a new thread of execution by spawning a cooperative thread */ static void pth_spawn_trampoline(void) { void *data; /* just jump into the start routine */ data = (*pth_current->start_func)(pth_current->start_arg); /* and do an implicit exit of the thread with the result value */ pth_exit(data); /* NOTREACHED */ abort(); } pth_t pth_spawn(pth_attr_t attr, void *(*func)(void *), void *arg) { pth_t t; unsigned int stacksize; void *stackaddr; pth_time_t ts; pth_debug1("pth_spawn: enter"); /* consistency */ if (func == NULL) return pth_error((pth_t)NULL, EINVAL); /* support the special case of main() */ if (func == (void *(*)(void *))(-1)) func = NULL; /* allocate a new thread control block */ stacksize = (attr == PTH_ATTR_DEFAULT ? 64*1024 : attr->a_stacksize); stackaddr = (attr == PTH_ATTR_DEFAULT ? NULL : attr->a_stackaddr); if ((t = pth_tcb_alloc(stacksize, stackaddr)) == NULL) return pth_error((pth_t)NULL, errno); /* configure remaining attributes */ if (attr != PTH_ATTR_DEFAULT) { /* overtake fields from the attribute structure */ t->prio = attr->a_prio; t->joinable = attr->a_joinable; t->cancelstate = attr->a_cancelstate; t->dispatches = attr->a_dispatches; pth_util_cpystrn(t->name, attr->a_name, PTH_TCB_NAMELEN); } else if (pth_current != NULL) { /* overtake some fields from the parent thread */ t->prio = pth_current->prio; t->joinable = pth_current->joinable; t->cancelstate = pth_current->cancelstate; t->dispatches = 0; pth_snprintf(t->name, PTH_TCB_NAMELEN, "%s.child@@%d=0x%lx", pth_current->name, (unsigned int)time(NULL), (unsigned long)pth_current); } else { /* defaults */ t->prio = PTH_PRIO_STD; t->joinable = TRUE; t->cancelstate = PTH_CANCEL_DEFAULT; t->dispatches = 0; pth_snprintf(t->name, PTH_TCB_NAMELEN, "user/%x", (unsigned int)time(NULL)); } /* initialize the time points and ranges */ pth_time_set(&ts, PTH_TIME_NOW); pth_time_set(&t->spawned, &ts); pth_time_set(&t->lastran, &ts); pth_time_set(&t->running, PTH_TIME_ZERO); /* initialize events */ t->events = NULL; /* clear raised signals */ sigemptyset(&t->sigpending); t->sigpendcnt = 0; /* remember the start routine and arguments for our trampoline */ t->start_func = func; t->start_arg = arg; /* initialize join argument */ t->join_arg = NULL; /* initialize thread specific storage */ t->data_value = NULL; t->data_count = 0; /* initialize cancellation stuff */ t->cancelreq = FALSE; t->cleanups = NULL; /* initialize mutex stuff */ pth_ring_init(&t->mutexring); #ifdef PTH_EX /* initialize exception handling context */ EX_CTX_INITIALIZE(&t->ex_ctx); #endif /* initialize the machine context of this new thread */ if (t->stacksize > 0) { /* the "main thread" (indicated by == 0) is special! */ if (!pth_mctx_set(&t->mctx, pth_spawn_trampoline, t->stack, ((char *)t->stack+t->stacksize))) { pth_shield { pth_tcb_free(t); } return pth_error((pth_t)NULL, errno); } } /* finally insert it into the "new queue" where the scheduler will pick it up for dispatching */ if (func != pth_scheduler) { t->state = PTH_STATE_NEW; pth_pqueue_insert(&pth_NQ, t->prio, t); } pth_debug1("pth_spawn: leave"); /* the returned thread id is just the pointer to the thread control block... */ return t; } /* returns the current thread */ pth_t pth_self(void) { return pth_current; } /* raise a signal for a thread */ int pth_raise(pth_t t, int sig) { struct sigaction sa; if (t == NULL || t == pth_current || (sig < 0 || sig > PTH_NSIG)) return pth_error(FALSE, EINVAL); if (sig == 0) /* just test whether thread exists */ return pth_thread_exists(t); else { /* raise signal for thread */ if (sigaction(sig, NULL, &sa) != 0) return FALSE; if (sa.sa_handler == SIG_IGN) return TRUE; /* fine, nothing to do, sig is globally ignored */ if (!sigismember(&t->sigpending, sig)) { sigaddset(&t->sigpending, sig); t->sigpendcnt++; } pth_yield(t); return TRUE; } } /* check whether a thread exists */ intern int pth_thread_exists(pth_t t) { if (!pth_pqueue_contains(&pth_NQ, t)) if (!pth_pqueue_contains(&pth_RQ, t)) if (!pth_pqueue_contains(&pth_WQ, t)) if (!pth_pqueue_contains(&pth_SQ, t)) if (!pth_pqueue_contains(&pth_DQ, t)) return pth_error(FALSE, ESRCH); /* not found */ return TRUE; } /* cleanup a particular thread */ intern void pth_thread_cleanup(pth_t thread) { /* run the cleanup handlers */ if (thread->cleanups != NULL) pth_cleanup_popall(thread, TRUE); /* run the specific data destructors */ if (thread->data_value != NULL) pth_key_destroydata(thread); /* release still acquired mutex variables */ pth_mutex_releaseall(thread); return; } /* terminate the current thread */ static int pth_exit_cb(void *arg) { int rc; /* BE CAREFUL HERE: THIS FUNCTION EXECUTES FROM WITHIN THE _SCHEDULER_ THREAD! */ /* calculate number of still existing threads in system. Only skipped queue is pth_DQ (dead queue). This queue does not count here, because those threads are non-detached but already terminated ones -- and if we are the only remaining thread (which also wants to terminate and not join those threads) we can signal us through the scheduled event (for which we are running as the test function inside the scheduler) that the whole process can terminate now. */ rc = 0; rc += pth_pqueue_elements(&pth_NQ); rc += pth_pqueue_elements(&pth_RQ); rc += pth_pqueue_elements(&pth_WQ); rc += pth_pqueue_elements(&pth_SQ); if (rc == 1 /* just our main thread */) return TRUE; else return FALSE; } void pth_exit(void *value) { pth_event_t ev; pth_debug2("pth_exit: marking thread \"%s\" as dead", pth_current->name); /* the main thread is special, because its termination would terminate the whole process, so we have to delay its termination until it is really the last thread */ if (pth_current == pth_main) { if (!pth_exit_cb(NULL)) { ev = pth_event(PTH_EVENT_FUNC, pth_exit_cb); pth_wait(ev); pth_event_free(ev, PTH_FREE_THIS); } } /* execute cleanups */ pth_thread_cleanup(pth_current); if (pth_current != pth_main) { /* * Now mark the current thread as dead, explicitly switch into the * scheduler and let it reap the current thread structure; we can't * free it here, or we'd be running on a stack which malloc() regards * as free memory, which would be a somewhat perilous situation. */ pth_current->join_arg = value; pth_current->state = PTH_STATE_DEAD; pth_debug2("pth_exit: switching from thread \"%s\" to scheduler", pth_current->name); pth_mctx_switch(&pth_current->mctx, &pth_sched->mctx); } else { /* * main thread is special: exit the _process_ * [double-casted to avoid warnings because of size] */ pth_kill(); exit((int)((long)value)); } /* NOTREACHED */ abort(); } /* waits for the termination of the specified thread */ int pth_join(pth_t tid, void **value) { pth_event_t ev; static pth_key_t ev_key = PTH_KEY_INIT; pth_debug2("pth_join: joining thread \"%s\"", tid == NULL ? "-ANY-" : tid->name); if (tid == pth_current) return pth_error(FALSE, EDEADLK); if (tid != NULL && !tid->joinable) return pth_error(FALSE, EINVAL); if (pth_ctrl(PTH_CTRL_GETTHREADS) == 1) return pth_error(FALSE, EDEADLK); if (tid == NULL) tid = pth_pqueue_head(&pth_DQ); if (tid == NULL || (tid != NULL && tid->state != PTH_STATE_DEAD)) { ev = pth_event(PTH_EVENT_TID|PTH_UNTIL_TID_DEAD|PTH_MODE_STATIC, &ev_key, tid); pth_wait(ev); } if (tid == NULL) tid = pth_pqueue_head(&pth_DQ); if (tid == NULL || (tid != NULL && tid->state != PTH_STATE_DEAD)) return pth_error(FALSE, EIO); if (value != NULL) *value = tid->join_arg; pth_pqueue_delete(&pth_DQ, tid); pth_tcb_free(tid); return TRUE; } /* delegates control back to scheduler for context switches */ int pth_yield(pth_t to) { pth_pqueue_t *q = NULL; pth_debug2("pth_yield: enter from thread \"%s\"", pth_current->name); /* a given thread has to be new or ready or we ignore the request */ if (to != NULL) { switch (to->state) { case PTH_STATE_NEW: q = &pth_NQ; break; case PTH_STATE_READY: q = &pth_RQ; break; default: q = NULL; } if (q == NULL || !pth_pqueue_contains(q, to)) return pth_error(FALSE, EINVAL); } /* give a favored thread maximum priority in his queue */ if (to != NULL && q != NULL) pth_pqueue_favorite(q, to); /* switch to scheduler */ if (to != NULL) pth_debug2("pth_yield: give up control to scheduler " "in favour of thread \"%s\"", to->name); else pth_debug1("pth_yield: give up control to scheduler"); pth_mctx_switch(&pth_current->mctx, &pth_sched->mctx); pth_debug1("pth_yield: got back control from scheduler"); pth_debug2("pth_yield: leave to thread \"%s\"", pth_current->name); return TRUE; } /* suspend a thread until its again manually resumed */ int pth_suspend(pth_t t) { pth_pqueue_t *q; if (t == NULL) return pth_error(FALSE, EINVAL); if (t == pth_sched || t == pth_current) return pth_error(FALSE, EPERM); switch (t->state) { case PTH_STATE_NEW: q = &pth_NQ; break; case PTH_STATE_READY: q = &pth_RQ; break; case PTH_STATE_WAITING: q = &pth_WQ; break; default: q = NULL; } if (q == NULL) return pth_error(FALSE, EPERM); if (!pth_pqueue_contains(q, t)) return pth_error(FALSE, ESRCH); pth_pqueue_delete(q, t); pth_pqueue_insert(&pth_SQ, PTH_PRIO_STD, t); pth_debug2("pth_suspend: suspend thread \"%s\"\n", t->name); return TRUE; } /* resume a previously suspended thread */ int pth_resume(pth_t t) { pth_pqueue_t *q; if (t == NULL) return pth_error(FALSE, EINVAL); if (t == pth_sched || t == pth_current) return pth_error(FALSE, EPERM); if (!pth_pqueue_contains(&pth_SQ, t)) return pth_error(FALSE, EPERM); pth_pqueue_delete(&pth_SQ, t); switch (t->state) { case PTH_STATE_NEW: q = &pth_NQ; break; case PTH_STATE_READY: q = &pth_RQ; break; case PTH_STATE_WAITING: q = &pth_WQ; break; default: q = NULL; } pth_pqueue_insert(q, PTH_PRIO_STD, t); pth_debug2("pth_resume: resume thread \"%s\"\n", t->name); return TRUE; } /* switch a filedescriptor's I/O mode */ int pth_fdmode(int fd, int newmode) { int fdmode; int oldmode; /* retrieve old mode (usually a very cheap operation) */ if ((fdmode = fcntl(fd, F_GETFL, NULL)) == -1) oldmode = PTH_FDMODE_ERROR; else if (fdmode & O_NONBLOCKING) oldmode = PTH_FDMODE_NONBLOCK; else oldmode = PTH_FDMODE_BLOCK; /* set new mode (usually a more expensive operation) */ if (oldmode == PTH_FDMODE_BLOCK && newmode == PTH_FDMODE_NONBLOCK) fcntl(fd, F_SETFL, (fdmode | O_NONBLOCKING)); if (oldmode == PTH_FDMODE_NONBLOCK && newmode == PTH_FDMODE_BLOCK) fcntl(fd, F_SETFL, (fdmode & ~(O_NONBLOCKING))); /* return old mode */ return oldmode; } /* wait for specific amount of time */ int pth_nap(pth_time_t naptime) { pth_time_t until; pth_event_t ev; static pth_key_t ev_key = PTH_KEY_INIT; if (pth_time_cmp(&naptime, PTH_TIME_ZERO) == 0) return pth_error(FALSE, EINVAL); pth_time_set(&until, PTH_TIME_NOW); pth_time_add(&until, &naptime); ev = pth_event(PTH_EVENT_TIME|PTH_MODE_STATIC, &ev_key, until); pth_wait(ev); return TRUE; } /* runs a constructor once */ int pth_once(pth_once_t *oncectrl, void (*constructor)(void *), void *arg) { if (oncectrl == NULL || constructor == NULL) return pth_error(FALSE, EINVAL); if (*oncectrl != TRUE) constructor(arg); *oncectrl = TRUE; return TRUE; } @ 1.61 log @Adjusted all copyright messages for new year 2006 @ text @d3 1 a3 1 ** Copyright (c) 1999-2006 Ralf S. Engelschall @ 1.60 log @Return an error if pth_kill() is called before pth_init(). Submitted by: Martin Kraemer @ text @d3 1 a3 1 ** Copyright (c) 1999-2005 Ralf S. Engelschall @ 1.59 log @Adjusted all copyright messages for new year 2005. @ text @d141 2 @ 1.58 log @Added PTH_CTRL_FAVOURNEW control which allows the user to disable the favouring of new threads on scheduling to get more strict priority based scheduling behavior. Triggered by: Vinu V @ text @d3 1 a3 1 ** Copyright (c) 1999-2004 Ralf S. Engelschall @ 1.57 log @Adjusted all copyright messages for new year 2004. @ text @d196 4 @ 1.56 log @Adjusted all copyright messages for new year 2003. @ text @d3 1 a3 1 ** Copyright (c) 1999-2003 Ralf S. Engelschall @ 1.55 log @Fixed error handling in pth_init(3): it now correctly returns an error instead of abort(3)'ing. @ text @d3 1 a3 1 ** Copyright (c) 1999-2002 Ralf S. Engelschall @ 1.54 log @Added thread attribute PTH_ATTR_DISPATCHES which (in bounded attribute objects) is incremented every time the context is switched to the associated thread. This can be used for statistical information. @ text @d76 4 a79 1 pth_scheduler_init(); d100 1 d117 1 @ 1.53 log @Internally switch from "errno_shield {...}" to "pth_shield {...}" and from "return_errno(..)" to "return pth_error(...)" in order to make the internal error handling a little bit more consistent. @ text @d242 1 d250 1 d260 1 @ 1.52 log @Cleaned and speeded up the pth_exit() processing. @ text @d66 1 a66 1 return_errno(FALSE, EPERM); d94 1 a94 1 errno_shield { d110 1 a110 1 errno_shield { d137 1 a137 1 return_errno(FALSE, EPERM); d195 1 a195 1 return_errno(-1, EINVAL); d224 1 a224 1 return_errno(NULL, EINVAL); d234 1 a234 1 return NULL; d302 2 a303 2 errno_shield { pth_tcb_free(t); } return NULL; d333 1 a333 1 return_errno(FALSE, EINVAL); d360 1 a360 1 return_errno(FALSE, ESRCH); /* not found */ d461 1 a461 1 return_errno(FALSE, EDEADLK); d463 1 a463 1 return_errno(FALSE, EINVAL); d465 1 a465 1 return_errno(FALSE, EDEADLK); d475 1 a475 1 return_errno(FALSE, EIO); d498 1 a498 1 return_errno(FALSE, EINVAL); d524 1 a524 1 return_errno(FALSE, EINVAL); d526 1 a526 1 return_errno(FALSE, EPERM); d534 1 a534 1 return_errno(FALSE, EPERM); d536 1 a536 1 return_errno(FALSE, ESRCH); d549 1 a549 1 return_errno(FALSE, EINVAL); d551 1 a551 1 return_errno(FALSE, EPERM); d553 1 a553 1 return_errno(FALSE, EPERM); d572 1 a572 1 /* retrieve old mode (usually cheap) */ d580 1 a580 1 /* set new mode (usually expensive) */ d598 1 a598 1 return_errno(FALSE, EINVAL); d610 1 a610 1 return_errno(FALSE, EINVAL); @ 1.51 log @Completely rewrote the "hard syscall mapping". Previously the internal syscall exit points were based on syscall(2) only. This is problematic because it by-passes the C library glue code which sometimes performs necessary assembly fiddling in order to call the underlying system calls correctly. Additionally, syscall(2) does not exists everywhere. Now the internal exit points are based on a by-syscall dynamically selected combination of RTLD_NEXT+dlsym(2), dlopen(2)+dlsym(2) and the known syscall(2) (in this fallback order). This way the "hard syscall mapping" is a lot more portable and flexible. Initial implementation by: Jonathan Schilling Enhanced implementation by: Ralf S. Engelschall @ text @d206 2 a207 1 /* and do an implicit exit of the tread with the result value */ d209 2 a210 1 /* no return! */ d232 1 a232 1 stackaddr = (attr == PTH_ATTR_DEFAULT ? NULL : attr->a_stackaddr); d234 1 a234 1 return NULL; /* errno is inherited */ d286 1 a286 1 /* initialize cancellaton stuff */ d381 1 a381 1 /* terminates the current thread */ d386 2 a387 2 /* NOTICE: THIS FUNCTION EXECUTES FROM WITHIN THE SCHEDULER THREAD! */ d414 3 a416 2 /* main thread is special: wait until it is the last thread */ d418 5 a422 3 ev = pth_event(PTH_EVENT_FUNC, pth_exit_cb); pth_wait(ev); pth_event_free(ev, PTH_FREE_THIS); a427 4 /* mark the current thread as dead, so the scheduler removes us */ pth_current->join_arg = value; pth_current->state = PTH_STATE_DEAD; d430 4 a433 4 * Now we explicitly switch into the scheduler and let it * reap the current thread structure; we can't free it here, * or we'd be running on a stack which malloc() regards as * free memory, which would be a somewhat perilous situation. d435 2 a438 1 abort(); /* not reached! */ d443 1 a443 1 * [double-cast to avoid warnings because of size] a446 1 abort(); /* not reached! */ d448 3 @ 1.50 log @remove trailing whitespaces @ text @d72 3 d144 1 @ 1.49 log @Fixed a long-standing termination bug in pth_exit(3): The event handler of pth_exit(3) didn't let pth_exit(3) finish if there were any threads on the "dead queue" (where non-detached terminated threads are put). Instead it re-entered the scheduler which in turn aborted with "**Pth** SCHEDULER INTERNAL ERROR: no more thread(s) available to schedule!?!?". This is now fixed by not counting the "dead queue" for the determination whether the process as a whole should terminate or not. Submitted by: Jonathan Schilling @ text @d91 1 a91 1 errno_shield { d107 1 a107 1 errno_shield { d133 1 a133 1 if (pth_current != pth_main) d166 1 a166 1 if (query & PTH_CTRL_GETTHREADS_SUSPENDED) d243 2 a244 2 pth_snprintf(t->name, PTH_TCB_NAMELEN, "%s.child@@%d=0x%lx", pth_current->name, (unsigned int)time(NULL), d435 4 a438 4 /* * main thread is special: exit the _process_ * [double-cast to avoid warnings because of size] */ @ 1.48 log @Add optional support for OSSP ex based exception handling. GNU Pth (still) does not throw exceptions by itself, but handles the per-thread exception context of OSSP ex to make exception handling local to a thread. @ text @d382 9 d396 1 a396 1 rc += pth_pqueue_elements(&pth_DQ); @ 1.47 log @bump copyright year @ text @d46 12 d75 6 d141 4 d286 5 @ 1.46 log @*** empty log message *** @ text @d3 1 a3 1 ** Copyright (c) 1999-2001 Ralf S. Engelschall @ 1.45 log @*** empty log message *** @ text @d3 1 a3 1 ** Copyright (c) 1999-2000 Ralf S. Engelschall @ 1.44 log @*** empty log message *** @ text @d495 1 d518 1 @ 1.43 log @*** empty log message *** @ text @d221 3 a223 3 sprintf(t->name, "%s.child@@%d=0x%lx", pth_current->name, (unsigned int)time(NULL), (unsigned long)pth_current); d230 2 a231 1 sprintf(t->name, "user/%x", (unsigned int)time(NULL)); @ 1.42 log @*** empty log message *** @ text @d262 1 a262 1 pth_ring_init(&(t->mutexring), NULL); @ 1.41 log @*** empty log message *** @ text @d441 2 d445 1 a445 1 /* a given thread has to be ready or we ignore the request */ d447 6 a452 5 if (to->state != PTH_STATE_READY) to = NULL; else if (!pth_pqueue_contains(&pth_RQ, to)) to = NULL; if (to == NULL) d456 3 a458 3 /* give a favored thread maximum priority in ready queue */ if (to != NULL) pth_pqueue_favorite(&pth_RQ, to); @ 1.40 log @*** empty log message *** @ text @d188 1 a188 1 int stacksize; @ 1.39 log @*** empty log message *** @ text @d69 1 a69 1 pth_attr_set(t_attr, PTH_ATTR_STACK_SIZE, 32*1024); d203 1 a203 1 stacksize = (attr == PTH_ATTR_DEFAULT ? 32*1024 : attr->a_stacksize); @ 1.38 log @*** empty log message *** @ text @d1 29 a29 29 /* ** GNU Pth - The GNU Portable Threads ** Copyright (c) 1999-2000 Ralf S. Engelschall ** ** This file is part of GNU Pth, a non-preemptive thread scheduling ** library which can be found at http://www.gnu.org/software/pth/. ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation; either ** version 2.1 of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ** USA, or contact Ralf S. Engelschall . ** ** pth_lib.c: Pth main library code */ /* ``It took me fifteen years to discover I had no talent for programming, but I couldn't give it up because by that time I was too famous.'' -- Unknown */ d32 5 a36 5 /* return the hexadecimal Pth library version number */ long pth_version(void) { return PTH_VERSION; } d38 2 a39 2 /* implicit initialization support */ intern int pth_initialized = FALSE; d42 2 a43 2 if (!pth_initialized) \ pth_init(); d46 11 a56 11 /* initialize the package */ int pth_init(void) { pth_attr_t t_attr; /* support for implicit initialization calls and to prevent multiple explict initialization, too */ if (pth_initialized) return_errno(FALSE, EPERM); else pth_initialized = TRUE; d58 1 a58 1 pth_debug1("pth_init: enter"); d60 2 a61 2 /* initialize the scheduler */ pth_scheduler_init(); d63 1 a63 1 /* spawn the scheduler thread */ @ 1.37 log @*** empty log message *** @ text @d1 29 a29 29 /* ** GNU Pth - The GNU Portable Threads ** Copyright (c) 1999-2000 Ralf S. Engelschall ** ** This file is part of GNU Pth, a non-preemptive thread scheduling ** library which can be found at http://www.gnu.org/software/pth/. ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation; either ** version 2.1 of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ** USA, or contact Ralf S. Engelschall . ** ** pth_lib.c: Pth main library code */ /* ``It took me fifteen years to discover I had no talent for programming, but I couldn't give it up because by that time I was too famous.'' -- Unknown */ d32 5 a36 5 /* return the hexadecimal Pth library version number */ long pth_version(void) { return PTH_VERSION; } d38 2 a39 2 /* implicit initialization support */ intern int pth_initialized = FALSE; d42 2 a43 2 if (!pth_initialized) \ pth_init(); d46 11 a56 11 /* initialize the package */ int pth_init(void) { pth_attr_t t_attr; /* support for implicit initialization calls and to prevent multiple explict initialization, too */ if (pth_initialized) return_errno(FALSE, EPERM); else pth_initialized = TRUE; d58 1 a58 1 pth_debug1("pth_init: enter"); d60 2 a61 2 /* initialize the scheduler */ pth_scheduler_init(); d63 1 a63 1 /* spawn the scheduler thread */ d185 1 a185 1 pth_t pth_spawn(pth_attr_t attr, void *(*func)(void*), void *arg) @ 1.36 log @*** empty log message *** @ text @d54 1 a54 1 return FALSE; d72 5 a76 1 if (pth_sched == NULL) d78 1 d88 7 a95 2 if (pth_main == NULL) return FALSE; d116 1 a116 1 return_errno(FALSE, EINVAL); d265 1 a265 1 if (t->stacksize > 0) /* the "main thread" (indicated by == 0) is special! */ d267 2 a268 1 t->stack, ((char *)t->stack+t->stacksize))) d270 2 @ 1.35 log @*** empty log message *** @ text @d134 2 d287 3 a289 11 if (sig == 0) { /* * test whether thread exists */ if (!pth_pqueue_contains(&pth_NQ, t)) if (!pth_pqueue_contains(&pth_RQ, t)) if (!pth_pqueue_contains(&pth_WQ, t)) if (!pth_pqueue_contains(&pth_DQ, t)) return_errno(FALSE, ESRCH); /* not found */ return TRUE; } d291 1 a291 3 /* * raise signal for thread */ d305 12 d345 1 d454 46 @ 1.34 log @*** empty log message *** @ text @d396 1 @ 1.33 log @*** empty log message *** @ text @d107 1 d113 1 d149 4 d209 3 a211 1 sprintf(t->name, "%s.child/%x", pth_current->name, (unsigned int)time(NULL)); @ 1.32 log @*** empty log message *** @ text @d110 2 @ 1.31 log @*** empty log message *** @ text @d3 1 a3 1 ** Copyright (c) 1999 Ralf S. Engelschall @ 1.30 log @*** empty log message *** @ text @d103 1 a103 1 void pth_kill(void) d105 3 d110 1 a110 1 return; d303 17 d343 2 a344 1 /* main thread is special */ a345 1 /* wait until we're the last thread */ d348 1 a348 3 /* exit the _process_ [double-cast to avoid warnings because of size] */ pth_kill(); exit((int)((long)value)); d351 2 a352 10 /* run the cleanup handlers */ if (pth_current->cleanups != NULL) pth_cleanup_popall(pth_current, TRUE); /* run the per-thread specific data destructors */ if (pth_current->data_value != NULL) pth_key_destroydata(pth_current); /* release still acquired mutex variables */ pth_mutex_releaseall(pth_current); d358 20 a377 11 /* * Now we explicitly switch into the scheduler and let it * reap the current thread structure; we can't free it here, * or we'd be running on a stack which malloc() regards as * free memory, which would be a somewhat perilous situation. */ pth_debug2("pth_exit: switching from thread \"%s\" to scheduler", pth_current->name); pth_mctx_switch(&pth_current->mctx, &pth_sched->mctx); /* not reached! */ abort(); @ 1.29 log @*** empty log message *** @ text @d87 1 a87 1 /* d119 1 a119 1 if (query & PTH_CTRL_GETTHREADS_NEW) d121 1 a121 1 if (query & PTH_CTRL_GETTHREADS_READY) d123 1 a123 1 if (query & PTH_CTRL_GETTHREADS_RUNNING) d125 1 a125 1 if (query & PTH_CTRL_GETTHREADS_WAITING) d127 1 a127 1 if (query & PTH_CTRL_GETTHREADS_DEAD) d131 1 a131 1 float *pload = va_arg(ap, float *); d135 1 a135 1 pth_t t = va_arg(ap, pth_t); d139 1 a139 1 pth_t t = va_arg(ap, pth_t); d142 1 a142 1 else d160 1 a160 1 abort(); d227 1 a227 1 d231 1 a231 1 d244 1 a244 1 d254 1 a254 1 /* the returned thread id is just the pointer d273 1 a273 1 /* d284 1 a284 1 /* d330 1 a330 1 exit((int)((long)value)); d348 1 a348 1 /* d404 1 a404 1 d433 1 a433 1 else @ 1.28 log @*** empty log message *** @ text @d2 1 a2 2 ** pth_lib.c -- Pth main library code ** d22 2 @ 1.28.2.1 log @*** empty log message *** @ text @d2 2 a3 1 ** GNU Pth - The GNU Portable Threads a22 2 ** ** pth_lib.c: Pth main library code @ 1.28.2.2 log @*** empty log message *** @ text @d103 1 a103 1 int pth_kill(void) a104 3 if (pth_current != pth_main) return_errno(FALSE, EINVAL); pth_debug1("pth_kill: enter"); d107 1 a107 4 pth_tcb_free(pth_sched); pth_tcb_free(pth_main); pth_debug1("pth_kill: leave"); return TRUE; @ 1.27 log @*** empty log message *** @ text @d12 1 a12 1 ** version 2 of the License, or (at your option) any later version. @ 1.26 log @*** empty log message *** @ text @d10 1 a10 1 ** modify it under the terms of the GNU Library General Public d17 1 a17 1 ** Library General Public License for more details. d19 1 a19 1 ** You should have received a copy of the GNU Library General Public @ 1.25 log @*** empty log message *** @ text @d182 1 a182 1 return NULL; /* errno is already set */ @ 1.24 log @*** empty log message *** @ text @d179 1 a179 1 stacksize = (attr == PTH_ATTR_DEFAULT ? 32*16384 : attr->a_stacksize); d182 1 a182 1 return_errno(NULL, ENOMEM); @ 1.23 log @*** empty log message *** @ text @d390 1 a390 1 void pth_yield(pth_t to) d400 2 d418 1 a418 1 return; @ 1.22 log @*** empty log message *** @ text @d41 2 a42 4 if (!pth_initialized) { \ pth_initialized = TRUE; \ pth_init(); \ } d50 2 a51 2 pth_debug1("pth_init: enter"); d54 4 @ 1.21 log @*** empty log message *** @ text @d24 5 a28 1 @ 1.20 log @*** empty log message *** @ text @d235 1 a235 1 t->stack, (void *)((char *)t->stack+t->stacksize))) @ 1.19 log @*** empty log message *** @ text @d27 1 a27 1 /* return the hexadecial Pth library version number */ @ 1.18 log @*** empty log message *** @ text @d413 2 a414 2 /* switch a filedescriptor to non-blocking I/O mode */ int pth_nonblocking(int fd) d416 2 a417 2 int mode; int rv; d419 16 a434 20 #ifdef O_NONBLOCK #define O_NONBLOCKING O_NONBLOCK #else #ifdef O_NDELAY #define O_NONBLOCKING O_NDELAY #else #ifdef FNDELAY #define O_NONBLOCKING FNDELAY #else #error "No O_NONBLOCK, O_NDELAY or FNDELAY flag available!" #endif #endif #endif rv = TRUE; mode = fcntl(fd, F_GETFL, NULL); if (mode != -1 && (mode & O_NONBLOCKING) == 0) { fcntl(fd, F_SETFL, mode|O_NONBLOCKING); rv = FALSE; } return rv; @ 1.18.2.1 log @*** empty log message *** @ text @d173 1 a173 1 stacksize = (attr == PTH_ATTR_DEFAULT ? 32*1024 : attr->a_stacksize); @ 1.17 log @*** empty log message *** @ text @d173 2 a174 2 stacksize = (attr == PTH_ATTR_NULL ? 32*16384 : attr->a_stacksize); stackaddr = (attr == PTH_ATTR_NULL ? NULL : attr->a_stackaddr); d179 1 a179 1 if (attr != PTH_ATTR_NULL) { @ 1.16 log @*** empty log message *** @ text @d425 5 a429 1 #error "No O_NONBLOCK or O_NDELAY define available!" @ 1.15 log @*** empty log message *** @ text @d99 1 @ 1.14 log @*** empty log message *** @ text @d423 2 @ 1.13 log @*** empty log message *** @ text @d37 4 a40 2 if (!pth_initialized) \ pth_init(); @ 1.12 log @*** empty log message *** @ text @d70 1 a70 1 pth_attr_set(t_attr, PTH_ATTR_CANCEL_STATE, PTH_CANCEL_DISABLE); @ 1.11 log @*** empty log message *** @ text @d67 6 a72 3 pth_attr_set(t_attr, PTH_ATTR_PRIO, PTH_PRIO_STD); pth_attr_set(t_attr, PTH_ATTR_NAME, "main"); pth_attr_set(t_attr, PTH_ATTR_STACK_SIZE, 0 /* special */); @ 1.10 log @*** empty log message *** @ text @d33 8 d47 3 @ 1.9 log @*** empty log message *** @ text @d281 1 a281 1 /* NOTICE: THIS FUNCTION EXECUTED @ 1.8 log @*** empty log message *** @ text @d2 1 a2 1 ** pth_lib.c -- PTH main library code d27 1 a27 1 /* return the hexadecial PTH library version number */ @ 1.7 log @*** empty log message *** @ text @d6 2 a7 2 ** This file is part of PTH, a non-preemptive thread scheduling library ** which can be found at http://www.gnu.org/software/pth/. @ 1.6 log @*** empty log message *** @ text @d362 1 @ 1.5 log @*** empty log message *** @ text @d7 1 a7 1 ** which can be found at http://www.engelschall.com/sw/pth/. @ 1.4 log @*** empty log message *** @ text @d2 1 a2 1 ** pth.c -- PTH main API code d119 3 a122 1 va_end(ap); @ 1.3 log @*** empty log message *** @ text @d436 1 a436 1 if (oncectrl->on_todo) d438 1 a438 1 oncectrl->on_todo = FALSE; @ 1.2 log @*** empty log message *** @ text @d28 1 a28 1 int pth_version(void) @ 1.1 log @*** empty log message *** @ text @d27 6 @