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