bug fixes for ethread's periodic mode.
[feisty_meow.git] / kona / src / org / feistymeow / process / ethread.java
1 package org.feistymeow.process;
2
3 import org.apache.commons.logging.Log;
4 import org.apache.commons.logging.LogFactory;
5
6 /**
7  * A simple java thread that hearkens back to the HOOPLE C++ ethread in features.
8  * 
9  * @author Chris Koeritz
10  * @copyright Copyright (c) 2010-$now By Feisty Meow Concerns Ltd.
11  * @license This file is free software; you can modify and redistribute it under the terms of the Apache License v2.0:
12  *          http://www.apache.org/licenses/LICENSE-2.0
13  */
14 public abstract class ethread implements Runnable
15 {
16         private static Log c_logger = LogFactory.getLog(ethread.class);
17
18         // the actual java thread object.
19         private volatile Thread c_RealThread = null;
20         // provides synchronization for the thread.
21         private volatile Object c_lock = new Object();
22         // this is the timing period, for a timed thread. if zero, then this is a single shot thread.
23         private long c_period = 0;
24         // records whether the thread should shut down or not.
25         private boolean c_stopThread = false;
26         // snooze between checks on the stop timer.
27         final long SNOOZE_PERIOD = 20;
28
29         /**
30          * creates a new single-shot ethread without starting it. this type of thread will run just once.
31          */
32         public ethread()
33         {
34         }
35
36         /**
37          * creates a new periodic ethread without starting it. this type of thread runs every "period" milliseconds until stopped or until the
38          * performActivity method returns false.
39          */
40         public ethread(long period)
41         {
42                 c_period = period;
43         }
44
45         /**
46          * this is the main function that derived classes must implement. it does the actual work that the thread is intended to perform. note
47          * that the derived version must not do anything to cause the thread to be ripped out while performActivity is still being invoked. the
48          * return value should be true if the thread can continue executing. this is meaningless for single shot threads executed via runOnce, but
49          * matters for the periodic threads started with runPeriodic.
50          */
51         abstract public boolean performActivity();
52
53         /**
54          * Begins execution of the thread.
55          */
56         public void start()
57         {
58                 synchronized (c_lock) {
59                         if (null == this.c_RealThread) {
60                                 this.c_RealThread = new Thread(this);
61                                 c_logger.debug("starting thread " + c_RealThread.getId());
62                                 this.c_RealThread.start();
63                         }
64                 }
65         }
66
67         /**
68          * Stops execution of the thread, or at least attempts to.
69          */
70         public void stop()
71         {
72                 cancel();
73                 while (true) {
74                         if (threadAlive()) {
75                                 try {
76                                         Thread.sleep(40);
77                                 } catch (InterruptedException e) {
78                                         // ignoring this since we'll keep snoozing as needed.
79                                 }
80                         } else {
81                                 break;
82                         }
83                 }
84         }
85
86         /**
87          * Signals the thread to stop executing, but does not wait for it.
88          */
89         void cancel()
90         {
91                 synchronized (c_lock) {
92                         c_stopThread = true;
93                         Thread goAway = c_RealThread;
94                         c_RealThread = null;
95                         if (null != goAway) {
96                                 goAway.interrupt();
97                         }
98                 }
99         }
100
101         /**
102          * Returns true if the thread object is still alive. this does not necessarily mean it is currently active.
103          */
104         public boolean threadAlive()
105         {
106                 synchronized (c_lock) {
107                         return this.c_RealThread != null;
108                 }
109         }
110
111         /**
112          * returns true if the thread has been told to stop running.
113          */
114         public boolean shouldStop()
115         {
116                 synchronized (c_lock) {
117                         return c_stopThread;
118                 }
119         }
120
121         /**
122          * this is the override from Runnable that allows us to call our own performActivity method. implementors should not override this; they
123          * should override performActivity instead.
124          */
125         @Override
126         public void run()
127         {
128                 if (!threadAlive()) {
129                         return; // stopped before it ever started. how can this be? we just got invoked.
130                 }
131                 try {
132                         while (true) {
133                                 boolean keepGoing = performActivity();
134                                 if (!keepGoing) {
135                                         c_logger.debug("thread returned false, signifying it wants to exit.  now dropping it.");
136                                         break;
137                                 }
138                                 if (c_period == 0) {
139                                         // not a periodic thread, so we're done now.
140                                         break;
141                                 }
142                                 long nextRun = System.currentTimeMillis() + c_period;
143                                 while (System.currentTimeMillis() < nextRun) {
144                                         if (shouldStop()) {
145                                                 break;
146                                         }
147                                         try {
148                                                 Thread.sleep(SNOOZE_PERIOD);
149                                         } catch (InterruptedException e) {
150                                                 // well, we'll hit it again soon.
151                                         }
152                                 }
153                         }
154                 } catch (Throwable t) {
155                         c_logger.info("exception thrown from performActivity: " + t.getLocalizedMessage(), t);
156                 }
157                 // reset the thread held since we're leaving right now.
158                 c_stopThread = true;
159                 c_RealThread = null;
160         }
161 }