blob: 7aa3405d9e461d92eca6497e3e031f19d2f51488 [file] [log] [blame]
Alexandre Lision51140e12013-12-02 10:54:09 -05001/*
2 Copyright (C) 2006, 2005, 2004 Erik Eliasson, Johan Bilien, Werner Dittmann
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17*/
18
19
20#ifndef TIMEOUTPROVIDER_H
21#define TIMEOUTPROVIDER_H
22
23/**
24 * Provides a way to request timeouts after a number of milli seconds.
25 *
26 * A command is associated to each timeout.
27 *
28 * Modified to use the common c++ library functions and the STL
29 * list by Werner Dittmann.
30 *
31 * @author Erik Eliasson, eliasson@it.kth.se, 2003
32 * @author Werner Dittmann <Werner.Dittmann@t-online.de>
33 */
34
35#include <list>
36#include <sys/time.h>
37
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -050038#include <commoncpp/config.h>
39#include <commoncpp/thread.h>
Alexandre Lision51140e12013-12-02 10:54:09 -050040
41/**
42 * Represents a request of a "timeout" (delivery of a command to a
43 * "timeout receiver" after at least a specified time period).
44 *
45 * Slightly modified to use gettimeofday directly.
46 *
47 * NOTE: This class is only used internaly.
48 * @author Erik Eliasson
49 * @author Werner Dittmann
50 */
51template <class TOCommand, class TOSubscriber>
52class TPRequest
53{
54
55public:
56
57 TPRequest( TOSubscriber tsi, int timeoutMs, const TOCommand &command):
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -050058 subscriber(tsi)
Alexandre Lision51140e12013-12-02 10:54:09 -050059 {
60 struct timeval tv;
61 gettimeofday(&tv, NULL );
62
63 when_ms = ((uint64)tv.tv_sec) * (uint64)1000 + ((uint64)tv.tv_usec) / (uint64)1000;
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -050064 when_ms += timeoutMs;
65 this->command = command;
Alexandre Lision51140e12013-12-02 10:54:09 -050066 }
67
68 /**
69 * @param t ms since Epoch
70 */
71 bool happensBefore(uint64 t)
72 {
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -050073 if (when_ms < t) {
74 return true;
75 }
76 if (when_ms > t) {
77 return false;
78 }
79 return false; // if equal it does not "happens_before"
Alexandre Lision51140e12013-12-02 10:54:09 -050080
81 }
82
83 bool happensBefore(const TPRequest *req){
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -050084 return happensBefore(req->when_ms);
Alexandre Lision51140e12013-12-02 10:54:09 -050085 }
86
87 /**
88 * Number of milli seconds until timeout from when this method is
89 * called
90 */
91 int getMsToTimeout ()
92 {
93 struct timeval tv;
94 gettimeofday(&tv, NULL );
95
96 uint64 now = ((uint64)tv.tv_sec) * (uint64)1000 + ((uint64)tv.tv_usec) / (uint64)1000;
97
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -050098 if (happensBefore(now)) {
99 return 0;
100 }
101 else {
102 return (int)(when_ms - now);
103 }
Alexandre Lision51140e12013-12-02 10:54:09 -0500104 }
105
106 TOCommand getCommand()
107 {
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500108 return command;
Alexandre Lision51140e12013-12-02 10:54:09 -0500109 }
110
111 TOSubscriber getSubscriber()
112 {
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500113 return subscriber;
Alexandre Lision51140e12013-12-02 10:54:09 -0500114 }
115
116 /**
117 * Two timeout requests are considered equeal if they have
118 * the same subscriber AND command AND time when they
119 * occur. If one of the time is zero then this is a
120 * wildcard and matches always.
121 */
122 bool operator==(const TPRequest<TOCommand, TOSubscriber> &req)
123 {
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500124 if (req.subscriber == subscriber &&
125 req.command == command &&
126 req.when_ms == when_ms) {
Alexandre Lision51140e12013-12-02 10:54:09 -0500127 return true;
128 }
129 return false;
130 }
131
132private:
133 TOSubscriber subscriber;
134 uint64 when_ms; // Time since Epoch in ms when the timeout
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500135 // will happen
Alexandre Lision51140e12013-12-02 10:54:09 -0500136
137 TOCommand command; // Command that will be delivered to the
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500138 // receiver (subscriber) of the timeout.
Alexandre Lision51140e12013-12-02 10:54:09 -0500139};
140
141/**
142 * Class to generate objects giving timeout functionality.
143 *
144 * @author Erik Eliasson
145 * @author Werner Dittmann
146 */
147template<class TOCommand, class TOSubscriber>
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500148class TimeoutProvider : public ost::Thread, ost::Event {
Alexandre Lision51140e12013-12-02 10:54:09 -0500149
150public:
151
152 /**
153 * Timeout Provide Constructor
154 */
155 TimeoutProvider(): requests(), synchLock(), stop(false) { }
156
157 /**
158 * Destructor also terminates the Timeout thread.
159 */
160 ~TimeoutProvider() {
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500161 terminate();
Alexandre Lision51140e12013-12-02 10:54:09 -0500162 }
163
164 /**
165 * Terminates the Timeout provider thread.
166 */
167 void stopThread(){
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500168 stop = true;
169 signal(); // signal event to waiting thread
Alexandre Lision51140e12013-12-02 10:54:09 -0500170 }
171
172 /**
173 * Request a timeout trigger.
174 *
175 * @param time_ms Number of milli-seconds until the timeout is
176 * wanted. Note that a small additional period of time is
177 * added that depends on execution speed.
178 * @param subscriber The receiver of the callback when the command has timed
179 * out. This argument must not be NULL.
180 * @param command Specifies the String command to be passed back in the
181 * callback.
182 */
183 void requestTimeout(int32_t time_ms, TOSubscriber subscriber, const TOCommand &command)
184 {
185 TPRequest<TOCommand, TOSubscriber>* request =
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500186 new TPRequest<TOCommand, TOSubscriber>(subscriber, time_ms, command);
Alexandre Lision51140e12013-12-02 10:54:09 -0500187
188 synchLock.enter();
189
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500190 if (requests.size()==0) {
191 requests.push_front(request);
192 signal();
193 synchLock.leave();
194 return;
195 }
Alexandre Lision51140e12013-12-02 10:54:09 -0500196 if (request->happensBefore(requests.front())) {
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500197 requests.push_front(request);
198 signal();
Alexandre Lision51140e12013-12-02 10:54:09 -0500199 synchLock.leave();
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500200 return;
201 }
Alexandre Lision51140e12013-12-02 10:54:09 -0500202 if (requests.back()->happensBefore(request)){
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500203 requests.push_back(request);
204 signal();
Alexandre Lision51140e12013-12-02 10:54:09 -0500205 synchLock.leave();
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500206 return;
207 }
Alexandre Lision51140e12013-12-02 10:54:09 -0500208
209 typename std::list<TPRequest<TOCommand, TOSubscriber>* >::iterator i;
210 for(i = requests.begin(); i != requests.end(); i++ ) {
211 if( request->happensBefore(*i)) {
212 requests.insert(i, request);
213 break;
214 }
215 }
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500216 signal();
217 synchLock.leave();
Alexandre Lision51140e12013-12-02 10:54:09 -0500218 }
219
220 /**
221 * Removes timeout requests that belong to a subscriber and command.
222 *
223 * @see requestTimeout
224 */
225 void cancelRequest(TOSubscriber subscriber, const TOCommand &command)
226 {
227 synchLock.enter();
228 typename std::list<TPRequest<TOCommand, TOSubscriber>* >::iterator i;
229 for(i = requests.begin(); i != requests.end(); ) {
230 if( (*i)->getCommand() == command &&
231 (*i)->getSubscriber() == subscriber) {
232 i = requests.erase(i);
233 continue;
234 }
235 i++;
236 }
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500237 synchLock.leave();
Alexandre Lision51140e12013-12-02 10:54:09 -0500238 }
239
240protected:
241
242 void run()
243 {
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500244 do {
245 synchLock.enter();
246 int32_t time = 3600000;
247 int32_t size = 0;
248 if ((size = requests.size()) > 0) {
Alexandre Lision51140e12013-12-02 10:54:09 -0500249 time = requests.front()->getMsToTimeout();
250 }
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500251 if (time == 0 && size > 0) {
252 if (stop){ // This must be checked so that we will
253 // stop even if we have timeouts to deliver.
Alexandre Lision51140e12013-12-02 10:54:09 -0500254 synchLock.leave();
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500255 return;
256 }
Alexandre Lision51140e12013-12-02 10:54:09 -0500257 TPRequest<TOCommand, TOSubscriber>* req = requests.front();
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500258 TOSubscriber subs = req->getSubscriber();
259 TOCommand command = req->getCommand();
Alexandre Lision51140e12013-12-02 10:54:09 -0500260
261 requests.pop_front();
262
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500263 synchLock.leave(); // call the command with free Mutex
264 subs->handleTimeout(command);
265 continue;
266 }
267 synchLock.leave();
268 if (stop) { // If we were told to stop while delivering
269 // a timeout we will exit here
270 return;
271 }
272 reset(); // ready to receive triggers again
Alexandre Lision51140e12013-12-02 10:54:09 -0500273 wait(time);
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500274 if (stop) { // If we are told to exit while waiting we
275 // will exit
276 return;
277 }
278 } while(true);
Alexandre Lision51140e12013-12-02 10:54:09 -0500279 }
280
281private:
282
283 // The timeouts are ordered in the order of which they
284 // will expire. Nearest in future is first in list.
285 std::list<TPRequest<TOCommand, TOSubscriber> *> requests;
286
287 ost::Mutex synchLock; // Protects the internal data structures
288
289 bool stop; // Flag to tell the worker thread
Alexandre Lision7fd5d3d2013-12-04 13:06:40 -0500290 // to terminate. Set to true and
291 // wake the worker thread to
292 // terminate it.
Alexandre Lision51140e12013-12-02 10:54:09 -0500293};
294
295#endif