1238510fcSJason Evans /*- 2238510fcSJason Evans * Copyright (c) 2000 Jake Burkholder <jake@freebsd.org>. 3238510fcSJason Evans * All rights reserved. 4238510fcSJason Evans * 5238510fcSJason Evans * Redistribution and use in source and binary forms, with or without 6238510fcSJason Evans * modification, are permitted provided that the following conditions 7238510fcSJason Evans * are met: 8238510fcSJason Evans * 1. Redistributions of source code must retain the above copyright 9238510fcSJason Evans * notice, this list of conditions and the following disclaimer. 10238510fcSJason Evans * 2. Redistributions in binary form must reproduce the above copyright 11238510fcSJason Evans * notice, this list of conditions and the following disclaimer in the 12238510fcSJason Evans * documentation and/or other materials provided with the distribution. 13238510fcSJason Evans * 14238510fcSJason Evans * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15238510fcSJason Evans * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16238510fcSJason Evans * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17238510fcSJason Evans * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18238510fcSJason Evans * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19238510fcSJason Evans * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20238510fcSJason Evans * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21238510fcSJason Evans * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22238510fcSJason Evans * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23238510fcSJason Evans * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24238510fcSJason Evans * SUCH DAMAGE. 25238510fcSJason Evans */ 26238510fcSJason Evans 27677b542eSDavid E. O'Brien #include <sys/cdefs.h> 28677b542eSDavid E. O'Brien __FBSDID("$FreeBSD$"); 29677b542eSDavid E. O'Brien 30238510fcSJason Evans #include "opt_ktrace.h" 31238510fcSJason Evans 32238510fcSJason Evans #include <sys/param.h> 33238510fcSJason Evans #include <sys/systm.h> 34fb919e4dSMark Murray #include <sys/lock.h> 35fb919e4dSMark Murray #include <sys/mutex.h> 36238510fcSJason Evans #include <sys/proc.h> 37238510fcSJason Evans #include <sys/kernel.h> 38238510fcSJason Evans #include <sys/ktr.h> 39238510fcSJason Evans #include <sys/condvar.h> 404e997f4bSJeff Roberson #include <sys/sched.h> 41238510fcSJason Evans #include <sys/signalvar.h> 4244f3b092SJohn Baldwin #include <sys/sleepqueue.h> 43238510fcSJason Evans #include <sys/resourcevar.h> 44238510fcSJason Evans #ifdef KTRACE 45238510fcSJason Evans #include <sys/uio.h> 46238510fcSJason Evans #include <sys/ktrace.h> 47238510fcSJason Evans #endif 48238510fcSJason Evans 49238510fcSJason Evans /* 50238510fcSJason Evans * Common sanity checks for cv_wait* functions. 51238510fcSJason Evans */ 52b40ce416SJulian Elischer #define CV_ASSERT(cvp, mp, td) do { \ 53a48740b6SDavid E. O'Brien KASSERT((td) != NULL, ("%s: curthread NULL", __func__)); \ 5471fad9fdSJulian Elischer KASSERT(TD_IS_RUNNING(td), ("%s: not TDS_RUNNING", __func__)); \ 55a48740b6SDavid E. O'Brien KASSERT((cvp) != NULL, ("%s: cvp NULL", __func__)); \ 56a48740b6SDavid E. O'Brien KASSERT((mp) != NULL, ("%s: mp NULL", __func__)); \ 57238510fcSJason Evans mtx_assert((mp), MA_OWNED | MA_NOTRECURSED); \ 58238510fcSJason Evans } while (0) 59238510fcSJason Evans 60238510fcSJason Evans /* 61238510fcSJason Evans * Initialize a condition variable. Must be called before use. 62238510fcSJason Evans */ 63238510fcSJason Evans void 64238510fcSJason Evans cv_init(struct cv *cvp, const char *desc) 65238510fcSJason Evans { 66238510fcSJason Evans 67238510fcSJason Evans cvp->cv_description = desc; 689000d57dSJohn Baldwin cvp->cv_waiters = 0; 69238510fcSJason Evans } 70238510fcSJason Evans 71238510fcSJason Evans /* 72238510fcSJason Evans * Destroy a condition variable. The condition variable must be re-initialized 73238510fcSJason Evans * in order to be re-used. 74238510fcSJason Evans */ 75238510fcSJason Evans void 76238510fcSJason Evans cv_destroy(struct cv *cvp) 77238510fcSJason Evans { 7844f3b092SJohn Baldwin #ifdef INVARIANTS 7944f3b092SJohn Baldwin struct sleepqueue *sq; 80238510fcSJason Evans 812ff0e645SJohn Baldwin sleepq_lock(cvp); 8244f3b092SJohn Baldwin sq = sleepq_lookup(cvp); 8344f3b092SJohn Baldwin sleepq_release(cvp); 8444f3b092SJohn Baldwin KASSERT(sq == NULL, ("%s: associated sleep queue non-empty", __func__)); 8544f3b092SJohn Baldwin #endif 86238510fcSJason Evans } 87238510fcSJason Evans 88238510fcSJason Evans /* 89b40ce416SJulian Elischer * Wait on a condition variable. The current thread is placed on the condition 90238510fcSJason Evans * variable's wait queue and suspended. A cv_signal or cv_broadcast on the same 91b40ce416SJulian Elischer * condition variable will resume the thread. The mutex is released before 92238510fcSJason Evans * sleeping and will be held on return. It is recommended that the mutex be 93238510fcSJason Evans * held when cv_signal or cv_broadcast are called. 94238510fcSJason Evans */ 95238510fcSJason Evans void 96238510fcSJason Evans cv_wait(struct cv *cvp, struct mtx *mp) 97238510fcSJason Evans { 98238510fcSJason Evans WITNESS_SAVE_DECL(mp); 99238510fcSJason Evans 10019284646SJohn Baldwin WITNESS_SAVE(&mp->mtx_object, mp); 101238510fcSJason Evans 10244f3b092SJohn Baldwin if (cold || panicstr) { 103238510fcSJason Evans /* 104fe799533SAndrew Gallatin * During autoconfiguration, just give interrupts 105fe799533SAndrew Gallatin * a chance, then just return. Don't run any other 106fe799533SAndrew Gallatin * thread or panic below, in case this is the idle 107fe799533SAndrew Gallatin * process and already asleep. 108238510fcSJason Evans */ 109238510fcSJason Evans return; 110238510fcSJason Evans } 1114bc37205SJeffrey Hsu 11292f44a3fSCraig Rodrigues cv_wait_unlock(cvp, mp); 11392f44a3fSCraig Rodrigues mtx_lock(mp); 11492f44a3fSCraig Rodrigues WITNESS_RESTORE(&mp->mtx_object, mp); 11592f44a3fSCraig Rodrigues } 11692f44a3fSCraig Rodrigues 11792f44a3fSCraig Rodrigues /* 11892f44a3fSCraig Rodrigues * Wait on a condition variable. This function differs from cv_wait by 11992f44a3fSCraig Rodrigues * not aquiring the mutex after condition variable was signaled. 12092f44a3fSCraig Rodrigues */ 12192f44a3fSCraig Rodrigues void 12292f44a3fSCraig Rodrigues cv_wait_unlock(struct cv *cvp, struct mtx *mp) 12392f44a3fSCraig Rodrigues { 12492f44a3fSCraig Rodrigues struct thread *td; 12592f44a3fSCraig Rodrigues 12692f44a3fSCraig Rodrigues td = curthread; 12792f44a3fSCraig Rodrigues #ifdef KTRACE 12892f44a3fSCraig Rodrigues if (KTRPOINT(td, KTR_CSW)) 12992f44a3fSCraig Rodrigues ktrcsw(1, 0); 13092f44a3fSCraig Rodrigues #endif 13192f44a3fSCraig Rodrigues CV_ASSERT(cvp, mp, td); 13292f44a3fSCraig Rodrigues WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, &mp->mtx_object, 13392f44a3fSCraig Rodrigues "Waiting on \"%s\"", cvp->cv_description); 13492f44a3fSCraig Rodrigues 13592f44a3fSCraig Rodrigues if (cold || panicstr) { 13692f44a3fSCraig Rodrigues /* 13792f44a3fSCraig Rodrigues * During autoconfiguration, just give interrupts 13892f44a3fSCraig Rodrigues * a chance, then just return. Don't run any other 13992f44a3fSCraig Rodrigues * thread or panic below, in case this is the idle 14092f44a3fSCraig Rodrigues * process and already asleep. 14192f44a3fSCraig Rodrigues */ 14292f44a3fSCraig Rodrigues mtx_unlock(mp); 14392f44a3fSCraig Rodrigues return; 14492f44a3fSCraig Rodrigues } 14592f44a3fSCraig Rodrigues 1462ff0e645SJohn Baldwin sleepq_lock(cvp); 147238510fcSJason Evans 1489000d57dSJohn Baldwin cvp->cv_waiters++; 149c86b6ff5SJohn Baldwin DROP_GIANT(); 150c86b6ff5SJohn Baldwin mtx_unlock(mp); 151238510fcSJason Evans 1522ff0e645SJohn Baldwin sleepq_add(cvp, mp, cvp->cv_description, SLEEPQ_CONDVAR); 15344f3b092SJohn Baldwin sleepq_wait(cvp); 154238510fcSJason Evans 155238510fcSJason Evans PICKUP_GIANT(); 156238510fcSJason Evans } 157238510fcSJason Evans 158238510fcSJason Evans /* 159238510fcSJason Evans * Wait on a condition variable, allowing interruption by signals. Return 0 if 160b40ce416SJulian Elischer * the thread was resumed with cv_signal or cv_broadcast, EINTR or ERESTART if 161238510fcSJason Evans * a signal was caught. If ERESTART is returned the system call should be 162238510fcSJason Evans * restarted if possible. 163238510fcSJason Evans */ 164238510fcSJason Evans int 165238510fcSJason Evans cv_wait_sig(struct cv *cvp, struct mtx *mp) 166238510fcSJason Evans { 167b40ce416SJulian Elischer struct thread *td; 16866f769feSPeter Wemm struct proc *p; 16944f3b092SJohn Baldwin int rval, sig; 170238510fcSJason Evans WITNESS_SAVE_DECL(mp); 171238510fcSJason Evans 172b40ce416SJulian Elischer td = curthread; 1739ef3a985SJohn Baldwin p = td->td_proc; 174238510fcSJason Evans #ifdef KTRACE 1759ba7fe1bSJohn Baldwin if (KTRPOINT(td, KTR_CSW)) 1769ba7fe1bSJohn Baldwin ktrcsw(1, 0); 177238510fcSJason Evans #endif 178b40ce416SJulian Elischer CV_ASSERT(cvp, mp, td); 17926306795SJohn Baldwin WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, &mp->mtx_object, 18026306795SJohn Baldwin "Waiting on \"%s\"", cvp->cv_description); 18119284646SJohn Baldwin WITNESS_SAVE(&mp->mtx_object, mp); 182238510fcSJason Evans 183238510fcSJason Evans if (cold || panicstr) { 184238510fcSJason Evans /* 185238510fcSJason Evans * After a panic, or during autoconfiguration, just give 186238510fcSJason Evans * interrupts a chance, then just return; don't run any other 187238510fcSJason Evans * procs or panic below, in case this is the idle process and 188238510fcSJason Evans * already asleep. 189238510fcSJason Evans */ 190274f8f48SJohn Baldwin return (0); 191238510fcSJason Evans } 1924bc37205SJeffrey Hsu 1932ff0e645SJohn Baldwin sleepq_lock(cvp); 1944bc37205SJeffrey Hsu 195274f8f48SJohn Baldwin /* 196274f8f48SJohn Baldwin * Don't bother sleeping if we are exiting and not the exiting 197274f8f48SJohn Baldwin * thread or if our thread is marked as interrupted. 198274f8f48SJohn Baldwin */ 199274f8f48SJohn Baldwin mtx_lock_spin(&sched_lock); 200007ddf7eSJohn Baldwin rval = thread_sleep_check(td); 201274f8f48SJohn Baldwin mtx_unlock_spin(&sched_lock); 202007ddf7eSJohn Baldwin if (rval != 0) { 203274f8f48SJohn Baldwin sleepq_release(cvp); 204274f8f48SJohn Baldwin return (rval); 205274f8f48SJohn Baldwin } 206238510fcSJason Evans 2079000d57dSJohn Baldwin cvp->cv_waiters++; 208c86b6ff5SJohn Baldwin DROP_GIANT(); 209c86b6ff5SJohn Baldwin mtx_unlock(mp); 210238510fcSJason Evans 2112ff0e645SJohn Baldwin sleepq_add(cvp, mp, cvp->cv_description, SLEEPQ_CONDVAR | 212007ddf7eSJohn Baldwin SLEEPQ_INTERRUPTIBLE); 21344f3b092SJohn Baldwin sig = sleepq_catch_signals(cvp); 21444f3b092SJohn Baldwin rval = sleepq_wait_sig(cvp); 21544f3b092SJohn Baldwin if (rval == 0) 21644f3b092SJohn Baldwin rval = sleepq_calc_signal_retval(sig); 217238510fcSJason Evans 218238510fcSJason Evans #ifdef KTRACE 2199ba7fe1bSJohn Baldwin if (KTRPOINT(td, KTR_CSW)) 2209ba7fe1bSJohn Baldwin ktrcsw(0, 0); 221238510fcSJason Evans #endif 2229ba7fe1bSJohn Baldwin PICKUP_GIANT(); 2239ed346baSBosko Milekic mtx_lock(mp); 22419284646SJohn Baldwin WITNESS_RESTORE(&mp->mtx_object, mp); 225238510fcSJason Evans 226238510fcSJason Evans return (rval); 227238510fcSJason Evans } 228238510fcSJason Evans 229238510fcSJason Evans /* 230238510fcSJason Evans * Wait on a condition variable for at most timo/hz seconds. Returns 0 if the 231238510fcSJason Evans * process was resumed by cv_signal or cv_broadcast, EWOULDBLOCK if the timeout 232238510fcSJason Evans * expires. 233238510fcSJason Evans */ 234238510fcSJason Evans int 235238510fcSJason Evans cv_timedwait(struct cv *cvp, struct mtx *mp, int timo) 236238510fcSJason Evans { 237b40ce416SJulian Elischer struct thread *td; 238238510fcSJason Evans int rval; 239238510fcSJason Evans WITNESS_SAVE_DECL(mp); 240238510fcSJason Evans 241b40ce416SJulian Elischer td = curthread; 242238510fcSJason Evans rval = 0; 243238510fcSJason Evans #ifdef KTRACE 2449ba7fe1bSJohn Baldwin if (KTRPOINT(td, KTR_CSW)) 2459ba7fe1bSJohn Baldwin ktrcsw(1, 0); 246238510fcSJason Evans #endif 247b40ce416SJulian Elischer CV_ASSERT(cvp, mp, td); 24826306795SJohn Baldwin WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, &mp->mtx_object, 24926306795SJohn Baldwin "Waiting on \"%s\"", cvp->cv_description); 25019284646SJohn Baldwin WITNESS_SAVE(&mp->mtx_object, mp); 251238510fcSJason Evans 252238510fcSJason Evans if (cold || panicstr) { 253238510fcSJason Evans /* 254238510fcSJason Evans * After a panic, or during autoconfiguration, just give 255238510fcSJason Evans * interrupts a chance, then just return; don't run any other 256b40ce416SJulian Elischer * thread or panic below, in case this is the idle process and 257238510fcSJason Evans * already asleep. 258238510fcSJason Evans */ 259238510fcSJason Evans return 0; 260238510fcSJason Evans } 2614bc37205SJeffrey Hsu 2622ff0e645SJohn Baldwin sleepq_lock(cvp); 263238510fcSJason Evans 2649000d57dSJohn Baldwin cvp->cv_waiters++; 265c86b6ff5SJohn Baldwin DROP_GIANT(); 266c86b6ff5SJohn Baldwin mtx_unlock(mp); 267238510fcSJason Evans 2682ff0e645SJohn Baldwin sleepq_add(cvp, mp, cvp->cv_description, SLEEPQ_CONDVAR); 2691ed3e44fSJohn Baldwin sleepq_set_timeout(cvp, timo); 270a5471e4eSJohn Baldwin rval = sleepq_timedwait(cvp); 271238510fcSJason Evans 272238510fcSJason Evans #ifdef KTRACE 2739ba7fe1bSJohn Baldwin if (KTRPOINT(td, KTR_CSW)) 2749ba7fe1bSJohn Baldwin ktrcsw(0, 0); 275238510fcSJason Evans #endif 276238510fcSJason Evans PICKUP_GIANT(); 2779ed346baSBosko Milekic mtx_lock(mp); 27819284646SJohn Baldwin WITNESS_RESTORE(&mp->mtx_object, mp); 279238510fcSJason Evans 280238510fcSJason Evans return (rval); 281238510fcSJason Evans } 282238510fcSJason Evans 283238510fcSJason Evans /* 284238510fcSJason Evans * Wait on a condition variable for at most timo/hz seconds, allowing 285b40ce416SJulian Elischer * interruption by signals. Returns 0 if the thread was resumed by cv_signal 286238510fcSJason Evans * or cv_broadcast, EWOULDBLOCK if the timeout expires, and EINTR or ERESTART if 287238510fcSJason Evans * a signal was caught. 288238510fcSJason Evans */ 289238510fcSJason Evans int 290238510fcSJason Evans cv_timedwait_sig(struct cv *cvp, struct mtx *mp, int timo) 291238510fcSJason Evans { 292b40ce416SJulian Elischer struct thread *td; 2939ef3a985SJohn Baldwin struct proc *p; 294238510fcSJason Evans int rval; 295238510fcSJason Evans int sig; 296238510fcSJason Evans WITNESS_SAVE_DECL(mp); 297238510fcSJason Evans 298b40ce416SJulian Elischer td = curthread; 2999ef3a985SJohn Baldwin p = td->td_proc; 300238510fcSJason Evans rval = 0; 301238510fcSJason Evans #ifdef KTRACE 3029ba7fe1bSJohn Baldwin if (KTRPOINT(td, KTR_CSW)) 3039ba7fe1bSJohn Baldwin ktrcsw(1, 0); 304238510fcSJason Evans #endif 305b40ce416SJulian Elischer CV_ASSERT(cvp, mp, td); 30626306795SJohn Baldwin WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, &mp->mtx_object, 30726306795SJohn Baldwin "Waiting on \"%s\"", cvp->cv_description); 30819284646SJohn Baldwin WITNESS_SAVE(&mp->mtx_object, mp); 309238510fcSJason Evans 310238510fcSJason Evans if (cold || panicstr) { 311238510fcSJason Evans /* 312238510fcSJason Evans * After a panic, or during autoconfiguration, just give 313238510fcSJason Evans * interrupts a chance, then just return; don't run any other 314b40ce416SJulian Elischer * thread or panic below, in case this is the idle process and 315238510fcSJason Evans * already asleep. 316238510fcSJason Evans */ 317238510fcSJason Evans return 0; 318238510fcSJason Evans } 3194bc37205SJeffrey Hsu 3202ff0e645SJohn Baldwin sleepq_lock(cvp); 321238510fcSJason Evans 322274f8f48SJohn Baldwin /* 323274f8f48SJohn Baldwin * Don't bother sleeping if we are exiting and not the exiting 324274f8f48SJohn Baldwin * thread or if our thread is marked as interrupted. 325274f8f48SJohn Baldwin */ 326274f8f48SJohn Baldwin mtx_lock_spin(&sched_lock); 327007ddf7eSJohn Baldwin rval = thread_sleep_check(td); 328274f8f48SJohn Baldwin mtx_unlock_spin(&sched_lock); 329007ddf7eSJohn Baldwin if (rval != 0) { 330274f8f48SJohn Baldwin sleepq_release(cvp); 331274f8f48SJohn Baldwin return (rval); 332274f8f48SJohn Baldwin } 333274f8f48SJohn Baldwin 3349000d57dSJohn Baldwin cvp->cv_waiters++; 335c86b6ff5SJohn Baldwin DROP_GIANT(); 336c86b6ff5SJohn Baldwin mtx_unlock(mp); 337238510fcSJason Evans 3382ff0e645SJohn Baldwin sleepq_add(cvp, mp, cvp->cv_description, SLEEPQ_CONDVAR | 339007ddf7eSJohn Baldwin SLEEPQ_INTERRUPTIBLE); 3401ed3e44fSJohn Baldwin sleepq_set_timeout(cvp, timo); 34144f3b092SJohn Baldwin sig = sleepq_catch_signals(cvp); 34244f3b092SJohn Baldwin rval = sleepq_timedwait_sig(cvp, sig != 0); 34344f3b092SJohn Baldwin if (rval == 0) 34444f3b092SJohn Baldwin rval = sleepq_calc_signal_retval(sig); 345238510fcSJason Evans 346238510fcSJason Evans #ifdef KTRACE 3479ba7fe1bSJohn Baldwin if (KTRPOINT(td, KTR_CSW)) 3489ba7fe1bSJohn Baldwin ktrcsw(0, 0); 349238510fcSJason Evans #endif 3509ba7fe1bSJohn Baldwin PICKUP_GIANT(); 3519ed346baSBosko Milekic mtx_lock(mp); 35219284646SJohn Baldwin WITNESS_RESTORE(&mp->mtx_object, mp); 353238510fcSJason Evans 354238510fcSJason Evans return (rval); 355238510fcSJason Evans } 356238510fcSJason Evans 357238510fcSJason Evans /* 358b40ce416SJulian Elischer * Signal a condition variable, wakes up one waiting thread. Will also wakeup 359238510fcSJason Evans * the swapper if the process is not in memory, so that it can bring the 360b40ce416SJulian Elischer * sleeping process in. Note that this may also result in additional threads 361238510fcSJason Evans * being made runnable. Should be called with the same mutex as was passed to 362238510fcSJason Evans * cv_wait held. 363238510fcSJason Evans */ 364238510fcSJason Evans void 365238510fcSJason Evans cv_signal(struct cv *cvp) 366238510fcSJason Evans { 367238510fcSJason Evans 3682ff0e645SJohn Baldwin sleepq_lock(cvp); 3699000d57dSJohn Baldwin if (cvp->cv_waiters > 0) { 3709000d57dSJohn Baldwin cvp->cv_waiters--; 37144f3b092SJohn Baldwin sleepq_signal(cvp, SLEEPQ_CONDVAR, -1); 3722ff0e645SJohn Baldwin } else 3732ff0e645SJohn Baldwin sleepq_release(cvp); 3749000d57dSJohn Baldwin } 375238510fcSJason Evans 376238510fcSJason Evans /* 377b40ce416SJulian Elischer * Broadcast a signal to a condition variable. Wakes up all waiting threads. 378238510fcSJason Evans * Should be called with the same mutex as was passed to cv_wait held. 379238510fcSJason Evans */ 380238510fcSJason Evans void 381512824f8SSeigo Tanimura cv_broadcastpri(struct cv *cvp, int pri) 382238510fcSJason Evans { 383238510fcSJason Evans 3842ff0e645SJohn Baldwin sleepq_lock(cvp); 3859000d57dSJohn Baldwin if (cvp->cv_waiters > 0) { 3869000d57dSJohn Baldwin cvp->cv_waiters = 0; 38744f3b092SJohn Baldwin sleepq_broadcast(cvp, SLEEPQ_CONDVAR, pri); 3882ff0e645SJohn Baldwin } else 3892ff0e645SJohn Baldwin sleepq_release(cvp); 3909000d57dSJohn Baldwin } 391