]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/mtasker.cc
wuh
[thirdparty/pdns.git] / pdns / mtasker.cc
CommitLineData
12c86877
BH
1/*
2 PowerDNS Versatile Database Driven Nameserver
3 Copyright (C) 2002 PowerDNS.COM BV
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18*/
19#include "mtasker.hh"
20#include <stdio.h>
21#include <iostream>
22
bdc9f8d2 23/** \page MTasker
12c86877
BH
24 Simple system for implementing cooperative multitasking of functions, with
25 support for waiting on events which can return values.
26
27 \section copyright Copyright and License
28 MTasker is (c) 2002 by bert hubert. It is licensed to you under the terms of the GPL version 2.
29
30 \section overview High level overview
31 MTasker is designed to support very simple cooperative multitasking to facilitate writing
32 code that would ordinarily require a statemachine, for which the author does not consider
33 himself smart enough.
34
35 This class does not perform any magic it only makes calls to makecontext() and swapcontext().
36 Getting the details right however is complicated and MTasker does that for you.
37
38 If preemptive multitasking or more advanced concepts such as semaphores, locks or mutexes
39 are required, the use of POSIX threads is advised.
40
41 MTasker is designed to offer the performance of statemachines while maintaining simple thread semantics. It is not
42 a replacement for a full threading system.
43
44 \section concepts Concepts
45
46 There are two important concepts, the 'kernel' and the 'thread'. Each thread starts out as a function,
47 which is passed to MTasker::makeThread(), together with a possible argument.
48
49 This function is now free to do whatever it wants, but realise that MTasker implements cooperative
50 multitasking, which means that the coder has the responsiblilty of not taking the CPU overly long.
51 Other threads can only get the CPU if MTasker::yield() is called or if a thread sleeps to wait for an event,
52 using the MTasker::waitEvent() method.
53
54 \section kernel The Kernel
55 The Kernel consists of functions that do housekeeping, but also of code that the client coder
56 can call to report events. A minimal kernel loop looks like this:
57 \code
58 for(;;) {
59 MT.schedule();
60 if(MT.noProcesses()) // exit if no processes are left
61 break;
62 }
63 \endcode
64
65 The kernel typically starts from the main() function of your program. New threads are also
66 created from the kernel. This can also happen before entering the main loop. To start a thread,
67 the method MTasker::makeThread is provided.
68
69 \section events Events
70 By default, Events are recognized by an int and their value is also an int.
71 This can be overriden by specifying the EventKey and EventVal template parameters.
72
73 An event can be a keypress, but also a UDP packet, or a bit of data from a TCP socket. The
74 sample code provided works with keypresses, but that is just a not very useful example.
75
76 A thread can also wait for an event only for a limited time, and receive a timeout of that
77 event did not occur within the specified timeframe.
78
79 \section example A simple menu system
80 \code
81MTasker<> MT;
82
83void menuHandler(void *p)
84{
85 int num=(int)p;
86 cout<<"Key handler for key "<<num<<" launched"<<endl;
87
88 MT.waitEvent(num);
89 cout<<"Key "<<num<<" was pressed!"<<endl;
90}
91
92
93int main()
94{
95 char line[10];
96
97 for(int i=0;i<10;++i)
98 MT.makeThread(menuHandler,(void *)i);
99
100 for(;;) {
101 while(MT.schedule()); // do everything we can do
102 if(MT.noProcesses()) // exit if no processes are left
103 break;
104
105 if(!fgets(line,sizeof(line),stdin))
106 break;
107
108 MT.sendEvent(*line-'0');
109 }
110}
111\endcode
112
113\section example2 Canonical multitasking example
114This implements the canonical multitasking example, alternately printing an 'A' and a 'B'. The Linux kernel
115 started this way too.
116\code
117void printer(void *p)
118{
119 char c=(char)p;
120 for(;;) {
121 cout<<c<<endl;
122 MT.yield();
123 }
124
125}
126
127int main()
128{
129 MT.makeThread(printer,(void*)'a');
130 MT.makeThread(printer,(void*)'b');
131
132 for(;;) {
133 while(MT.schedule()); // do everything we can do
134 if(MT.noProcesses()) // exit if no processes are left
135 break;
136 }
137}
138\endcode
139
140*/
141
142//! puts a thread to sleep waiting until a specified event arrives
143/** Threads can call waitEvent to register that they are waiting on an event with a certain key.
144 If so desidered, the event can carry data which is returned in val in case that is non-zero.
145
146 Furthermore, a timeout can be specified in seconds.
147
148 Only one thread can be waiting on a key, results of trying to have more threads
149 waiting on the same key are undefined.
150
151 \param key Event key to wait for. Needs to match up to a key reported to sendEvent
152 \param val If non-zero, the value of the event will be stored in *val
153 \param timeout If non-zero, number of seconds to wait for an event.
154
155 \return returns -1 in case of error, 0 in case of timeout, 1 in case of an answer
156*/
157
158template<class EventKey, class EventVal>int MTasker<EventKey,EventVal>::waitEvent(const EventKey &key, EventVal *val, unsigned int timeout)
159{
160 Waiter w;
161 w.context=new ucontext_t;
162 w.ttd= timeout ? time(0)+timeout : 0;
163 w.tid=d_tid;
164
165 d_waiters[key]=w;
166
167 swapcontext(d_waiters[key].context,&d_kernel); // 'A' will return here when 'key' has arrived, hands over control to kernel first
168 if(val && d_waitstatus==Answer)
169 *val=d_waitval;
170 return d_waitstatus;
171}
172
173//! yields control to the kernel or other threads
174/** Hands over control to the kernel, allowing other processes to run, or events to arrive */
175
176template<class Key, class Val>void MTasker<Key,Val>::yield()
177{
178 d_runQueue.push(d_tid);
179 swapcontext(d_threads[d_tid],&d_kernel); // give control to the kernel
180}
181
182//! reports that an event took place for which threads may be waiting
183/** From the kernel loop, sendEvent can be called to report that something occured for which there may be waiters.
184 \param key Key of the event for which threads may be waiting
185 \param val If non-zero, pointer to the content of the event
186 \return Returns -1 in case of error, 0 if there were no waiters, 1 if a thread was woken up.
187*/
188template<class EventKey, class EventVal>int MTasker<EventKey,EventVal>::sendEvent(const EventKey& key, const EventVal* val)
189{
190 if(!d_waiters.count(key)) {
191 return 0;
192 }
193
194 d_waitstatus=Answer;
195 if(val)
196 d_waitval=*val;
197
198 ucontext_t *userspace=d_waiters[key].context;
199 d_tid=d_waiters[key].tid; // set tid
200
201 d_waiters.erase(key); // removes the waitpoint
202 swapcontext(&d_kernel,userspace); // swaps back to the above point 'A'
203
204 delete userspace;
205 return 1;
206}
207
208//! launches a new thread
209/** The kernel can call this to make a new thread, which starts at the function start and gets passed the val void pointer.
210 \param start Pointer to the function which will form the start of the thread
211 \param val A void pointer that can be used to pass data to the thread
212*/
213template<class Key, class Val>void MTasker<Key,Val>::makeThread(tfunc_t *start, void* val)
214{
215 ucontext_t *uc=new ucontext_t;
216 getcontext(uc);
217
218 uc->uc_link = &d_kernel; // come back to kernel after dying
219 uc->uc_stack.ss_sp = new char[d_stacksize];
220
221 uc->uc_stack.ss_size = d_stacksize;
222 makecontext (uc, (void (*)(void))threadWrapper, 4, this, start, d_maxtid, val);
223 d_threads[d_maxtid]=uc;
224 d_runQueue.push(d_maxtid++); // will run at next schedule invocation
225}
226
227
228//! needs to be called periodically so threads can run and housekeeping can be performed
229/** The kernel should call this function every once in a while. It makes sense
230 to call this function if you:
231 - reported an event
232 - called makeThread
233 - want to have threads running waitEvent() to get a timeout if enough time passed
234
235 \return Returns if there is more work scheduled and recalling schedule now would be useful
236
237*/
238template<class Key, class Val>bool MTasker<Key,Val>::schedule()
239{
240
241 if(!d_runQueue.empty()) {
242 d_tid=d_runQueue.front();
243 swapcontext(&d_kernel, d_threads[d_tid]);
244 d_runQueue.pop();
245 return true;
246 }
247 if(!d_zombiesQueue.empty()) {
248 delete (char *)d_threads[d_zombiesQueue.front()]->uc_stack.ss_sp;
249 delete d_threads[d_zombiesQueue.front()];
250 d_threads.erase(d_zombiesQueue.front());
251 d_zombiesQueue.pop();
252 return true;
253 }
254 if(!d_waiters.empty()) {
255 time_t now=time(0);
256 for(typename waiters_t::const_iterator i=d_waiters.begin();i!=d_waiters.end();++i) {
257 if(i->second.ttd && i->second.ttd<now) {
258 d_waitstatus=TimeOut;
259 swapcontext(&d_kernel,i->second.context); // swaps back to the above point 'A'
260 delete i->second.context;
261 d_waiters.erase(i->first); // removes the waitpoint
262 }
263 }
264 }
265 return false;
266}
267
268//! returns true if there are no processes
269/** Call this to check if no processes are running anymore
270 \return true if no processes are left
271 */
272template<class Key, class Val>bool MTasker<Key,Val>::noProcesses()
273{
274 return d_threads.empty();
275}
276
277//! gives access to the list of Events threads are waiting for
278/** The kernel can call this to get a list of Events threads are waiting for. This is very useful
279 to setup 'select' or 'poll' or 'aio' events needed to satisfy these requests.
280 getEvents clears the events parameter before filling it.
281
282 \param events Vector which is to be filled with keys threads are waiting for
283*/
284template<class Key, class Val>void MTasker<Key,Val>::getEvents(std::vector<Key>& events)
285{
286 events.clear();
287 for(typename waiters_t::const_iterator i=d_waiters.begin();i!=d_waiters.end();++i) {
288 events.push_back(i->first);
289 }
290}
291
292
293template<class Key, class Val>void MTasker<Key,Val>::threadWrapper(MTasker *self, tfunc_t *tf, int tid, void* val)
294{
295 (*tf)(val);
296 self->d_zombiesQueue.push(tid);
297
298 // we now jump to &kernel, automatically
299}
300