diff options
Diffstat (limited to 'lib/qpthreads.c')
-rw-r--r-- | lib/qpthreads.c | 775 |
1 files changed, 775 insertions, 0 deletions
diff --git a/lib/qpthreads.c b/lib/qpthreads.c new file mode 100644 index 00000000..baa34d52 --- /dev/null +++ b/lib/qpthreads.c @@ -0,0 +1,775 @@ +/* Quagga Pthreads support -- header + * Copyright (C) 2009 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* This MUST come first... otherwise we don't get __USE_UNIX98, which is */ +/* essential if glibc is to allow pthread_mutexattr_settype() to be used. */ +#include "config.h" + +#include <signal.h> +#include <string.h> + +#include "qpthreads.h" +#include "memory.h" + +/* If this is not set, will get errors later. */ +//#ifndef __USE_UNIX98 +//#error "_USE_UNIX98 not defined" +//#endif + +/*============================================================================== + * Quagga Pthread Interface -- qpt_xxxx + * + * Here (and in qpthreads.h) are captured all the pthreads features used in + * Quagga. + * + * This provides: + * + * * "wrappers" around functions which should not fail, but whose return + * code it is best to check... at least in a debug environment. + * + * * the possibility of a separate no pthreads build where pthread facilities + * are either dummied out or otherwise dealt with. + * + * * the ability to add any work-arounds which may be required if poorly + * conforming pthreads implementations are encountered + * + * Continued Working Without Pthreads + * ================================== + * + * A big Global Switch -- qpthreads_enabled -- is used to control whether the + * system is pthreaded or not. + * + * The initial state is qpthreads_enabled == false (0). + * + * The function qpt_set_qpthreads_enabled() should be called when the + * application has decided whether to use qpthreads or not. (But does not have + * to call this if it is happy to proceed in the default -- disabled -- state.) + * + * If this is never set, then the system runs without pthreads, and all the + * mutex and condition variable functions are NOPs. This allows, for example, + * mutex operations to be placed where they are needed for thread-safety, + * without affecting the code when running without pthreads. + * + * There are a very few operations which require qpthreads_enabled: + * + * * qpt_thread_attr_init + * * qpt_thread_create + * + * A few operations "freeze" the state of qpthreads_enabled. Any call of these + * before qpthreads are enabled, causes the state to be frozen, disabled. This + * means that any later attempt to enable qpthreads will be refused. These + * operations are: + * + * * qpt_mutex_init_new + * * qpt_cond_init_new + * + * This allows the application to decide as late as possible (but no later) + * whether to enable pthreads. If a mutex or a condition variable has been + * initialised before the application gets around to enabling qpthreads, that + * will be trapped when qpthreads is finally enabled. + * + * Pthread Requirements + * ==================== + * + * This is assuming support for 1003.1-2004 -- XOPEN Issue 6, with [THR] and + * [XSI] options. + * + * The [XSI] is required for pthread_mutexattr_settype(), only. + * + * If qpt_thread_attr_init() uses: + * + * pthread_attr_getinheritsched()/_setinheritshed() [TPS] + * pthread_attr_getscope()/_setscope() [TPS] + * pthread_attr_getschedpolicy()/_setschedpolicy() [TPS] + * pthread_attr_getschedparam()/_setschedparam() [THR] + * + * but they are only required if explicit scheduling attributes are being set. + * (So, could be dropped where not supported.) + * + * Amongst the things which are NOT required: + * + * pthread_attr_getguardsize()/_setguardsize() [XSI] + * pthread_attr_getstack()/_setstack() [TSA TSS] + * pthread_attr_getstackaddr()/_setstackaddr() [TSA OB] + * pthread_attr_getstacksize()/_setstacksize() [TSA TSS] + * + * pthread_barrier_xxx() [BAR] + * + * pthread_condattr_getpshared()/_setpshared() [TSH] + * + * pthread_mutex_getprioceiling()/_setprioceiling() [TPP] + * pthread_mutex_timedlock() [TMO] pro tem + * pthread_mutexattr_getprioceiling()/_setprioceiling() [TPP] + * pthread_mutexattr_getprotocol()/_setprotocol() [TPP TPI] + * pthread_mutexattr_getpshared()/_setpshared() [TSH] + * + * pthread_rwlock_xxx() [THR] pro tem + * pthread_rwlockattr_init()/_destroy() [THR] pro tem + * pthread_rwlockattr_getpshared()/_setpshared() [TSH] + * + * pthread_spin_xxx() [SPI] + * + * [CS] (Clock Select) is assumed if HAVE_CLOCK_MONOTONIC. + * + * In 1003.1-2008, XOPEN issue 7, [THR] and pthread_mutexattr_settype() have + * been moved to Base. + * + * NB: it is essential that pthread_kill() delivers the signal to the target + * thread only -- ie, it must be POSIX compliant. That rules out the old + * (2.4) LinuxThreads. For Linux, 2.6 (or greater) is required, with + * NPTL (these days generally included in glibc). + * + * NB: for glibc to give all the required features, either _GNU_SOURCE or + * _XOPEN_SOURCE must be set *before* the first #include <features.h>. + * _XOPEN_SOURCE=600 is sufficient. + * + * Pthread Thread Attributes -- Scheduling + * ======================================= + * + * Pthreads defines some useful looking real-time scheduling features. + * + * One would like to be able to give I/O intensive threads an advantage over + * CPU bound threads. + * + * Unfortunately, conformance allows a system to have its own scheduling + * system -- so long as the standard ones are implemented. Further, there is + * no way of telling what priority values are reasonable, even in the standard + * scheduling policies. + * + * The approach taken here is that by default a thread will be created with + * the system default attributes -- which may mean inheriting the creating + * thread's scheduling attributes. + * + * It is also possible to construct a set of attributes, using the most + * obviously useful properties. It is envisaged that this may be used when a + * configuration file is used to set locally sensible values. The attributes + * supported are: + * + * * attr_detached -- whether to start detached or not + * * attr_inherit_sched -- whether to inherit scheduling attributes + * * attr_sched_scope -- scheduling scope + * * attr_sched_policy -- scheduling policy + * * attr_sched_priority -- scheduling priority + * + * See qpt_thread_attr_init, below. + * + * Not supported here are: + * + * * attr_guardsize + * * attr_stack + * * attr_stacksize + * + * Pthread Mutex Attributes -- Error Checking + * ========================================== + * + * Mutexes are kept simple, only attr_type is used, and that by default. + * + * POSIX defines four types of mutex: + * + * _NORMAL no ownership check -- owner will deadlock if locks mutex ! + * -- undefined what happens if unlock + * mutex not owned by self ! + * no recursive locking + * + * _ERRORCHECK checks for ownership on lock and unlock + * no recursive locking + * + * _RECURSIVE checks for ownership on lock and unlock + * counts up locks and counts down unlocks + * + * This looks useful, but goes wrong with condition variables ! + * + * _DEFAULT undefined whether checks owner or not, on lock and/or unlock. + * no recursive locking + * + * See qpthreads.h for discussion of Quagga's standard type (QPT_MUTEX_TYPE). + * + * Other attributes are left in their default state: + * + * * attr_prioceiling -- default undefined + * * attr_protocol -- default undefined + * * attr_pshared -- defaults to _PROCESS_PRIVATE + * + * For the time being it is assumed that these are too exotic. + * + * Pthread Condition Variable Attributes + * ===================================== + * + * Condition variables have only two attributes: + * + * * attr_clock -- which clock to use + * * attr_pshared -- defaults to _PROCESS_PRIVATE + * + * The use a clock other than Quagga's standard (QPT_COND_CLOCK_ID) is possible, + * but not recommended. (See qpthreads.h for discussion of this.) + * + * Pthread Specific Signal Handling + * ================================ + * + * In a threaded application, need to use pthread_sigmask (not sigproc_mask). + * (Can use pthread_sigmask in a single threaded application.) + * + * To direct a signal at a given thread need pthread_kill. * + */ + +/*============================================================================== + * The Global Switch + * + * The state of the switch is: unset -- implicitly not enabled + * set_frozen -- implicitly not enabled & frozen + * set_disabled -- explicitly not enabled + * set_enabled -- explicitly set enabled + * + * "set_frozen" means that "qpthreads_freeze_enabled_state()" has been called, + * and the state was unset at the time. This means that some initialisation + * has been done on the basis of !qpthreads_enabled, and it is TOO LATE to + * enable qpthreads afterwards. + */ + +enum qpthreads_enabled_state +{ + qpt_state_unset = 0, + qpt_state_set_frozen = 1, + qpt_state_set_disabled = 2, + qpt_state_set_enabled = 3, +} ; + +static enum qpthreads_enabled_state qpthreads_enabled_state = qpt_state_unset ; + +uint8_t qpthreads_enabled_flag = 0 ; +uint8_t qpthreads_thread_created_flag = 0 ; + +/* Function to set qpthreads_enabled, one way or the other. + * + * Returns: true <=> successful set the required state. + * false <=> it is too late to enable qpthreads :-( + * + * NB: can repeatedly set to the same state, but not change state once set. + */ +extern int +qpt_set_qpthreads_enabled(int how) +{ + switch (qpthreads_enabled_state) + { + case qpt_state_unset: + break ; + case qpt_state_set_frozen: + if (how != 0) + return 0 ; + break ; + case qpt_state_set_disabled: + if (how != 0) + zabort("qpthreads_enabled is already set: cannot set enabled") ; + break ; + case qpt_state_set_enabled: + if (how == 0) + zabort("qpthreads_enabled is already set: cannot set disabled") ; + break ; + default: + break ; + } + + qpthreads_enabled_flag = (how != 0) ; + qpthreads_enabled_state = (how != 0) ? qpt_state_set_enabled + : qpt_state_set_disabled ; + return 1 ; +} ; + +/* Get state of qpthreads_enabled, and freeze if not yet explictly set. + * + * Where some initialisation depends on the state of qpthreads_enabled(), this + * returns the state and freezes it if it is implicitly not enabled. + */ +extern int +qpt_freeze_qpthreads_enabled(void) +{ + if (qpthreads_enabled_state == qpt_state_unset) + qpthreads_enabled_state = qpt_state_set_frozen ; + + return qpthreads_enabled_flag ; +} ; + +/*============================================================================== + * Thread creation and attributes. + * + * Threads may be created with a given set of attributes if required. + * + * qpt_thread_attr_init() will initialise a set of attributes including the + * current standard scheduling attributes. It is envisaged that configuration + * options may be used to specify these. + * + * qpt_thread_create() creates a thread using the given attributes. If those + * are NULL, then the system defaults are used. + */ + +/* Initialise a set of attributes -- setting the scheduling options. + * + * Options: + * + * qpt_attr_joinable -- the default if nothing specified. + * qpt_attr_detached -- overrides qpt_attr_joinable. + * + * qpt_attr_sched_inherit -- all scheduling attributes are to be inherited. + * No explicit scheduling attributes may be set. + * + * qpt_attr_sched_scope -- set explicit, given, scope. + * qpt_attr_sched_policy -- set explicit, given, policy + * qpt_attr_sched_priority -- set explicit, given, priority + * + * If none of the _sched_ options are given, then the scheduling attributes are + * left to whatever default values the system chooses. + * + * If the _sched_inherit option is specified, none of the other _sched_ options + * may be specified. + * + * If any of the explicit scheduling options are given, they are set in this + * order. If only some of these options are given, then the caller is + * assuming that the system will choose sensible defaults. + * + * The scope, policy and priority arguments are use only if the corresponding + * option is specified. + * + * NB: FATAL error to attempt this is !qptthreads_enabled. + * + * Returns the address of the qpt_thread_attr_t structure. + */ +qpt_thread_attr_t* +qpt_thread_attr_init(qpt_thread_attr_t* attr, enum qpt_attr_options opts, + int scope, int policy, int priority) +{ + int err ; + + assert((opts & ~qpt_attr_known) == 0) ; + passert(qpthreads_enabled) ; + + /* Initialise thread attributes structure (allocating if required.) */ + if (attr == NULL) + attr = XMALLOC(MTYPE_QPT_THREAD_ATTR, sizeof(qpt_thread_attr_t)) ; + + err = pthread_attr_init(attr) ; + if (err != 0) + zabort_err("pthread_attr_init failed", err) ; + + /* If not qpt_attr_detached, then set joinable. */ + err = pthread_attr_setdetachstate(attr, + (opts & qpt_attr_detached) ? PTHREAD_CREATE_DETACHED + : PTHREAD_CREATE_JOINABLE) ; + if (err != 0) + zabort_err("pthread_attr_setdetachstate failed", err) ; + + /* If setting anything to do with scheduling... */ + if (opts & qpt_attr_sched_setting) + { + /* Either we inherit or we set explicit parameters. */ + + err = pthread_attr_setinheritsched(attr, + (opts & qpt_attr_sched_inherit) ? PTHREAD_INHERIT_SCHED + : PTHREAD_EXPLICIT_SCHED) ; + if (err != 0) + zabort_err("pthread_attr_setinheritsched", err) ; + + if (opts & qpt_attr_sched_inherit) + assert((opts & qpt_attr_sched_explicit) == 0) ; + else + { + if (opts & qpt_attr_sched_scope) + { + err = pthread_attr_setscope(attr, scope) ; + if (err != 0) + zabort_err("pthread_attr_setscope failed", err) ; + } ; + if (opts & qpt_attr_sched_policy) + { + err = pthread_attr_setschedpolicy(attr, scope) ; + if (err != 0) + zabort_err("pthread_attr_setschedpolicy failed", err) ; + } ; + if (opts & qpt_attr_sched_priority) + { + struct sched_param sparm ; + err = pthread_attr_getschedparam(attr, &sparm) ; + if (err != 0) + zabort_err("pthread_attr_getschedparam failed", err) ; + sparm.sched_priority = priority ; + err = pthread_attr_setschedparam(attr, &sparm) ; + if (err != 0) + zabort_err("pthread_attr_setschedparam failed", err) ; + } ; + } ; + } ; + + /* Done -- return qpt_thread_attr_t* */ + return attr ; +} ; + +/* Create Thread with given attributes (if any). + * + * If no attributes are given (attr == NULL) the thread is created with system + * default attributes -- *except* that it is created joinable. + * + * NB: FATAL error to attempt this is !qptthreads_enabled. + * + * Returns the qpt_thread_t "thread id". + */ +qpt_thread_t +qpt_thread_create(void* (*start)(void*), void* arg, qpt_thread_attr_t* attr) +{ + qpt_thread_attr_t thread_attr ; + qpt_thread_t thread_id ; + int default_attr ; + int err ; + + passert(qpthreads_enabled) ; + qpthreads_thread_created_flag = 1 ; /* and at least one thread created */ + + default_attr = (attr == NULL) ; + if (default_attr) + attr = qpt_thread_attr_init(&thread_attr, qpt_attr_joinable, 0, 0, 0) ; + + err = pthread_create(&thread_id, attr, start, arg) ; + if (err != 0) + zabort_err("pthread_create failed", err) ; + + if (default_attr) + { + err = pthread_attr_destroy(attr) ; /* being tidy */ + if (err != 0) + zabort_err("pthread_attr_destroy failed", err) ; + } ; + + return thread_id ; +} ; + +/* Join given thread -- do nothing if !qpthreads_enabled + * + * Tolerates ESRCH (no thread known by given id). + * + * Returns whatever the thread returns, NULL otherwise. + * + * NB: all other errors are FATAL. + */ +extern void* +qpt_thread_join(qpt_thread_t thread_id) +{ + int err ; + void* ret ; + + if (!qpthreads_enabled) + return NULL ; + + err = pthread_join(thread_id, &ret) ; + + if (err == 0) + return ret ; + + if (err == ESRCH) + return NULL ; + + zabort_err("pthread_join failed", err) ; +} ; + +/*============================================================================== + * Mutex initialise and destroy. + */ + +/* Initialise Mutex (allocating if required) + * + * Does nothing if !qpthreads_enabled -- but freezes the state (attempting to + * later enable qpthreads will be a FATAL error). + * + * Options: + * + * qpt_mutex_quagga -- see qpthreads.h for discussion of this. + * qpt_mutex_normal -- ie PTHREAD_MUTEX_NORMAL + * qpt_mutex_recursive -- ie PTHREAD_MUTEX_RECURSIVE + * qpt_mutex_errorcheck -- ie PTHREAD_MUTEX_ERRORCHECK + * qpt_mutex_default -- system default + * + * Of these _recursive is the most likely alternative to _quagga... BUT do + * remember that such mutexes DO NOT play well with condition variables. + * + * Returns the mutex -- or original mx if !qpthreads_enabled. + */ +qpt_mutex +qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts) +{ + pthread_mutexattr_t mutex_attr ; + int type ; + int err ; + + if (!qpthreads_enabled_freeze) + { + if (mx != NULL) + memset(mx, 0x0F, sizeof(qpt_mutex_t)) ; + return mx ; + } ; + + if (mx == NULL) + mx = XMALLOC(MTYPE_QPT_MUTEX, sizeof(qpt_mutex_t)) ; + + /* Set up attributes so we can set the mutex type */ + err = pthread_mutexattr_init(&mutex_attr); + if (err != 0) + zabort_err("pthread_mutexattr_init failed", err) ; + + switch(opts) + { + case qpt_mutex_quagga: + type = QPT_MUTEX_TYPE ; + break ; + case qpt_mutex_normal: + type = PTHREAD_MUTEX_NORMAL ; + break ; + case qpt_mutex_recursive: + type = PTHREAD_MUTEX_RECURSIVE ; + break ; + case qpt_mutex_errorcheck: + type = PTHREAD_MUTEX_ERRORCHECK ; + break ; + case qpt_mutex_default: + type = PTHREAD_MUTEX_DEFAULT ; + break ; + default: + zabort("Invalid qpt_mutex option") ; + } ; + + err = pthread_mutexattr_settype(&mutex_attr, type); + if (err != 0) + zabort_err("pthread_mutexattr_settype failed", err) ; + + /* Now we're ready to initialize the mutex itself */ + err = pthread_mutex_init(mx, &mutex_attr) ; + if (err != 0) + zabort_err("pthread_mutex_init failed", err) ; + + /* Be tidy with the attributes */ + err = pthread_mutexattr_destroy(&mutex_attr) ; + if (err != 0) + zabort_err("pthread_mutexattr_destroy failed", err) ; + + /* Done: return the mutex */ + return mx ; +} ; + +/* Destroy given mutex, and (if required) free it. + * -- or do nothing if !qpthreads_enabled. + * + * Returns NULL if freed the mutex, otherwise the address of same. + * + * NB: if !qpthreads_enabled qpt_mutex_init_new() will not have allocated + * anything, so there can be nothing to release -- so does nothing, but + * returns the original mutex address (if any). + */ +qpt_mutex +qpt_mutex_destroy(qpt_mutex mx, int free_mutex) +{ + int err ; + + if (qpthreads_enabled) + { + err = pthread_mutex_destroy(mx) ; + if (err != 0) + zabort_err("pthread_mutex_destroy failed", err) ; + + if (free_mutex) + XFREE(MTYPE_QPT_MUTEX, mx) ; /* sets mx == NULL */ + } ; + + return mx ; +} ; + +/*============================================================================== + * Condition Variable initialise and destroy. + */ + +/* Initialise Condition Variable (allocating if required). + * + * Does nothing if !qpthreads_enabled -- but freezes the state (attempting to + * later enable qpthreads will be a FATAL error). + * + * Options: + * + * qpt_cond_quagga -- use Quagga's default clock + * qpt_cond_realtime -- force CLOCK_REALTIME + * qpt_cond_monotonic -- force CLOCK_MONOTONIC (if available) + * + * NB: FATAL error to attempt this is !qptthreads_enabled. + * + * Returns the condition variable -- or original cv id !qpthreads_enabled. + */ +qpt_cond +qpt_cond_init_new(qpt_cond cv, enum qpt_cond_options opts) +{ + pthread_condattr_t cond_attr ; + int err ; + + if (!qpthreads_enabled_freeze) + { + if (cv != NULL) + memset(cv, 0x0F, sizeof(qpt_cond_t)) ; + return cv ; + } ; + + if (cv == NULL) + cv = XMALLOC(MTYPE_QPT_COND, sizeof(qpt_cond_t)) ; + + /* Set up attributes so we can set the type */ + err = pthread_condattr_init(&cond_attr); + if (err != 0) + zabort_err("pthread_condattr_init failed", err) ; + + switch(opts) + { + case qpt_cond_quagga: + break ; + default: + zabort("Invalid qpt_cond option") ; + } ; + + err = pthread_condattr_setclock(&cond_attr, QPT_COND_CLOCK_ID); + if (err != 0) + zabort_err("pthread_condattr_setclock failed", err) ; + + /* Now we're ready to initialize the condition variable itself */ + err = pthread_cond_init(cv, &cond_attr) ; + if (err != 0) + zabort_err("pthread_cond_init failed", err) ; + + /* Be tidy with the attributes */ + err = pthread_condattr_destroy(&cond_attr) ; + if (err != 0) + zabort_err("pthread_condattr_destroy failed", err) ; + + /* Done: return the condition variable */ + return cv ; +} ; + +/* Destroy given condition variable, and (if required) free it + * -- or do nothing if !qpthreads_enabled. + * + * NB: if !qpthreads_enabled qpt_cond_init_new() will not have allocated + * anything, so there can be nothing to release -- so does nothing, but + * returns the original condition variable address (if any). + * + * Returns NULL if freed the condition variable, otherwise the address of same. + */ +qpt_cond +qpt_cond_destroy(qpt_cond cv, int free_cond) +{ + int err ; + + if (qpthreads_enabled) + { + err = pthread_cond_destroy(cv) ; + if (err != 0) + zabort_err("pthread_cond_destroy failed", err) ; + + if (free_cond) + XFREE(MTYPE_QPT_COND, cv) ; /* sets cv == NULL */ + } ; + + return cv ; +} ; + +/* Wait for given condition variable or time-out + * -- or return immediate success if !qpthreads_enabled. + * + * Returns: wait succeeded (1 => success, 0 => timed-out). + * + * NB: timeout time is a qtime_mono_t (monotonic time). + * + * Has to check the return value, so zabort_errno if not EBUSY. + */ + +int +qpt_cond_timedwait(qpt_cond cv, qpt_mutex mx, qtime_mono_t timeout_time) +{ + struct timespec ts ; + int err ; + + if (qpthreads_enabled) + { + if (QPT_COND_CLOCK_ID != CLOCK_MONOTONIC) + { + timeout_time = qt_clock_gettime(QPT_COND_CLOCK_ID) + + (timeout_time - qt_get_monotonic()) ; + } ; + + err = pthread_cond_timedwait(cv, mx, qtime2timespec(&ts, timeout_time)) ; + if (err == 0) + return 1 ; /* got condition */ + if (err == ETIMEDOUT) + return 0 ; /* got time-out */ + + zabort_err("pthread_cond_timedwait failed", err) ; + } + else + return 0 ; +} ; + +/*============================================================================== + * Signal Handling. + */ + +/* Set thread signal mask -- requires qpthreads_enabled. + * + * Thin wrapper around pthread_sigmask. + * + * zaborts if gets any error. + * + * NB: it is a FATAL error to do this if !qpthreads_enabled. + * + * This is mostly because wish to avoid all pthreads_xxx calls when not + * using pthreads. There is no reason not to use this in a single threaded + * program. + */ +void +qpt_thread_sigmask(int how, const sigset_t* set, sigset_t* oset) +{ + int err ; + + passert(qpthreads_enabled) ; + + if (oset != NULL) + sigemptyset(oset) ; /* to make absolutely sure */ + + err = pthread_sigmask(how, set, oset) ; + if (err != 0) + zabort_err("pthread_sigmask failed", err) ; +} ; + +/* Send given thread the given signal -- requires qpthreads_enabled (!) + * + * Thin wrapper around pthread_kill. + * + * zaborts if gets any error. + */ +void +qpt_thread_signal(qpt_thread_t thread, int signum) +{ + int err ; + + passert(qpthreads_enabled) ; + + err = pthread_kill(thread, signum) ; + if (err != 0) + zabort_err("pthread_kill failed", err) ; +} ; |