source: trunk/synergy/lib/arch/CArchMultithreadOS2.cpp

Last change on this file was 2759, checked in by bird, 19 years ago

Can't use DCE_POSTONE or DCE_AUTORESET with MUX. too bad.

File size: 17.0 KB
Line 
1/*
2 * synergy -- mouse and keyboard sharing utility
3 * Copyright (C) 2002 Chris Schoeneman
4 * Copyright (C) 2006 Knut St. Osmundsen
5 *
6 * This package is free software you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * found in the file COPYING that should have accompanied this file.
9 *
10 * This package is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 */
15
16#include "CArchMultithreadOS2.h"
17#include "CArch.h"
18#include "XArch.h"
19#include <process.h>
20
21//
22// note -- implementation of condition variable taken from:
23// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
24// titled "Strategies for Implementing POSIX Condition Variables
25// on Win32." it also provides an implementation that doesn't
26// suffer from the incorrectness problem described in our
27// corresponding header but it is slower, still unfair, and
28// can cause busy waiting.
29//
30
31//
32// CArchThreadImpl
33//
34
35class CArchThreadImpl {
36public:
37 CArchThreadImpl();
38 ~CArchThreadImpl();
39
40public:
41 int m_refCount;
42 int m_id;
43 IArchMultithread::ThreadFunc m_func;
44 void* m_userData;
45 HEV m_cancel;
46 bool m_cancelling;
47 HEV m_exit;
48 void* m_result;
49 void* m_networkData;
50};
51
52CArchThreadImpl::CArchThreadImpl() :
53 m_refCount(1),
54 m_id(0),
55 m_func(NULL),
56 m_userData(NULL),
57 m_cancel(NULLHANDLE),
58 m_cancelling(false),
59 m_exit(NULLHANDLE),
60 m_result(NULL),
61 m_networkData(NULL)
62{
63 APIRET rc = DosCreateEventSem(NULL, &m_exit, 0, FALSE);
64 assert(rc == NO_ERROR);
65 rc = DosCreateEventSem(NULL, &m_cancel, 0, FALSE);
66 assert(rc == NO_ERROR);
67}
68
69CArchThreadImpl::~CArchThreadImpl()
70{
71 DosCloseEventSem(m_exit);
72 DosCloseEventSem(m_cancel);
73}
74
75
76//
77// CArchMultithreadWindows
78//
79
80CArchMultithreadOS2* CArchMultithreadOS2::s_instance = NULL;
81
82CArchMultithreadOS2::CArchMultithreadOS2()
83{
84 assert(s_instance == NULL);
85 s_instance = this;
86
87 // no signal handlers
88 for (size_t i = 0; i < kNUM_SIGNALS; ++i) {
89 m_signalFunc[i] = NULL;
90 m_signalUserData[i] = NULL;
91 }
92
93 // create mutex for thread list
94 m_threadMutex = newMutex();
95
96 // create thread for calling (main) thread and add it to our
97 // list. no need to lock the mutex since we're the only thread.
98 m_mainThread = new CArchThreadImpl;
99 m_mainThread->m_id = _gettid();
100 insert(m_mainThread);
101}
102
103CArchMultithreadOS2::~CArchMultithreadOS2()
104{
105 s_instance = NULL;
106
107 // clean up thread list
108 for (CThreadList::iterator index = m_threadList.begin();
109 index != m_threadList.end(); ++index) {
110 delete *index;
111 }
112
113 // done with mutex
114 delete m_threadMutex;
115}
116
117void
118CArchMultithreadOS2::setNetworkDataForCurrentThread(void* data)
119{
120 lockMutex(m_threadMutex);
121 CArchThreadImpl* thread = findNoRef(_gettid());
122 thread->m_networkData = data;
123 unlockMutex(m_threadMutex);
124}
125
126void*
127CArchMultithreadOS2::getNetworkDataForThread(CArchThread thread)
128{
129 lockMutex(m_threadMutex);
130 void* data = thread->m_networkData;
131 unlockMutex(m_threadMutex);
132 return data;
133}
134
135HEV
136CArchMultithreadOS2::getCancelEventForCurrentThread()
137{
138 lockMutex(m_threadMutex);
139 CArchThreadImpl* thread = findNoRef(_gettid());
140 unlockMutex(m_threadMutex);
141 return thread->m_cancel;
142}
143
144CArchMultithreadOS2*
145CArchMultithreadOS2::getInstance()
146{
147 return s_instance;
148}
149
150CArchCond
151CArchMultithreadOS2::newCondVar()
152{
153 CArchCondImpl* cond = new CArchCondImpl;
154 APIRET rc = DosCreateEventSem(NULL, &cond->m_events[CArchCondImpl::kSignal], /*DCE_AUTORESET*/0, FALSE);
155 assert(rc == NO_ERROR);
156 rc = DosCreateEventSem(NULL, &cond->m_events[CArchCondImpl::kBroadcast], 0, FALSE);
157 assert(rc == NO_ERROR);
158 cond->m_waitCountMutex = newMutex();
159 cond->m_waitCount = 0;
160 return cond;
161}
162
163void
164CArchMultithreadOS2::closeCondVar(CArchCond cond)
165{
166 DosCloseEventSem(cond->m_events[CArchCondImpl::kSignal]);
167 DosCloseEventSem(cond->m_events[CArchCondImpl::kBroadcast]);
168 closeMutex(cond->m_waitCountMutex);
169 delete cond;
170}
171
172void
173CArchMultithreadOS2::signalCondVar(CArchCond cond)
174{
175 // is anybody waiting?
176 lockMutex(cond->m_waitCountMutex);
177 if (cond->m_waitCount > 0)
178 DosPostEventSem(cond->m_events[CArchCondImpl::kSignal]);
179 unlockMutex(cond->m_waitCountMutex);
180}
181
182void
183CArchMultithreadOS2::broadcastCondVar(CArchCond cond)
184{
185 // is anybody waiting?
186 lockMutex(cond->m_waitCountMutex);
187 if (cond->m_waitCount > 0)
188 DosPostEventSem(cond->m_events[CArchCondImpl::kBroadcast]);
189 unlockMutex(cond->m_waitCountMutex);
190}
191
192bool
193CArchMultithreadOS2::waitCondVar(CArchCond cond,
194 CArchMutex mutex, double timeout)
195{
196 // prepare to wait
197 const ULONG os2Timeout = (timeout < 0.0) ? SEM_INDEFINITE_WAIT :
198 static_cast<ULONG>(1000.0 * timeout);
199
200 // make a list of the condition variable events and the cancel event
201 // for the current thread.
202 SEMRECORD handles[3];
203 handles[0].hsemCur = (HSEM)cond->m_events[CArchCondImpl::kSignal];
204 handles[0].ulUser = 0;
205 handles[1].hsemCur = (HSEM)cond->m_events[CArchCondImpl::kBroadcast];
206 handles[1].ulUser = 1;
207 handles[2].hsemCur = (HSEM)getCancelEventForCurrentThread();
208 handles[2].ulUser = 2;
209 HMUX hmux = NULLHANDLE;
210 APIRET rc = DosCreateMuxWaitSem(NULL, &hmux, 3, &handles[0], DCMW_WAIT_ANY);
211 if (rc != NO_ERROR) {
212 assert(rc == NO_ERROR);
213 return false;
214 }
215
216
217 // update waiter count
218 lockMutex(cond->m_waitCountMutex);
219 ++cond->m_waitCount;
220 unlockMutex(cond->m_waitCountMutex);
221
222 // release mutex. this should be atomic with the wait so that it's
223 // impossible for another thread to signal us between the unlock and
224 // the wait, which would lead to a lost signal on broadcasts.
225 // however, we're using a manual reset event for broadcasts which
226 // stays set until we reset it, so we don't lose the broadcast.
227 unlockMutex(mutex);
228
229 // wait for a signal or broadcast
230 // we're using a a manual reset semaphore, that complicates matters.
231 ULONG iSem = ~0UL;
232 for (;;) {
233 rc = DosWaitMuxWaitSem(hmux, os2Timeout, &iSem);
234 lockMutex(cond->m_waitCountMutex);
235 if (rc != NO_ERROR) {
236 iSem = ~0UL;
237 break;
238 }
239
240 // cancel takes priority
241 if ( iSem != 2
242 && DosWaitEventSem((HEV)handles[2].hsemCur, 0) == NO_ERROR)
243 iSem = 2;
244
245 // see if we got here first.
246 if (iSem != 0)
247 break;
248 ULONG cPosts = 0;
249 rc = DosResetEventSem((HEV)handles[0].hsemCur, &cPosts);
250 if (rc == NO_ERROR && cPosts > 0) {
251 while (--cPosts > 0)
252 DosPostEventSem((HEV)handles[0].hsemCur);
253 break;
254 }
255 }
256 DosCloseMuxWaitSem(hmux);
257
258 // update the waiter count and reset the broadcast event if we're the last waiter
259 if ((iSem == 1 && cond->m_waitCount == 0)) {
260 ULONG ulIgnore;
261 DosResetEventSem(cond->m_events[CArchCondImpl::kBroadcast], &ulIgnore);
262 }
263 unlockMutex(cond->m_waitCountMutex);
264
265 // reacquire the mutex
266 lockMutex(mutex);
267
268 // cancel thread if necessary
269 if (iSem == 2) {
270 ARCH->testCancelThread();
271 }
272
273 // return success or failure
274 return (rc == NO_ERROR && (iSem == 0 || iSem == 1));
275}
276
277CArchMutex
278CArchMultithreadOS2::newMutex()
279{
280 CArchMutexImpl* mutex = new CArchMutexImpl;
281 _fmutex_create2(&mutex->m_mutex, 0, __FUNCTION__);
282 return mutex;
283}
284
285void
286CArchMultithreadOS2::closeMutex(CArchMutex mutex)
287{
288 _fmutex_close(&mutex->m_mutex);
289 delete mutex;
290}
291
292void
293CArchMultithreadOS2::lockMutex(CArchMutex mutex)
294{
295 _fmutex_request(&mutex->m_mutex, 0);
296}
297
298void
299CArchMultithreadOS2::unlockMutex(CArchMutex mutex)
300{
301 _fmutex_release(&mutex->m_mutex);
302}
303
304CArchThread
305CArchMultithreadOS2::newThread(ThreadFunc func, void* data)
306{
307 lockMutex(m_threadMutex);
308
309 // create thread impl for new thread
310 CArchThreadImpl* thread = new CArchThreadImpl;
311 thread->m_func = func;
312 thread->m_userData = data;
313
314 // create thread
315 thread->m_id = _beginthread(threadFunc, NULL, 256*1024, thread);
316
317 // check if thread was started
318 if (thread->m_id < 0) {
319 // failed to start thread so clean up
320 delete thread;
321 thread = NULL;
322 }
323 else {
324 // add thread to list
325 insert(thread);
326
327 // increment ref count to account for the thread itself
328 refThread(thread);
329 }
330
331 // note that the child thread will wait until we release this mutex
332 unlockMutex(m_threadMutex);
333
334 return thread;
335}
336
337CArchThread
338CArchMultithreadOS2::newCurrentThread()
339{
340 lockMutex(m_threadMutex);
341 CArchThreadImpl* thread = find(_gettid());
342 unlockMutex(m_threadMutex);
343 assert(thread != NULL);
344 return thread;
345}
346
347void
348CArchMultithreadOS2::closeThread(CArchThread thread)
349{
350 assert(thread != NULL);
351
352 // decrement ref count and clean up thread if no more references
353 if (--thread->m_refCount == 0) {
354 // remove thread from list
355 lockMutex(m_threadMutex);
356 assert(findNoRefOrCreate(thread->m_id) == thread);
357 erase(thread);
358 unlockMutex(m_threadMutex);
359
360 // done with thread
361 delete thread;
362 }
363}
364
365CArchThread
366CArchMultithreadOS2::copyThread(CArchThread thread)
367{
368 refThread(thread);
369 return thread;
370}
371
372void
373CArchMultithreadOS2::cancelThread(CArchThread thread)
374{
375 assert(thread != NULL);
376
377 // set cancel flag
378 DosPostEventSem(thread->m_cancel);
379}
380
381void
382CArchMultithreadOS2::setPriorityOfThread(CArchThread thread, int n)
383{
384#if 0 /* not implemented on posix, so ignore. */
385 struct CPriorityInfo {
386 public:
387 ULONG m_class;
388 int m_level;
389 };
390 static const CPriorityInfo s_pClass[] = {
391 { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_IDLE },
392 { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST },
393 { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL },
394 { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL },
395 { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL },
396 { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST },
397 { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST },
398 { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL },
399 { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL },
400 { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL },
401 { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST },
402 { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST },
403 { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL },
404 { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL },
405 { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL },
406 { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST },
407 { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_IDLE },
408 { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST },
409 { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL },
410 { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL },
411 { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL },
412 { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST },
413 { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_TIME_CRITICAL}
414 };
415#if defined(_DEBUG)
416 // don't use really high priorities when debugging
417 static const size_t s_pMax = 13;
418#else
419 static const size_t s_pMax = sizeof(s_pClass) / sizeof(s_pClass[0]) - 1;
420#endif
421 static const size_t s_pBase = 8; // index of normal priority
422
423 assert(thread != NULL);
424
425 size_t index;
426 if (n > 0 && s_pBase < (size_t)n) {
427 // lowest priority
428 index = 0;
429 }
430 else {
431 index = (size_t)((int)s_pBase - n);
432 if (index > s_pMax) {
433 // highest priority
434 index = s_pMax;
435 }
436 }
437 SetPriorityClass(GetCurrentProcess(), s_pClass[index].m_class);
438 SetThreadPriority(thread->m_thread, s_pClass[index].m_level);
439#endif /* */
440}
441
442void
443CArchMultithreadOS2::testCancelThread()
444{
445 // find current thread
446 lockMutex(m_threadMutex);
447 CArchThreadImpl* thread = findNoRef(_gettid());
448 unlockMutex(m_threadMutex);
449
450 // test cancel on thread
451 testCancelThreadImpl(thread);
452}
453
454bool
455CArchMultithreadOS2::wait(CArchThread target, double timeout)
456{
457 assert(target != NULL);
458
459 lockMutex(m_threadMutex);
460
461 // find current thread
462 CArchThreadImpl* self = findNoRef(_gettid());
463
464 // ignore wait if trying to wait on ourself
465 if (target == self) {
466 unlockMutex(m_threadMutex);
467 return false;
468 }
469
470 // ref the target so it can't go away while we're watching it
471 refThread(target);
472
473 unlockMutex(m_threadMutex);
474
475 // convert timeout
476 ULONG os2Timeout;
477 if (timeout < 0.0) {
478 os2Timeout = SEM_INDEFINITE_WAIT;
479 }
480 else {
481 os2Timeout = (ULONG)(1000.0 * timeout);
482 }
483
484// fixme!
485 // wait for this thread to be cancelled or woken up or for the
486 // target thread to terminate.
487 SEMRECORD handles[2];
488 handles[0].hsemCur = (HSEM)target->m_exit;
489 handles[0].ulUser = 0;
490 handles[1].hsemCur = (HSEM)self->m_cancel;
491 handles[1].ulUser = 1;
492 HMUX hmux = NULLHANDLE;
493 APIRET rc = DosCreateMuxWaitSem(NULL, &hmux, 2, &handles[0], DCMW_WAIT_ANY);
494 if (rc != NO_ERROR) {
495 assert(rc == NO_ERROR);
496 return false;
497 }
498 ULONG iSem = ~0UL;
499 rc = DosWaitMuxWaitSem(hmux, os2Timeout, &iSem);
500 if (rc != NO_ERROR) {
501 iSem = ~0UL;
502 }
503 DosCloseMuxWaitSem(hmux);
504
505 // cancel takes priority
506 if ( rc == NO_ERROR
507 && iSem != 1
508 && DosWaitEventSem((HEV)handles[1].hsemCur, 0) == NO_ERROR) {
509 iSem = 1;
510 }
511
512 // release target
513 closeThread(target);
514
515 // handle result
516 switch (iSem) {
517 case 0:
518 // target thread terminated
519 return true;
520
521 case 1:
522 // this thread was cancelled. does not return.
523 testCancelThreadImpl(self);
524
525 default:
526 // timeout or error
527 return false;
528 }
529}
530
531bool
532CArchMultithreadOS2::isSameThread(CArchThread thread1, CArchThread thread2)
533{
534 return (thread1 == thread2);
535}
536
537bool
538CArchMultithreadOS2::isExitedThread(CArchThread thread)
539{
540 // poll exit event
541 APIRET rc = DosWaitEventSem(thread->m_exit, 0);
542 return (rc == NO_ERROR);
543}
544
545void*
546CArchMultithreadOS2::getResultOfThread(CArchThread thread)
547{
548 lockMutex(m_threadMutex);
549 void* result = thread->m_result;
550 unlockMutex(m_threadMutex);
551 return result;
552}
553
554IArchMultithread::ThreadID
555CArchMultithreadOS2::getIDOfThread(CArchThread thread)
556{
557 return static_cast<ThreadID>(thread->m_id);
558}
559
560void
561CArchMultithreadOS2::setSignalHandler(
562 ESignal signal, SignalFunc func, void* userData)
563{
564 lockMutex(m_threadMutex);
565 m_signalFunc[signal] = func;
566 m_signalUserData[signal] = userData;
567 unlockMutex(m_threadMutex);
568}
569
570void
571CArchMultithreadOS2::raiseSignal(ESignal signal)
572{
573 lockMutex(m_threadMutex);
574 if (m_signalFunc[signal] != NULL) {
575 m_signalFunc[signal](signal, m_signalUserData[signal]);
576 ARCH->unblockPollSocket(m_mainThread);
577 }
578 else if (signal == kINTERRUPT || signal == kTERMINATE) {
579 ARCH->cancelThread(m_mainThread);
580 }
581 unlockMutex(m_threadMutex);
582}
583
584CArchThreadImpl*
585CArchMultithreadOS2::find(int id)
586{
587 CArchThreadImpl* impl = findNoRef(id);
588 if (impl != NULL) {
589 refThread(impl);
590 }
591 return impl;
592}
593
594CArchThreadImpl*
595CArchMultithreadOS2::findNoRef(int id)
596{
597 CArchThreadImpl* impl = findNoRefOrCreate(id);
598 if (impl == NULL) {
599 // create thread for calling thread which isn't in our list and
600 // add it to the list. this won't normally happen but it can if
601 // the system calls us under a new thread, like it does when we
602 // run as a service.
603 impl = new CArchThreadImpl;
604 impl->m_id = _gettid();
605 insert(impl);
606 }
607 return impl;
608}
609
610CArchThreadImpl*
611CArchMultithreadOS2::findNoRefOrCreate(int id)
612{
613 // linear search
614 for (CThreadList::const_iterator index = m_threadList.begin();
615 index != m_threadList.end(); ++index) {
616 if ((*index)->m_id == id) {
617 return *index;
618 }
619 }
620 return NULL;
621}
622
623void
624CArchMultithreadOS2::insert(CArchThreadImpl* thread)
625{
626 assert(thread != NULL);
627
628 // thread shouldn't already be on the list
629 assert(findNoRefOrCreate(thread->m_id) == NULL);
630
631 // append to list
632 m_threadList.push_back(thread);
633}
634
635void
636CArchMultithreadOS2::erase(CArchThreadImpl* thread)
637{
638 for (CThreadList::iterator index = m_threadList.begin();
639 index != m_threadList.end(); ++index) {
640 if (*index == thread) {
641 m_threadList.erase(index);
642 break;
643 }
644 }
645}
646
647void
648CArchMultithreadOS2::refThread(CArchThreadImpl* thread)
649{
650 assert(thread != NULL);
651 assert(findNoRefOrCreate(thread->m_id) != NULL);
652 ++thread->m_refCount;
653}
654
655void
656CArchMultithreadOS2::testCancelThreadImpl(CArchThreadImpl* thread)
657{
658 assert(thread != NULL);
659
660 // poll cancel event. return if not set.
661 APIRET rc = DosWaitEventSem(thread->m_cancel, 0);
662 if (rc != NO_ERROR) {
663 return;
664 }
665
666 // update cancel state
667 lockMutex(m_threadMutex);
668 bool cancel = !thread->m_cancelling;
669 thread->m_cancelling = true;
670 ULONG ulIgnored;
671 DosResetEventSem(thread->m_cancel, &ulIgnored);
672 unlockMutex(m_threadMutex);
673
674 // unwind thread's stack if cancelling
675 if (cancel) {
676 throw XThreadCancel();
677 }
678}
679
680void
681CArchMultithreadOS2::threadFunc(void* vrep)
682{
683 // get the thread
684 CArchThreadImpl* thread = reinterpret_cast<CArchThreadImpl*>(vrep);
685
686 // run thread
687 s_instance->doThreadFunc(thread);
688
689 // terminate the thread
690 _endthread();
691}
692
693void
694CArchMultithreadOS2::doThreadFunc(CArchThread thread)
695{
696 // wait for parent to initialize this object
697 lockMutex(m_threadMutex);
698 unlockMutex(m_threadMutex);
699
700 void* result = NULL;
701 try {
702 // go
703 result = (*thread->m_func)(thread->m_userData);
704 }
705
706 catch (XThreadCancel&) {
707 // client called cancel()
708 }
709 catch (...) {
710 // note -- don't catch (...) to avoid masking bugs
711 DosPostEventSem(thread->m_exit);
712 closeThread(thread);
713 throw;
714 }
715
716 // thread has exited
717 lockMutex(m_threadMutex);
718 thread->m_result = result;
719 unlockMutex(m_threadMutex);
720 DosPostEventSem(thread->m_exit);
721
722 // done with thread
723 closeThread(thread);
724}
Note: See TracBrowser for help on using the repository browser.