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