locking.c revision 507fd43f
1/* 2 3Copyright 1992, 1998 The Open Group 4 5Permission to use, copy, modify, distribute, and sell this software and its 6documentation for any purpose is hereby granted without fee, provided that 7the above copyright notice appear in all copies and that both that 8copyright notice and this permission notice appear in supporting 9documentation. 10 11The above copyright notice and this permission notice shall be included in 12all copies or substantial portions of the Software. 13 14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 18AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 21Except as contained in this notice, the name of The Open Group shall not be 22used in advertising or otherwise to promote the sale, use or other dealings 23in this Software without prior written authorization from The Open Group. 24 25*/ 26 27/* 28 * Author: Stephen Gildea, MIT X Consortium 29 * 30 * locking.c - multi-thread locking routines implemented in C Threads 31 */ 32 33#ifdef HAVE_CONFIG_H 34#include <config.h> 35#endif 36#include "Xlibint.h" 37#undef _XLockMutex 38#undef _XUnlockMutex 39#undef _XCreateMutex 40#undef _XFreeMutex 41 42#ifdef XTHREADS 43 44#ifdef __UNIXWARE__ 45#include <dlfcn.h> 46#endif 47 48#include "Xprivate.h" 49#include "locking.h" 50#ifdef XTHREADS_WARN 51#include <stdio.h> /* for warn/debug stuff */ 52#endif 53 54/* Additional arguments for source code location lock call was made from */ 55#if defined(XTHREADS_WARN) || defined(XTHREADS_FILE_LINE) 56# define XTHREADS_FILE_LINE_ARGS \ 57 , \ 58 char* file, /* source file, from macro */ \ 59 int line 60#else 61# define XTHREADS_FILE_LINE_ARGS /* None */ 62#endif 63 64 65#define NUM_FREE_CVLS 4 66 67/* in lcWrap.c */ 68extern LockInfoPtr _Xi18n_lock; 69/* in lcConv.c */ 70extern LockInfoPtr _conv_lock; 71 72#ifdef WIN32 73static DWORD _X_TlsIndex = (DWORD)-1; 74 75void _Xthread_init(void) 76{ 77 if (_X_TlsIndex == (DWORD)-1) 78 _X_TlsIndex = TlsAlloc(); 79} 80 81struct _xthread_waiter * 82_Xthread_waiter(void) 83{ 84 struct _xthread_waiter *me; 85 86 if (!(me = TlsGetValue(_X_TlsIndex))) { 87 me = xmalloc(sizeof(struct _xthread_waiter)); 88 me->sem = CreateSemaphore(NULL, 0, 1, NULL); 89 me->next = NULL; 90 TlsSetValue(_X_TlsIndex, me); 91 } 92 return me; 93} 94#endif /* WIN32 */ 95 96static xthread_t _Xthread_self(void) 97{ 98 return xthread_self(); 99} 100 101static LockInfoRec global_lock; 102static LockInfoRec i18n_lock; 103static LockInfoRec conv_lock; 104 105static void _XLockMutex( 106 LockInfoPtr lip 107 XTHREADS_FILE_LINE_ARGS 108 ) 109{ 110 xmutex_lock(lip->lock); 111} 112 113static void _XUnlockMutex( 114 LockInfoPtr lip 115 XTHREADS_FILE_LINE_ARGS 116 ) 117{ 118 xmutex_unlock(lip->lock); 119} 120 121static void _XCreateMutex( 122 LockInfoPtr lip) 123{ 124 lip->lock = xmutex_malloc(); 125 if (lip->lock) { 126 xmutex_init(lip->lock); 127 xmutex_set_name(lip->lock, "Xlib"); 128 } 129} 130 131static void _XFreeMutex( 132 LockInfoPtr lip) 133{ 134 xmutex_clear(lip->lock); 135 xmutex_free(lip->lock); 136 lip->lock = NULL; 137} 138 139#ifdef XTHREADS_WARN 140static char *locking_file; 141static int locking_line; 142static xthread_t locking_thread; 143static Bool xlibint_unlock = False; /* XlibInt.c may Unlock and re-Lock */ 144 145/* history that is useful to examine in a debugger */ 146#define LOCK_HIST_SIZE 21 147 148static struct { 149 Bool lockp; /* True for lock, False for unlock */ 150 xthread_t thread; 151 char *file; 152 int line; 153} locking_history[LOCK_HIST_SIZE]; 154 155int lock_hist_loc = 0; /* next slot to fill */ 156 157static void _XLockDisplayWarn( 158 Display *dpy, 159 char *file, /* source file, from macro */ 160 int line) 161{ 162 xthread_t self; 163 xthread_t old_locker; 164 165 self = xthread_self(); 166 old_locker = locking_thread; 167 if (xthread_have_id(old_locker)) { 168 if (xthread_equal(old_locker, self)) 169 printf("Xlib ERROR: %s line %d thread %x: locking display already locked at %s line %d\n", 170 file, line, self, locking_file, locking_line); 171#ifdef XTHREADS_DEBUG 172 else 173 printf("%s line %d: thread %x waiting on lock held by %s line %d thread %x\n", 174 file, line, self, 175 locking_file, locking_line, old_locker); 176#endif /* XTHREADS_DEBUG */ 177 } 178 179 xmutex_lock(dpy->lock->mutex); 180 181 if (strcmp(file, "XlibInt.c") == 0) { 182 if (!xlibint_unlock) 183 printf("Xlib ERROR: XlibInt.c line %d thread %x locking display it did not unlock\n", 184 line, self); 185 xlibint_unlock = False; 186 } 187 188#ifdef XTHREADS_DEBUG 189 /* if (old_locker && old_locker != self) */ 190 if (strcmp("XClearArea.c", file) && strcmp("XDrSegs.c", file)) /* ico */ 191 printf("%s line %d: thread %x got display lock\n", file, line, self); 192#endif /* XTHREADS_DEBUG */ 193 194 locking_thread = self; 195 if (strcmp(file, "XlibInt.c") != 0) { 196 locking_file = file; 197 locking_line = line; 198 } 199 locking_history[lock_hist_loc].file = file; 200 locking_history[lock_hist_loc].line = line; 201 locking_history[lock_hist_loc].thread = self; 202 locking_history[lock_hist_loc].lockp = True; 203 lock_hist_loc++; 204 if (lock_hist_loc >= LOCK_HIST_SIZE) 205 lock_hist_loc = 0; 206} 207#endif /* XTHREADS_WARN */ 208 209static void _XUnlockDisplay( 210 Display *dpy 211 XTHREADS_FILE_LINE_ARGS 212 ) 213{ 214#ifdef XTHREADS_WARN 215 xthread_t self = xthread_self(); 216 217#ifdef XTHREADS_DEBUG 218 if (strcmp("XClearArea.c", file) && strcmp("XDrSegs.c", file)) /* ico */ 219 printf("%s line %d: thread %x unlocking display\n", file, line, self); 220#endif /* XTHREADS_DEBUG */ 221 222 if (!xthread_have_id(locking_thread)) 223 printf("Xlib ERROR: %s line %d thread %x: unlocking display that is not locked\n", 224 file, line, self); 225 else if (strcmp(file, "XlibInt.c") == 0) 226 xlibint_unlock = True; 227#ifdef XTHREADS_DEBUG 228 else if (strcmp(file, locking_file) != 0) 229 /* not always an error because locking_file is not per-thread */ 230 printf("%s line %d: unlocking display locked from %s line %d (probably okay)\n", 231 file, line, locking_file, locking_line); 232#endif /* XTHREADS_DEBUG */ 233 xthread_clear_id(locking_thread); 234 235 locking_history[lock_hist_loc].file = file; 236 locking_history[lock_hist_loc].line = line; 237 locking_history[lock_hist_loc].thread = self; 238 locking_history[lock_hist_loc].lockp = False; 239 lock_hist_loc++; 240 if (lock_hist_loc >= LOCK_HIST_SIZE) 241 lock_hist_loc = 0; 242#endif /* XTHREADS_WARN */ 243 xmutex_unlock(dpy->lock->mutex); 244} 245 246 247static struct _XCVList *_XCreateCVL( 248 Display *dpy) 249{ 250 struct _XCVList *cvl; 251 252 if ((cvl = dpy->lock->free_cvls) != NULL) { 253 dpy->lock->free_cvls = cvl->next; 254 dpy->lock->num_free_cvls--; 255 } else { 256 cvl = Xmalloc(sizeof(struct _XCVList)); 257 if (!cvl) 258 return NULL; 259 cvl->cv = xcondition_malloc(); 260 if (!cvl->cv) { 261 Xfree(cvl); 262 return NULL; 263 } 264 xcondition_init(cvl->cv); 265 xcondition_set_name(cvl->cv, "Xlib read queue"); 266 } 267 cvl->next = NULL; 268 return cvl; 269} 270 271/* Put ourselves on the queue to read the connection. 272 Allocates and returns a queue element. */ 273 274static struct _XCVList * 275_XPushReader( 276 Display *dpy, 277 struct _XCVList ***tail) 278{ 279 struct _XCVList *cvl; 280 281 cvl = _XCreateCVL(dpy); 282#ifdef XTHREADS_DEBUG 283 printf("_XPushReader called in thread %x, pushing %x\n", 284 xthread_self(), cvl); 285#endif 286 **tail = cvl; 287 *tail = &cvl->next; 288 return cvl; 289} 290 291/* signal the next thread waiting to read the connection */ 292 293static void _XPopReader( 294 Display *dpy, 295 struct _XCVList **list, 296 struct _XCVList ***tail) 297{ 298 register struct _XCVList *front = *list; 299 300#ifdef XTHREADS_DEBUG 301 printf("_XPopReader called in thread %x, popping %x\n", 302 xthread_self(), front); 303#endif 304 305 if (dpy->flags & XlibDisplayProcConni) 306 /* we never added ourself in the first place */ 307 return; 308 309 if (front) { /* check "front" for paranoia */ 310 *list = front->next; 311 if (*tail == &front->next) /* did we free the last elt? */ 312 *tail = list; 313 if (dpy->lock->num_free_cvls < NUM_FREE_CVLS) { 314 front->next = dpy->lock->free_cvls; 315 dpy->lock->free_cvls = front; 316 dpy->lock->num_free_cvls++; 317 } else { 318 xcondition_clear(front->cv); 319 Xfree(front->cv); 320 Xfree(front); 321 } 322 } 323 324 /* signal new front after it is in place */ 325 if ((dpy->lock->reply_first = (dpy->lock->reply_awaiters != NULL))) { 326 ConditionSignal(dpy, dpy->lock->reply_awaiters->cv); 327 } else if (dpy->lock->event_awaiters) { 328 ConditionSignal(dpy, dpy->lock->event_awaiters->cv); 329 } 330} 331 332static void _XConditionWait( 333 xcondition_t cv, 334 xmutex_t mutex 335 XTHREADS_FILE_LINE_ARGS 336 ) 337{ 338#ifdef XTHREADS_WARN 339 xthread_t self = xthread_self(); 340 char *old_file = locking_file; 341 int old_line = locking_line; 342 343#ifdef XTHREADS_DEBUG 344 printf("line %d thread %x in condition wait\n", line, self); 345#endif 346 xthread_clear_id(locking_thread); 347 348 locking_history[lock_hist_loc].file = file; 349 locking_history[lock_hist_loc].line = line; 350 locking_history[lock_hist_loc].thread = self; 351 locking_history[lock_hist_loc].lockp = False; 352 lock_hist_loc++; 353 if (lock_hist_loc >= LOCK_HIST_SIZE) 354 lock_hist_loc = 0; 355#endif /* XTHREADS_WARN */ 356 357 xcondition_wait(cv, mutex); 358 359#ifdef XTHREADS_WARN 360 locking_thread = self; 361 locking_file = old_file; 362 locking_line = old_line; 363 364 locking_history[lock_hist_loc].file = file; 365 locking_history[lock_hist_loc].line = line; 366 locking_history[lock_hist_loc].thread = self; 367 locking_history[lock_hist_loc].lockp = True; 368 lock_hist_loc++; 369 if (lock_hist_loc >= LOCK_HIST_SIZE) 370 lock_hist_loc = 0; 371#ifdef XTHREADS_DEBUG 372 printf("line %d thread %x was signaled\n", line, self); 373#endif /* XTHREADS_DEBUG */ 374#endif /* XTHREADS_WARN */ 375} 376 377static void _XConditionSignal( 378 xcondition_t cv 379 XTHREADS_FILE_LINE_ARGS 380 ) 381{ 382#ifdef XTHREADS_WARN 383#ifdef XTHREADS_DEBUG 384 printf("line %d thread %x is signalling\n", line, xthread_self()); 385#endif 386#endif 387 xcondition_signal(cv); 388} 389 390 391static void _XConditionBroadcast( 392 xcondition_t cv 393 XTHREADS_FILE_LINE_ARGS 394 ) 395{ 396#ifdef XTHREADS_WARN 397#ifdef XTHREADS_DEBUG 398 printf("line %d thread %x is broadcasting\n", line, xthread_self()); 399#endif 400#endif 401 xcondition_broadcast(cv); 402} 403 404 405static void _XFreeDisplayLock( 406 Display *dpy) 407{ 408 struct _XCVList *cvl; 409 410 if (dpy->lock != NULL) { 411 if (dpy->lock->mutex != NULL) { 412 xmutex_clear(dpy->lock->mutex); 413 xmutex_free(dpy->lock->mutex); 414 } 415 if (dpy->lock->cv != NULL) { 416 xcondition_clear(dpy->lock->cv); 417 xcondition_free(dpy->lock->cv); 418 } 419 if (dpy->lock->writers != NULL) { 420 xcondition_clear(dpy->lock->writers); 421 xcondition_free(dpy->lock->writers); 422 } 423 while ((cvl = dpy->lock->free_cvls)) { 424 dpy->lock->free_cvls = cvl->next; 425 xcondition_clear(cvl->cv); 426 Xfree(cvl->cv); 427 Xfree(cvl); 428 } 429 Xfree(dpy->lock); 430 dpy->lock = NULL; 431 } 432 if (dpy->lock_fns != NULL) { 433 Xfree(dpy->lock_fns); 434 dpy->lock_fns = NULL; 435 } 436} 437 438/* 439 * wait for thread with user-level display lock to release it. 440 */ 441 442static void _XDisplayLockWait( 443 Display *dpy) 444{ 445 xthread_t self; 446 447 while (dpy->lock->locking_level > 0) { 448 self = xthread_self(); 449 if (xthread_equal(dpy->lock->locking_thread, self)) 450 break; 451 ConditionWait(dpy, dpy->lock->cv); 452 } 453} 454 455static void _XLockDisplay( 456 Display *dpy 457 XTHREADS_FILE_LINE_ARGS 458 ); 459 460static void _XIfEventLockDisplay( 461 Display *dpy 462 XTHREADS_FILE_LINE_ARGS 463 ) 464{ 465 /* assert(dpy->in_ifevent); */ 466} 467 468static void _XInternalLockDisplay( 469 Display *dpy, 470 Bool wskip 471 XTHREADS_FILE_LINE_ARGS 472 ); 473 474static void _XIfEventInternalLockDisplay( 475 Display *dpy, 476 Bool wskip 477 XTHREADS_FILE_LINE_ARGS 478 ) 479{ 480 /* assert(dpy->in_ifevent); */ 481} 482 483static void _XIfEventUnlockDisplay( 484 Display *dpy 485 XTHREADS_FILE_LINE_ARGS 486 ) 487{ 488 if (dpy->in_ifevent == 0) { 489 dpy->lock_fns->lock_display = _XLockDisplay; 490 dpy->lock_fns->unlock_display = _XUnlockDisplay; 491 dpy->lock->internal_lock_display = _XInternalLockDisplay; 492 UnlockDisplay(dpy); 493 } else 494 return; 495} 496 497static void _XLockDisplay( 498 Display *dpy 499 XTHREADS_FILE_LINE_ARGS 500 ) 501{ 502#ifdef XTHREADS 503 struct _XErrorThreadInfo *ti; 504#endif 505#ifdef XTHREADS_WARN 506 _XLockDisplayWarn(dpy, file, line); 507#else 508 xmutex_lock(dpy->lock->mutex); 509#endif 510 if (dpy->lock->locking_level > 0) 511 _XDisplayLockWait(dpy); 512#ifdef XTHREADS 513 /* 514 * Skip the two function calls below which may generate requests 515 * when LockDisplay is called from within _XError. 516 */ 517 for (ti = dpy->error_threads; ti; ti = ti->next) 518 if (ti->error_thread == xthread_self()) 519 return; 520#endif 521 _XIDHandler(dpy); 522 _XSeqSyncFunction(dpy); 523 if (dpy->in_ifevent) { 524 dpy->lock_fns->lock_display = _XIfEventLockDisplay; 525 dpy->lock_fns->unlock_display = _XIfEventUnlockDisplay; 526 dpy->lock->internal_lock_display = _XIfEventInternalLockDisplay; 527 } 528} 529 530/* 531 * _XReply is allowed to exit from select/poll and clean up even if a 532 * user-level lock is in force, so it uses this instead of _XFancyLockDisplay. 533 */ 534static void _XInternalLockDisplay( 535 Display *dpy, 536 Bool wskip 537 XTHREADS_FILE_LINE_ARGS 538 ) 539{ 540#ifdef XTHREADS_WARN 541 _XLockDisplayWarn(dpy, file, line); 542#else 543 xmutex_lock(dpy->lock->mutex); 544#endif 545 if (!wskip && dpy->lock->locking_level > 0) 546 _XDisplayLockWait(dpy); 547} 548 549static void _XUserLockDisplay( 550 register Display* dpy) 551{ 552 _XDisplayLockWait(dpy); 553 554 if (++dpy->lock->locking_level == 1) { 555 dpy->lock->lock_wait = _XDisplayLockWait; 556 dpy->lock->locking_thread = xthread_self(); 557 } 558} 559 560static 561void _XUserUnlockDisplay( 562 register Display* dpy) 563{ 564 if (dpy->lock->locking_level > 0 && --dpy->lock->locking_level == 0) { 565 /* signal other threads that might be waiting in XLockDisplay */ 566 ConditionBroadcast(dpy, dpy->lock->cv); 567 dpy->lock->lock_wait = NULL; 568 xthread_clear_id(dpy->lock->locking_thread); 569 } 570} 571 572/* returns 0 if initialized ok, -1 if unable to allocate 573 a mutex or other memory */ 574 575static int _XInitDisplayLock( 576 Display *dpy) 577{ 578 dpy->lock_fns = Xmalloc(sizeof(struct _XLockPtrs)); 579 if (dpy->lock_fns == NULL) 580 return -1; 581 dpy->lock = Xmalloc(sizeof(struct _XLockInfo)); 582 if (dpy->lock == NULL) { 583 _XFreeDisplayLock(dpy); 584 return -1; 585 } 586 dpy->lock->cv = xcondition_malloc(); 587 dpy->lock->mutex = xmutex_malloc(); 588 dpy->lock->writers = xcondition_malloc(); 589 if (!dpy->lock->cv || !dpy->lock->mutex || !dpy->lock->writers) { 590 _XFreeDisplayLock(dpy); 591 return -1; 592 } 593 594 dpy->lock->reply_bytes_left = 0; 595 dpy->lock->reply_was_read = False; 596 dpy->lock->reply_awaiters = NULL; 597 dpy->lock->reply_awaiters_tail = &dpy->lock->reply_awaiters; 598 dpy->lock->event_awaiters = NULL; 599 dpy->lock->event_awaiters_tail = &dpy->lock->event_awaiters; 600 dpy->lock->reply_first = False; 601 dpy->lock->locking_level = 0; 602 dpy->lock->num_free_cvls = 0; 603 dpy->lock->free_cvls = NULL; 604 xthread_clear_id(dpy->lock->locking_thread); 605 xthread_clear_id(dpy->lock->reading_thread); 606 xthread_clear_id(dpy->lock->conni_thread); 607 xmutex_init(dpy->lock->mutex); 608 xmutex_set_name(dpy->lock->mutex, "Xlib Display"); 609 xcondition_init(dpy->lock->cv); 610 xcondition_set_name(dpy->lock->cv, "XLockDisplay"); 611 xcondition_init(dpy->lock->writers); 612 xcondition_set_name(dpy->lock->writers, "Xlib wait for writable"); 613 dpy->lock_fns->lock_display = _XLockDisplay; 614 dpy->lock->internal_lock_display = _XInternalLockDisplay; 615 dpy->lock_fns->unlock_display = _XUnlockDisplay; 616 dpy->lock->user_lock_display = _XUserLockDisplay; 617 dpy->lock->user_unlock_display = _XUserUnlockDisplay; 618 dpy->lock->pop_reader = _XPopReader; 619 dpy->lock->push_reader = _XPushReader; 620 dpy->lock->condition_wait = _XConditionWait; 621 dpy->lock->condition_signal = _XConditionSignal; 622 dpy->lock->condition_broadcast = _XConditionBroadcast; 623 dpy->lock->create_cvl = _XCreateCVL; 624 dpy->lock->lock_wait = NULL; /* filled in by XLockDisplay() */ 625 626 return 0; 627} 628 629#ifdef __UNIXWARE__ 630xthread_t __x11_thr_self() { return 0; } 631xthread_t (*_x11_thr_self)() = __x11_thr_self; 632#endif 633 634 635Status XInitThreads(void) 636{ 637 if (_Xglobal_lock) 638 return 1; 639#ifdef __UNIXWARE__ 640 else { 641 void *dl_handle = dlopen(NULL, RTLD_LAZY); 642 if (!dl_handle || 643 ((_x11_thr_self = (xthread_t(*)())dlsym(dl_handle,"thr_self")) == 0)) { 644 _x11_thr_self = __x11_thr_self; 645 (void) fprintf (stderr, 646 "XInitThreads called, but no libthread in the calling program!\n" ); 647 } 648 } 649#endif /* __UNIXWARE__ */ 650#ifdef xthread_init 651 xthread_init(); /* return value? */ 652#endif 653 if (!(global_lock.lock = xmutex_malloc())) 654 return 0; 655 if (!(i18n_lock.lock = xmutex_malloc())) { 656 xmutex_free(global_lock.lock); 657 global_lock.lock = NULL; 658 return 0; 659 } 660 if (!(conv_lock.lock = xmutex_malloc())) { 661 xmutex_free(global_lock.lock); 662 global_lock.lock = NULL; 663 xmutex_free(i18n_lock.lock); 664 i18n_lock.lock = NULL; 665 return 0; 666 } 667 _Xglobal_lock = &global_lock; 668 xmutex_init(_Xglobal_lock->lock); 669 xmutex_set_name(_Xglobal_lock->lock, "Xlib global"); 670 _Xi18n_lock = &i18n_lock; 671 xmutex_init(_Xi18n_lock->lock); 672 xmutex_set_name(_Xi18n_lock->lock, "Xlib i18n"); 673 _conv_lock = &conv_lock; 674 xmutex_init(_conv_lock->lock); 675 xmutex_set_name(_conv_lock->lock, "Xlib conv"); 676 _XLockMutex_fn = _XLockMutex; 677 _XUnlockMutex_fn = _XUnlockMutex; 678 _XCreateMutex_fn = _XCreateMutex; 679 _XFreeMutex_fn = _XFreeMutex; 680 _XInitDisplayLock_fn = _XInitDisplayLock; 681 _XFreeDisplayLock_fn = _XFreeDisplayLock; 682 _Xthread_self_fn = _Xthread_self; 683 684#ifdef XTHREADS_WARN 685#ifdef XTHREADS_DEBUG 686 setlinebuf(stdout); /* for debugging messages */ 687#endif 688#endif 689 690 return 1; 691} 692 693Status XFreeThreads(void) 694{ 695 if (global_lock.lock != NULL) { 696 xmutex_free(global_lock.lock); 697 global_lock.lock = NULL; 698 } 699 if (i18n_lock.lock != NULL) { 700 xmutex_free(i18n_lock.lock); 701 i18n_lock.lock = NULL; 702 } 703 if (conv_lock.lock != NULL) { 704 xmutex_free(conv_lock.lock); 705 conv_lock.lock = NULL; 706 } 707 708 return 1; 709} 710 711#else /* XTHREADS */ 712Status XInitThreads(void) 713{ 714 return 0; 715} 716 717Status XFreeThreads(void) 718{ 719 return 0; 720} 721#endif /* XTHREADS */ 722