]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/mtasker.cc
auth: switch circleci mssql image
[thirdparty/pdns.git] / pdns / mtasker.cc
CommitLineData
12c86877 1/*
12471842
PL
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
870a0fe4
AT
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
12c86877 25#include "mtasker.hh"
34e779bc 26#include "misc.hh"
12c86877
BH
27#include <stdio.h>
28#include <iostream>
29
ec7fcc43
RG
30#ifdef PDNS_USE_VALGRIND
31#include <valgrind/valgrind.h>
32#endif /* PDNS_USE_VALGRIND */
b80f25df 33
bdc9f8d2 34/** \page MTasker
12c86877
BH
35 Simple system for implementing cooperative multitasking of functions, with
36 support for waiting on events which can return values.
37
38 \section copyright Copyright and License
a2745e89 39 MTasker is (c) 2002 - 2009 by bert hubert. It is licensed to you under the terms of the GPL version 2.
12c86877
BH
40
41 \section overview High level overview
42 MTasker is designed to support very simple cooperative multitasking to facilitate writing
43 code that would ordinarily require a statemachine, for which the author does not consider
44 himself smart enough.
45
46 This class does not perform any magic it only makes calls to makecontext() and swapcontext().
47 Getting the details right however is complicated and MTasker does that for you.
48
49 If preemptive multitasking or more advanced concepts such as semaphores, locks or mutexes
50 are required, the use of POSIX threads is advised.
51
52 MTasker is designed to offer the performance of statemachines while maintaining simple thread semantics. It is not
53 a replacement for a full threading system.
54
abc1d928 55 \section compatibility Compatibility
d3491d5a
BH
56 MTasker is only guaranteed to work on Linux with glibc 2.2.5 and higher. It does not work on FreeBSD and notably,
57 not on Red Hat 6.0. It may work on Solaris, please test.
58
12c86877
BH
59 \section concepts Concepts
60
61 There are two important concepts, the 'kernel' and the 'thread'. Each thread starts out as a function,
62 which is passed to MTasker::makeThread(), together with a possible argument.
63
64 This function is now free to do whatever it wants, but realise that MTasker implements cooperative
0658a915 65 multitasking, which means that the coder has the responsibility of not taking the CPU overly long.
12c86877
BH
66 Other threads can only get the CPU if MTasker::yield() is called or if a thread sleeps to wait for an event,
67 using the MTasker::waitEvent() method.
68
69 \section kernel The Kernel
70 The Kernel consists of functions that do housekeeping, but also of code that the client coder
71 can call to report events. A minimal kernel loop looks like this:
72 \code
73 for(;;) {
74 MT.schedule();
75 if(MT.noProcesses()) // exit if no processes are left
76 break;
77 }
78 \endcode
79
80 The kernel typically starts from the main() function of your program. New threads are also
81 created from the kernel. This can also happen before entering the main loop. To start a thread,
82 the method MTasker::makeThread is provided.
83
84 \section events Events
85 By default, Events are recognized by an int and their value is also an int.
abc1d928 86 This can be overridden by specifying the EventKey and EventVal template parameters.
12c86877
BH
87
88 An event can be a keypress, but also a UDP packet, or a bit of data from a TCP socket. The
89 sample code provided works with keypresses, but that is just a not very useful example.
90
91 A thread can also wait for an event only for a limited time, and receive a timeout of that
92 event did not occur within the specified timeframe.
93
94 \section example A simple menu system
95 \code
96MTasker<> MT;
97
98void menuHandler(void *p)
99{
100 int num=(int)p;
101 cout<<"Key handler for key "<<num<<" launched"<<endl;
102
103 MT.waitEvent(num);
104 cout<<"Key "<<num<<" was pressed!"<<endl;
105}
106
107
108int main()
109{
110 char line[10];
111
112 for(int i=0;i<10;++i)
113 MT.makeThread(menuHandler,(void *)i);
114
115 for(;;) {
116 while(MT.schedule()); // do everything we can do
117 if(MT.noProcesses()) // exit if no processes are left
118 break;
119
120 if(!fgets(line,sizeof(line),stdin))
121 break;
122
123 MT.sendEvent(*line-'0');
124 }
125}
126\endcode
127
128\section example2 Canonical multitasking example
129This implements the canonical multitasking example, alternately printing an 'A' and a 'B'. The Linux kernel
130 started this way too.
131\code
132void printer(void *p)
133{
134 char c=(char)p;
135 for(;;) {
136 cout<<c<<endl;
137 MT.yield();
138 }
139
140}
141
142int main()
143{
144 MT.makeThread(printer,(void*)'a');
145 MT.makeThread(printer,(void*)'b');
146
147 for(;;) {
148 while(MT.schedule()); // do everything we can do
149 if(MT.noProcesses()) // exit if no processes are left
150 break;
151 }
152}
153\endcode
154
155*/
156
157//! puts a thread to sleep waiting until a specified event arrives
158/** Threads can call waitEvent to register that they are waiting on an event with a certain key.
abc1d928 159 If so desired, the event can carry data which is returned in val in case that is non-zero.
12c86877
BH
160
161 Furthermore, a timeout can be specified in seconds.
162
163 Only one thread can be waiting on a key, results of trying to have more threads
164 waiting on the same key are undefined.
165
166 \param key Event key to wait for. Needs to match up to a key reported to sendEvent
167 \param val If non-zero, the value of the event will be stored in *val
168 \param timeout If non-zero, number of seconds to wait for an event.
169
170 \return returns -1 in case of error, 0 in case of timeout, 1 in case of an answer
171*/
172
5b0ddd18 173template<class EventKey, class EventVal>int MTasker<EventKey,EventVal>::waitEvent(EventKey &key, EventVal *val, unsigned int timeoutMsec, struct timeval* now)
12c86877 174{
f9f05db4
BH
175 if(d_waiters.count(key)) { // there was already an exact same waiter
176 return -1;
177 }
178
12c86877 179 Waiter w;
5cf909f3 180 w.context=std::make_shared<pdns_ucontext_t>();
5b0ddd18
BH
181 w.ttd.tv_sec = 0; w.ttd.tv_usec = 0;
182 if(timeoutMsec) {
183 struct timeval increment;
184 increment.tv_sec = timeoutMsec / 1000;
185 increment.tv_usec = 1000 * (timeoutMsec % 1000);
186 if(now)
187 w.ttd = increment + *now;
188 else {
189 struct timeval realnow;
190 gettimeofday(&realnow, 0);
191 w.ttd = increment + realnow;
192 }
193 }
f6c254c1 194
12c86877 195 w.tid=d_tid;
27af6ab1
BH
196 w.key=key;
197
198 d_waiters.insert(w);
b80f25df 199#ifdef MTASKERTIMING
200 unsigned int diff=d_threads[d_tid].dt.ndiff()/1000;
201 d_threads[d_tid].totTime+=diff;
202#endif
9c811931 203 notifyStackSwitchToKernel();
5cf909f3 204 pdns_swapcontext(*d_waiters.find(key)->context,d_kernel); // 'A' will return here when 'key' has arrived, hands over control to kernel first
9c811931 205 notifyStackSwitchDone();
b80f25df 206#ifdef MTASKERTIMING
207 d_threads[d_tid].dt.start();
208#endif
12c86877
BH
209 if(val && d_waitstatus==Answer)
210 *val=d_waitval;
b636533b 211 d_tid=w.tid;
ec6eacbc
BH
212 if((char*)&w < d_threads[d_tid].highestStackSeen) {
213 d_threads[d_tid].highestStackSeen = (char*)&w;
214 }
35ce8576 215 key=d_eventkey;
12c86877
BH
216 return d_waitstatus;
217}
218
219//! yields control to the kernel or other threads
220/** Hands over control to the kernel, allowing other processes to run, or events to arrive */
221
222template<class Key, class Val>void MTasker<Key,Val>::yield()
223{
224 d_runQueue.push(d_tid);
9c811931 225 notifyStackSwitchToKernel();
5cf909f3 226 pdns_swapcontext(*d_threads[d_tid].context ,d_kernel); // give control to the kernel
9c811931 227 notifyStackSwitchDone();
12c86877
BH
228}
229
230//! reports that an event took place for which threads may be waiting
7c696097 231/** From the kernel loop, sendEvent can be called to report that something occurred for which there may be waiters.
12c86877
BH
232 \param key Key of the event for which threads may be waiting
233 \param val If non-zero, pointer to the content of the event
234 \return Returns -1 in case of error, 0 if there were no waiters, 1 if a thread was woken up.
50c81227
BH
235
236 WARNING: when passing val as zero, d_waitval is undefined, and hence waitEvent will return undefined!
12c86877
BH
237*/
238template<class EventKey, class EventVal>int MTasker<EventKey,EventVal>::sendEvent(const EventKey& key, const EventVal* val)
239{
d903b428
BH
240 typename waiters_t::iterator waiter=d_waiters.find(key);
241
242 if(waiter == d_waiters.end()) {
9170fbaf 243 // cout<<"Event sent nobody was waiting for!"<<endl;
12c86877
BH
244 return 0;
245 }
246
247 d_waitstatus=Answer;
248 if(val)
249 d_waitval=*val;
250
d903b428 251 d_tid=waiter->tid; // set tid
35ce8576 252 d_eventkey=waiter->key; // pass waitEvent the exact key it was woken for
5cf909f3 253 auto userspace=std::move(waiter->context);
9c811931
RG
254 d_waiters.erase(waiter); // removes the waitpoint
255 notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
5cf909f3 256 pdns_swapcontext(d_kernel,*userspace); // swaps back to the above point 'A'
9c811931 257 notifyStackSwitchDone();
12c86877
BH
258 return 1;
259}
260
261//! launches a new thread
262/** The kernel can call this to make a new thread, which starts at the function start and gets passed the val void pointer.
263 \param start Pointer to the function which will form the start of the thread
264 \param val A void pointer that can be used to pass data to the thread
265*/
d23a4bc7 266template<class Key, class Val>void MTasker<Key,Val>::makeThread(tfunc_t *start, void* val)
12c86877 267{
5cf909f3 268 auto uc=std::make_shared<pdns_ucontext_t>();
12c86877
BH
269
270 uc->uc_link = &d_kernel; // come back to kernel after dying
5529b1b1 271 uc->uc_stack.resize (d_stacksize+1);
ec7fcc43
RG
272#ifdef PDNS_USE_VALGRIND
273 uc->valgrind_id = VALGRIND_STACK_REGISTER(&uc->uc_stack[0],
5529b1b1 274 &uc->uc_stack[uc->uc_stack.size()-1]);
ec7fcc43 275#endif /* PDNS_USE_VALGRIND */
5cf909f3 276
c4975eaf 277 ++d_threadsCount;
5cf909f3
AN
278 auto& thread = d_threads[d_maxtid];
279 auto mt = this;
280 thread.start = [start, val, mt]() {
281 char dummy;
282 mt->d_threads[mt->d_tid].startOfStack = mt->d_threads[mt->d_tid].highestStackSeen = &dummy;
283 auto const tid = mt->d_tid;
284 start (val);
285 mt->d_zombiesQueue.push(tid);
286 };
287 pdns_makecontext (*uc, thread.start);
288
289 thread.context = std::move(uc);
12c86877
BH
290 d_runQueue.push(d_maxtid++); // will run at next schedule invocation
291}
292
293
294//! needs to be called periodically so threads can run and housekeeping can be performed
295/** The kernel should call this function every once in a while. It makes sense
296 to call this function if you:
297 - reported an event
298 - called makeThread
299 - want to have threads running waitEvent() to get a timeout if enough time passed
300
301 \return Returns if there is more work scheduled and recalling schedule now would be useful
302
303*/
5b0ddd18 304template<class Key, class Val>bool MTasker<Key,Val>::schedule(struct timeval* now)
12c86877 305{
12c86877
BH
306 if(!d_runQueue.empty()) {
307 d_tid=d_runQueue.front();
b80f25df 308#ifdef MTASKERTIMING
309 d_threads[d_tid].dt.start();
310#endif
9c811931 311 notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
5cf909f3 312 pdns_swapcontext(d_kernel, *d_threads[d_tid].context);
9c811931
RG
313 notifyStackSwitchDone();
314
12c86877
BH
315 d_runQueue.pop();
316 return true;
317 }
318 if(!d_zombiesQueue.empty()) {
12c86877 319 d_threads.erase(d_zombiesQueue.front());
c4975eaf 320 --d_threadsCount;
12c86877
BH
321 d_zombiesQueue.pop();
322 return true;
323 }
324 if(!d_waiters.empty()) {
5b0ddd18 325 struct timeval rnow;
f6c254c1 326 if(!now)
5b0ddd18
BH
327 gettimeofday(&rnow, 0);
328 else
329 rnow = *now;
27af6ab1 330
b786789d
BH
331 typedef typename waiters_t::template index<KeyTag>::type waiters_by_ttd_index_t;
332 // waiters_by_ttd_index_t& ttdindex=d_waiters.template get<KeyTag>();
333 waiters_by_ttd_index_t& ttdindex=boost::multi_index::get<KeyTag>(d_waiters);
334
27af6ab1 335 for(typename waiters_by_ttd_index_t::iterator i=ttdindex.begin(); i != ttdindex.end(); ) {
5b0ddd18 336 if(i->ttd.tv_sec && i->ttd < rnow) {
4957a608
BH
337 d_waitstatus=TimeOut;
338 d_eventkey=i->key; // pass waitEvent the exact key it was woken for
5cf909f3 339 auto uc = i->context;
e19a1ced 340 d_tid = i->tid;
9c811931 341 ttdindex.erase(i++); // removes the waitpoint
4957a608 342
9c811931 343 notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
5cf909f3 344 pdns_swapcontext(d_kernel, *uc); // swaps back to the above point 'A'
9c811931 345 notifyStackSwitchDone();
12c86877 346 }
5b0ddd18 347 else if(i->ttd.tv_sec)
4957a608 348 break;
887316ee 349 else
350 ++i;
12c86877
BH
351 }
352 }
353 return false;
354}
355
356//! returns true if there are no processes
357/** Call this to check if no processes are running anymore
358 \return true if no processes are left
359 */
06857845 360template<class Key, class Val>bool MTasker<Key,Val>::noProcesses() const
12c86877 361{
c4975eaf 362 return d_threadsCount == 0;
12c86877
BH
363}
364
e5d684f9
BH
365//! returns the number of processes running
366/** Call this to perhaps limit activities if too many threads are running
367 \return number of processes running
368 */
06857845 369template<class Key, class Val>unsigned int MTasker<Key,Val>::numProcesses() const
e5d684f9 370{
c4975eaf 371 return d_threadsCount;
e5d684f9
BH
372}
373
12c86877
BH
374//! gives access to the list of Events threads are waiting for
375/** The kernel can call this to get a list of Events threads are waiting for. This is very useful
376 to setup 'select' or 'poll' or 'aio' events needed to satisfy these requests.
377 getEvents clears the events parameter before filling it.
378
379 \param events Vector which is to be filled with keys threads are waiting for
380*/
381template<class Key, class Val>void MTasker<Key,Val>::getEvents(std::vector<Key>& events)
382{
383 events.clear();
384 for(typename waiters_t::const_iterator i=d_waiters.begin();i!=d_waiters.end();++i) {
385 events.push_back(i->first);
386 }
387}
388
c836dc19
BH
389//! Returns the current Thread ID (tid)
390/** Processes can call this to get a numerical representation of their current thread ID.
391 This can be useful for logging purposes.
392*/
06857845 393template<class Key, class Val>int MTasker<Key,Val>::getTid() const
c836dc19
BH
394{
395 return d_tid;
396}
ec6eacbc 397
ec6eacbc
BH
398//! Returns the maximum stack usage so far of this MThread
399template<class Key, class Val>unsigned int MTasker<Key,Val>::getMaxStackUsage()
400{
401 return d_threads[d_tid].startOfStack - d_threads[d_tid].highestStackSeen;
402}
b80f25df 403
404//! Returns the maximum stack usage so far of this MThread
405template<class Key, class Val>unsigned int MTasker<Key,Val>::getUsec()
406{
407#ifdef MTASKERTIMING
408 return d_threads[d_tid].totTime + d_threads[d_tid].dt.ndiff()/1000;
409#else
410 return 0;
411#endif
412}