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