checking in the recent efforts at optimizing clam
[feisty_meow.git] / nucleus / library / processes / ethread.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : ethread                                                           *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 1998-$now By Author.  This program is free software; you can  *
8 * redistribute it and/or modify it under the terms of the GNU General Public  *
9 * License as published by the Free Software Foundation; either version 2 of   *
10 * the License or (at your option) any later version.  This is online at:      *
11 *     http://www.fsf.org/copyleft/gpl.html                                    *
12 * Please send any updates to: fred@gruntose.com                               *
13 \*****************************************************************************/
14
15 #include "ethread.h"
16
17 #include <application/windoze_helper.h>
18 #include <basis/astring.h>
19 #include <basis/functions.h>
20 #include <basis/guards.h>
21 #include <loggers/critical_events.h>
22 #include <loggers/program_wide_logger.h>
23 #include <structures/static_memory_gremlin.h>
24 #include <timely/time_control.h>
25
26 //#ifdef _MSC_VER
27 //  #include <process.h>
28 //#elif defined(__UNIX__) || defined(__GNU_WINDOWS__)
29   #include <pthread.h>
30 //#else
31   //#error unknown OS for thread support.
32 //#endif
33
34 using namespace basis;
35 using namespace loggers;
36 using namespace structures;
37 using namespace timely;
38
39 //#define COUNT_THREADS
40   // if this is enabled, then threads will be counted when they are created
41   // or destroyed.
42
43 #undef LOG
44 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
45
46 namespace processes {
47
48 const int MAXIMUM_CREATE_ATTEMPTS = 20;
49   // the number of retries we allow to try creating a thread, if the first
50   // attempt fails.
51
52 const int MINIMUM_SLEEP_PERIOD = 10;
53   // this is the smallest time we'll sleep for if we're slack.
54
55 const int MAXIMUM_SLEEP_PERIOD = 200;
56   // the number of milliseconds we use for breaking up longer sleep periods.
57
58 const int SNOOZE_FOR_RETRY = 100;
59   // how long to sleep when a thread creation fails.
60
61 #ifdef COUNT_THREADS
62   // singleton thread counter code.
63   class thread_counter : public virtual root_object {
64   public:
65     thread_counter() : _count(0) {}
66     DEFINE_CLASS_NAME("thread_counter");
67     void increment() { 
68       auto_synchronizer l(_lock);
69       _count++;
70     }
71     void decrement() {
72       auto_synchronizer l(_lock);
73       _count--;
74     }
75   private:
76     int _count;
77     mutex _lock;
78   };
79
80   SAFE_STATIC(thread_counter, _current_threads, )
81
82 //hmmm: this seems to not be used anywhere yet.  it needs to be accessible
83 //      externally if it's going to serve any useful purpose.
84
85 #endif
86
87 ethread::ethread()
88 : _thread_ready(false),
89   _thread_active(false),
90   _stop_thread(false),
91   _data(NULL_POINTER),
92 //#ifdef _MSC_VER
93 //  _handle(0),
94 //#else
95   _handle(new pthread_t),
96 //#endif
97   _sleep_time(0),
98   _periodic(false),
99   _next_activation(new time_stamp),
100   _how(TIGHT_INTERVAL)  // unused.
101 {
102   FUNCDEF("constructor [one-shot]");
103 }
104
105 ethread::ethread(int sleep_timer, timed_thread_types how)
106 : _thread_ready(false),
107   _thread_active(false),
108   _stop_thread(false),
109   _data(NULL_POINTER),
110 //#ifdef _MSC_VER
111 //  _handle(0),
112 //#else
113   _handle(new pthread_t),
114 //#endif
115   _sleep_time(sleep_timer),
116   _periodic(true),
117   _next_activation(new time_stamp),
118   _how(how)
119 {
120   FUNCDEF("constructor [periodic]");
121   if (sleep_timer < MINIMUM_SLEEP_PERIOD) {
122     _sleep_time = MINIMUM_SLEEP_PERIOD;
123   }
124 }
125
126 ethread::~ethread()
127 {
128   stop();
129   WHACK(_next_activation);
130 //#ifndef _MSC_VER
131   WHACK(_handle);
132 //#endif
133 }
134
135 ///void ethread::pre_thread() {}
136
137 ///void ethread::post_thread() {}
138
139 // the reschedule operation assumes that assignment to a time stamp
140 // object (based on a real numbers) happens indivisibly.
141 void ethread::reschedule(int delay)
142 {
143   *_next_activation = time_stamp(delay);  // start after the delay.
144 }
145
146 bool ethread::start(void *thread_data)
147 {
148   FUNCDEF("start");
149   if (!thread_finished()) return false;  // already running.
150   _data = thread_data;  // store the thread's data pointer.
151   _stop_thread = false;  // don't stop now.
152   _thread_ready = true;  // we're starting it now.
153   _next_activation->reset();  // make "now" the next time to activate.
154   bool success = false;
155   int error = 0;
156   int attempts = 0;
157   while (attempts++ < MAXIMUM_CREATE_ATTEMPTS) {
158 //#ifndef _MSC_VER
159     pthread_attr_t attribs;  // special flags for creation of thread.
160     int aret = pthread_attr_init(&attribs);
161     if (aret) LOG("failed to init attribs.");
162     aret = pthread_attr_setdetachstate(&attribs, PTHREAD_CREATE_DETACHED);
163     if (aret) LOG("failed to set detach state.");
164     int ret = -1;
165     if (_periodic)
166       ret = pthread_create(_handle, &attribs, periodic_thread_driver,
167           (void *)this);
168     else
169       ret = pthread_create(_handle, &attribs, one_shot_thread_driver,
170           (void *)this);
171     if (!ret) success = true;
172     else error = ret;
173 /*
174 #else
175     if (_periodic)
176       _handle = _beginthread(periodic_thread_driver, 0, (void *)this);
177     else
178       _handle = _beginthread(one_shot_thread_driver, 0, (void *)this);
179     if (_handle != -1) success = true;
180     else error = critical_events::system_error();
181 #endif
182 */
183     if (success) break;  // got it created.
184     LOG("failed to create thread; trying again...");
185     time_control::sleep_ms(SNOOZE_FOR_RETRY);
186   }
187   if (!success) {
188     // couldn't start it, so reset our state.
189     LOG(astring("failed to create thread, error is ")
190         + critical_events::system_error_text(error));
191     exempt_stop();
192     return false;
193   }
194   return true;
195 }
196
197 void ethread::stop()
198 {
199   cancel();  // tell thread to leave.
200   if (!thread_started()) return;  // not running.
201   while (!thread_finished()) {
202 /*
203 #ifdef _MSC_VER
204     int result = 0;
205     if (!GetExitCodeThread((HANDLE)_handle, (LPDWORD)&result)
206         || (result != STILL_ACTIVE)) {
207       exempt_stop();
208       break;
209     }
210 #endif
211 */
212     time_control::sleep_ms(10);  // wait for thread to leave.
213   }
214 }
215
216 void ethread::exempt_stop()
217 {
218   _thread_active = false;
219   _thread_ready = false;
220 //#ifdef _MSC_VER
221 //  _handle = 0;
222 //#endif
223 }
224
225 //#if defined(__UNIX__) || defined(__GNU_WINDOWS__)
226 void *ethread::one_shot_thread_driver(void *hidden_pointer)
227 //#elif defined(_MSC_VER)
228 //void ethread::one_shot_thread_driver(void *hidden_pointer)
229 //#else
230 //#error unknown thread signature.
231 //#endif
232 {
233   FUNCDEF("one_shot_thread_driver");
234   ethread *manager = (ethread *)hidden_pointer;
235 //#ifndef _MSC_VER
236   if (!manager) return NULL_POINTER;
237 //#else
238   //if (!manager) return;
239 //#endif
240 #ifdef COUNT_THREADS
241   _current_threads().increment();
242 #endif
243 ///  manager->pre_thread();
244   manager->_thread_active = true;
245   manager->perform_activity(manager->_data);
246 ///  manager->post_thread();
247   manager->exempt_stop();
248 #ifdef COUNT_THREADS
249   _current_threads().decrement();
250 #endif
251 //#ifndef _MSC_VER
252   pthread_exit(NULL_POINTER);
253   return NULL_POINTER;
254 //#else
255   //_endthread();
256 //#endif
257 }
258
259 //#if defined(__UNIX__) || defined(__GNU_WINDOWS__)
260 void *ethread::periodic_thread_driver(void *hidden_pointer)
261 //#elif defined(_MSC_VER)
262 //void ethread::periodic_thread_driver(void *hidden_pointer)
263 //#else
264 //#error unknown thread signature.
265 //#endif
266 {
267   FUNCDEF("periodic_thread_driver");
268   ethread *manager = (ethread *)hidden_pointer;
269 //#if defined(__UNIX__) || defined(__GNU_WINDOWS__)
270   if (!manager) return NULL_POINTER;
271 //#elif defined(_MSC_VER)
272 //  if (!manager) return;
273 //#endif
274 #ifdef COUNT_THREADS
275   _current_threads().increment();
276 #endif
277 ///  manager->pre_thread();
278
279   while (!manager->_stop_thread) {
280     // for TIGHT_INTERVAL, we reset the next active time here.  this is safe
281     // relative to the reschedule() method, since we're about to do
282     // perform_activity() right now anyway.  this brings about a pretty hard
283     // interval; if perform_activity() takes N milliseconds, then there will
284     // only be sleep_time - N (min zero) ms before the next invocation.
285     if (manager->_how == TIGHT_INTERVAL)
286       *manager->_next_activation = time_stamp(manager->_sleep_time);
287
288     manager->_thread_active = true;
289     manager->perform_activity(manager->_data);
290     manager->_thread_active = false;
291
292     // SLACK_INTERVAL means between activations.  we reset the next activation
293     // here to ensure we wait the period specified for sleep time, including
294     // whatever time was taken for the activity itself.
295     if (manager->_how == SLACK_INTERVAL)
296       *manager->_next_activation = time_stamp(manager->_sleep_time);
297
298     // we do the sleep timing in chunks so that there's not such a huge wait
299     // when the user stops the thread before the sleep interval elapses.
300     // snooze until time for the next activation.
301     while (!manager->_stop_thread) {
302       int time_diff = int(manager->_next_activation->value()
303           - time_stamp().value());
304       if (time_diff < 0) time_diff = 0;  // time keeps on slipping.
305       // make sure we take our time if we're slack intervalled.
306       if (manager->_how == SLACK_INTERVAL) {
307         if (time_diff < MINIMUM_SLEEP_PERIOD)
308           time_diff = MINIMUM_SLEEP_PERIOD;
309       }
310       if (time_diff > MAXIMUM_SLEEP_PERIOD)
311         time_diff = MAXIMUM_SLEEP_PERIOD;
312       if (!manager->_stop_thread)
313         time_control::sleep_ms(time_diff);
314       if (time_stamp() >= *manager->_next_activation)
315         break;
316     }
317   }
318 ///  manager->post_thread();
319   manager->exempt_stop();
320 #ifdef COUNT_THREADS
321   _current_threads().decrement();
322 #endif
323 //#ifndef _MSC_VER
324   pthread_exit(NULL_POINTER);
325   return NULL_POINTER;
326 //#else
327   //_endthread();
328 //#endif
329 }
330
331 } //namespace.
332