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