222603aba9b9355aaadea558b095e96c8c583d61
[shibboleth/cpp-xmltooling.git] / xmltooling / util / Win32Threads.cpp
1 /*
2  *  Copyright 2001-2010 Internet2
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * Win32Threads.cpp
19  *
20  * Thread and locking wrappers for Win32 platforms.
21  */
22
23 #include "internal.h"
24 #include "logging.h"
25 #include "util/Threads.h"
26
27 #include <algorithm>
28
29 #ifndef WIN32
30 # error "This implementation is for WIN32 platforms."
31 #endif
32
33 using namespace xmltooling::logging;
34 using namespace xmltooling;
35 using namespace std;
36
37 // base error code for a routine to return on failure
38 #define THREAD_ERROR_TIMEOUT    (1)
39 #define THREAD_ERROR_WAKE_OTHER (2)
40 #define THREAD_ERROR                (3)
41
42 // windows returns non zero for success pthreads returns zero
43 static int XMLTOOL_DLLLOCAL map_windows_error_status_to_pthreads(int rc=0) {
44     if(rc!=0)  // success?
45         return 0;
46     Category::getInstance(XMLTOOLING_LOGCAT".Threads").error("error from thread operation (%d)", GetLastError());
47     return THREAD_ERROR;
48 }
49
50 namespace xmltooling {
51
52     // two levels of classes are needed here
53     // in case InitializeCriticalSection
54     // throws an exception we can keep from
55     // calling the critical_section destructor
56     // on unitilized data, or it could be done with a flag
57     struct XMLTOOL_DLLLOCAL critical_section_data {
58         CRITICAL_SECTION cs;
59         critical_section_data(){
60             InitializeCriticalSection(&cs);
61         }
62     };
63
64     class XMLTOOL_DLLLOCAL critical_section {
65     private:
66         critical_section_data   cse;
67     public:
68         critical_section(){}
69         ~critical_section(){
70             DeleteCriticalSection (&cse.cs);
71         }
72         void enter(void) {
73             EnterCriticalSection(&cse.cs);
74         }
75         void leave(void) {
76             LeaveCriticalSection(&cse.cs);
77         }
78     };
79
80     // hold a critical section over the lifetime of this object
81     // used to make a stack variable that unlocks automaticly
82     // on return/throw
83     class XMLTOOL_DLLLOCAL with_crit_section {
84     private:
85         critical_section& cs;
86     public:
87         with_crit_section(critical_section& acs):cs(acs){
88             cs.enter();
89         }
90         ~with_crit_section(){
91             cs.leave();
92         }
93     };
94
95     class XMLTOOL_DLLLOCAL ThreadImpl : public Thread {
96     private:
97         HANDLE thread_id;
98     public:
99         ThreadImpl(void* (*start_routine)(void*), void* arg, size_t stacksize) : thread_id(0) {
100             thread_id=CreateThread(
101                 0, // security attributes
102                 stacksize, // 0 just means the default size anyway
103                 (LPTHREAD_START_ROUTINE ) start_routine,
104                 arg,
105                 0, // flags, default is don't create suspended which is what we want
106                 0);
107             if (thread_id==0) {
108                 map_windows_error_status_to_pthreads();
109                 throw ThreadingException("Thread creation failed.");
110             }
111         }
112
113         ~ThreadImpl() {
114             (void)detach();
115         }
116
117         int detach() {
118             if (thread_id==0)
119                 return THREAD_ERROR;
120             int rc=map_windows_error_status_to_pthreads(CloseHandle(thread_id));
121             thread_id=0;
122             return rc;
123         }
124
125         int join(void** thread_return) {
126             if (thread_id==0)
127                 return THREAD_ERROR;
128             if (thread_return!=0)
129                 *thread_return=0;
130             int rc=WaitForSingleObject(thread_id,INFINITE);
131             switch(rc) {
132                 case WAIT_OBJECT_0:
133                     if (thread_return)
134                         map_windows_error_status_to_pthreads(GetExitCodeThread(thread_id,(unsigned long*)thread_return));
135                 default:
136                     return THREAD_ERROR;
137             }
138             return 0;
139         }
140
141         int kill(int signo) {
142             if (thread_id==0)
143                 return THREAD_ERROR;
144             return map_windows_error_status_to_pthreads(TerminateThread(thread_id,signo));
145         }
146     };
147
148     class XMLTOOL_DLLLOCAL MutexImpl : public Mutex {
149     private:
150         CRITICAL_SECTION mhandle;
151     public:
152         MutexImpl() {
153             InitializeCriticalSection(&mhandle);
154         }
155
156         ~MutexImpl() {
157             DeleteCriticalSection(&mhandle);
158         }
159
160         int lock() {
161             EnterCriticalSection(&mhandle);
162             return 0;
163         }
164
165         int unlock() {
166             LeaveCriticalSection(&mhandle);
167             return 0;
168         }
169     };
170
171     class XMLTOOL_DLLLOCAL CondWaitImpl : public CondWait {
172     private:
173         HANDLE cond;
174
175     public:
176         CondWaitImpl() : cond(CreateEvent(0,false,false,0)) {
177             if(cond==0) {
178                 map_windows_error_status_to_pthreads();
179                 throw ThreadingException("Event creation failed.");
180             }
181         };
182
183         ~CondWaitImpl() {
184             if((cond!=0) && (!CloseHandle(cond)))
185                 map_windows_error_status_to_pthreads();
186         }
187
188         int wait(Mutex* mutex) {
189             return timedwait(mutex,INFINITE);
190         }
191
192         int signal() {
193             if(!SetEvent(cond))
194                 return map_windows_error_status_to_pthreads();
195             return 0;
196         }
197
198         int broadcast() {
199             throw ThreadingException("Broadcast not implemented on Win32 platforms.");
200         }
201
202         // wait for myself to signal and this mutex or the timeout
203         int timedwait(Mutex* mutex, int delay_seconds) {
204             int rc=mutex->unlock();
205             if(rc!=0)
206                 return rc;
207
208             int delay_ms=delay_seconds;
209             if(delay_seconds!=INFINITE)
210                 delay_ms*=1000;
211             rc=WaitForSingleObject(cond,delay_ms);
212             int rc2=mutex->lock();
213             if(rc2!=0)
214                 return rc2;
215             switch(rc) {
216                 case WAIT_ABANDONED:
217                 case WAIT_OBJECT_0:
218                 case WAIT_TIMEOUT:
219                     return 0;
220                 default:
221                     return map_windows_error_status_to_pthreads();
222             }
223             return 0;
224         }
225     };
226
227     class XMLTOOL_DLLLOCAL RWLockImpl : public RWLock {
228     private:
229         // used to protect read or write to the data below
230         critical_section cs;
231         // event handle threads wait on when the lock they want is busy
232         // normally set to signaled all the time, if some thread can't get what
233         // they want they reset it and sleep.  on releasing a lock set it to
234         // signaled if someone may have wanted what you just released
235         HANDLE wake_waiters;
236         // number of threads holding a read lock
237         int num_readers;
238         // true iff there a writer has our lock
239         bool have_writer;
240
241     public:
242         RWLockImpl() : wake_waiters(0), num_readers(0), have_writer(true) {
243             with_crit_section acs(cs);
244             wake_waiters=CreateEvent(0,true,true,0);
245             have_writer=false;
246             if (wake_waiters==0) {
247                 map_windows_error_status_to_pthreads();
248                 throw ThreadingException("Event creation for shared lock failed.");
249             }
250         }
251
252         ~RWLockImpl() {
253             with_crit_section acs(cs);
254             if ((wake_waiters!=0) && (!CloseHandle(wake_waiters)))
255                 map_windows_error_status_to_pthreads();
256         }
257
258         int rdlock() {
259             while(1) {
260                 // wait for the lock maybe being availible
261                 // we will find out for sure inside the critical section
262                 if (WaitForSingleObject(wake_waiters,INFINITE)!=WAIT_OBJECT_0)
263                     return map_windows_error_status_to_pthreads();
264
265                 with_crit_section alock(cs);
266                 // invariant not locked for reading and writing
267                 if ((num_readers!=0) && (have_writer))
268                     return THREAD_ERROR;
269                 // if no writer we can join any existing readers
270                 if (!have_writer) {
271                     num_readers++;
272                     return 0;
273                 }
274
275                 // have a writer, mark the synchronization object
276                 // so everyone waits, when the writer unlocks it will wake us
277                 if (!ResetEvent(wake_waiters))
278                     return map_windows_error_status_to_pthreads();
279             }
280             return THREAD_ERROR;
281         }
282
283         int wrlock() {
284             while(1) {
285                 // wait for the lock maybe being availible
286                 // we will find out for sure inside the critical section
287                 if (WaitForSingleObject(wake_waiters,INFINITE)!=WAIT_OBJECT_0)
288                     return map_windows_error_status_to_pthreads();
289
290                 with_crit_section bla(cs);
291                 // invariant not locked for reading and writing
292                 if ((num_readers!=0) && (have_writer))
293                        return THREAD_ERROR;
294
295                 // if no writer and no readers we can become the writer
296                 if ((num_readers==0) && (!have_writer)) {
297                     have_writer=true;
298                     return 0;
299                 }
300
301                 // lock is busy, the unlocker will wake us
302                 if (!ResetEvent(wake_waiters))
303                     return map_windows_error_status_to_pthreads();
304             }
305             return THREAD_ERROR;
306         }
307
308         int unlock() {
309             with_crit_section mumble(cs);
310             // invariant not locked for reading and writing
311             if ((num_readers!=0) && (have_writer))
312                 return THREAD_ERROR;
313
314             // error if nothing locked
315             if ((num_readers==0) && (!have_writer))
316                 return THREAD_ERROR;
317
318             // if there was a writer it has to be us so unlock write lock
319             have_writer=false;
320
321             // if there where any reades there is one less now
322             if(num_readers>0)
323                 num_readers--;
324
325             // if no readers left wake up any readers/writers waiting
326             // to have a go at it
327             if (num_readers==0)
328                 if (!SetEvent(wake_waiters))
329                     return map_windows_error_status_to_pthreads();
330             return 0;
331         }
332     };
333
334     typedef void (*destroy_hook_type)(void*);
335
336     class XMLTOOL_DLLLOCAL ThreadKeyImpl : public ThreadKey {
337     private:
338         destroy_hook_type destroy_hook;
339         DWORD key;
340         static critical_section cs;
341         static set<ThreadKeyImpl*> m_keys;
342         friend class ThreadKey;
343     public:
344         ThreadKeyImpl(void (*destroy_fcn)(void*)) : destroy_hook(destroy_fcn) {
345             key=TlsAlloc();
346             if (destroy_fcn) {
347                 with_crit_section wcs(cs);
348                 m_keys.insert(this);
349             }
350         };
351
352         virtual ~ThreadKeyImpl() {
353             if (destroy_hook) {
354                 destroy_hook(TlsGetValue(key));
355                 with_crit_section wcs(cs);
356                 m_keys.erase(this);
357             }
358             TlsFree(key);
359         }
360
361         int setData(void* data) {
362             TlsSetValue(key, data);
363             return 0;
364         }
365
366         void* getData() const {
367             return TlsGetValue(key);
368         }
369
370         void onDetach() const {
371             if (destroy_hook) {
372                 destroy_hook(TlsGetValue(key));
373                 TlsSetValue(key, nullptr);
374             }
375         }
376     };
377
378 };
379
380 //
381 // public "static" creation functions
382 //
383
384 Thread* Thread::create(void* (*start_routine)(void*), void* arg, size_t stacksize)
385 {
386     return new ThreadImpl(start_routine, arg, stacksize);
387 }
388
389 void Thread::exit(void* return_val)
390 {
391     ExitThread((DWORD)return_val);
392 }
393
394 void Thread::sleep(int seconds)
395 {
396     Sleep(seconds * 1000);
397 }
398
399 Mutex * Mutex::create()
400 {
401     return new MutexImpl();
402 }
403
404 CondWait * CondWait::create()
405 {
406     return new CondWaitImpl();
407 }
408
409 RWLock * RWLock::create()
410 {
411     return new RWLockImpl();
412 }
413
414 critical_section ThreadKeyImpl::cs;
415 set<ThreadKeyImpl*> ThreadKeyImpl::m_keys;
416
417 ThreadKey* ThreadKey::create (void (*destroy_fcn)(void*))
418 {
419     return new ThreadKeyImpl(destroy_fcn);
420 }
421
422 void ThreadKey::onDetach()
423 {
424     with_crit_section wcs(ThreadKeyImpl::cs);
425     for_each(ThreadKeyImpl::m_keys.begin(), ThreadKeyImpl::m_keys.end(), mem_fun<void,ThreadKeyImpl>(&ThreadKeyImpl::onDetach));
426 }