xref: /haiku/src/kits/tracker/TaskLoop.cpp (revision a07cdb6e9f8e484b6ba9f209fbeb144e906d3405)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All rights reserved.
33 */
34 
35 #include <Debug.h>
36 #include <InterfaceDefs.h>
37 
38 #include "AutoLock.h"
39 #include "TaskLoop.h"
40 
41 
42 const float kTaskOverhead = 0.01f;
43 	// this should really be specified by the task itself
44 const float kIdleTreshold = 0.15f;
45 
46 const bigtime_t kInfinity = B_INFINITE_TIMEOUT;
47 
48 
49 static bigtime_t
50 ActivityLevel()
51 {
52 	// stolen from roster server
53 	bigtime_t time = 0;
54 	system_info	sinfo;
55 	get_system_info(&sinfo);
56 
57 	cpu_info* cpuInfos = new cpu_info[sinfo.cpu_count];
58 	get_cpu_info(0, sinfo.cpu_count, cpuInfos);
59 
60 	for (uint32 index = 0; index < sinfo.cpu_count; index++)
61 		time += cpuInfos[index].active_time;
62 
63 	delete[] cpuInfos;
64 	return time / ((bigtime_t) sinfo.cpu_count);
65 }
66 
67 
68 class AccumulatedOneShotDelayedTask : public OneShotDelayedTask {
69 	// supports accumulating functors
70 public:
71 	AccumulatedOneShotDelayedTask(AccumulatingFunctionObject* functor,
72 		bigtime_t delay, bigtime_t maxAccumulatingTime = 0,
73 		int32 maxAccumulateCount = 0)
74 		:
75 		OneShotDelayedTask(functor, delay),
76 		maxAccumulateCount(maxAccumulateCount),
77 		accumulateCount(1),
78 		maxAccumulatingTime(maxAccumulatingTime),
79 		initialTime(system_time())
80 	{
81 	}
82 
83 	bool CanAccumulate(const AccumulatingFunctionObject* accumulateThis) const
84 	{
85 		if (maxAccumulateCount && accumulateCount > maxAccumulateCount)
86 			// don't accumulate if too may accumulated already
87 			return false;
88 
89 		if (maxAccumulatingTime && system_time() > initialTime
90 				+ maxAccumulatingTime) {
91 			// don't accumulate if too late past initial task
92 			return false;
93 		}
94 
95 		return static_cast<AccumulatingFunctionObject*>(fFunctor)->
96 			CanAccumulate(accumulateThis);
97 	}
98 
99 	virtual void Accumulate(AccumulatingFunctionObject* accumulateThis,
100 		bigtime_t delay)
101 	{
102 		fRunAfter = system_time() + delay;
103 			// reset fRunAfter
104 		accumulateCount++;
105 		static_cast<AccumulatingFunctionObject*>(fFunctor)->
106 			Accumulate(accumulateThis);
107 	}
108 
109 private:
110 	int32 maxAccumulateCount;
111 	int32 accumulateCount;
112 	bigtime_t maxAccumulatingTime;
113 	bigtime_t initialTime;
114 };
115 
116 
117 //	#pragma mark - DelayedTask
118 
119 
120 DelayedTask::DelayedTask(bigtime_t delay)
121 	:
122 	fRunAfter(system_time() + delay)
123 {
124 }
125 
126 
127 DelayedTask::~DelayedTask()
128 {
129 }
130 
131 
132 //	#pragma mark - OneShotDelayedTask
133 
134 
135 OneShotDelayedTask::OneShotDelayedTask(FunctionObject* functor,
136 	bigtime_t delay)
137 	:
138 	DelayedTask(delay),
139 	fFunctor(functor)
140 {
141 }
142 
143 
144 OneShotDelayedTask::~OneShotDelayedTask()
145 {
146 	delete fFunctor;
147 }
148 
149 
150 bool
151 OneShotDelayedTask::RunIfNeeded(bigtime_t currentTime)
152 {
153 	if (currentTime < fRunAfter)
154 		return false;
155 
156 	(*fFunctor)();
157 	return true;
158 }
159 
160 
161 //	#pragma mark - PeriodicDelayedTask
162 
163 
164 PeriodicDelayedTask::PeriodicDelayedTask(
165 	FunctionObjectWithResult<bool>* functor, bigtime_t initialDelay,
166 	bigtime_t period)
167 	:
168 	DelayedTask(initialDelay),
169 	fPeriod(period),
170 	fFunctor(functor)
171 {
172 }
173 
174 
175 PeriodicDelayedTask::~PeriodicDelayedTask()
176 {
177 	delete fFunctor;
178 }
179 
180 
181 bool
182 PeriodicDelayedTask::RunIfNeeded(bigtime_t currentTime)
183 {
184 	if (!currentTime < fRunAfter)
185 		return false;
186 
187 	fRunAfter = currentTime + fPeriod;
188 	(*fFunctor)();
189 	return fFunctor->Result();
190 }
191 
192 
193 PeriodicDelayedTaskWithTimeout::PeriodicDelayedTaskWithTimeout(
194 	FunctionObjectWithResult<bool>* functor, bigtime_t initialDelay,
195 	bigtime_t period, bigtime_t timeout)
196 	:
197 	PeriodicDelayedTask(functor, initialDelay, period),
198 	fTimeoutAfter(system_time() + timeout)
199 {
200 }
201 
202 
203 bool
204 PeriodicDelayedTaskWithTimeout::RunIfNeeded(bigtime_t currentTime)
205 {
206 	if (currentTime < fRunAfter)
207 		return false;
208 
209 	fRunAfter = currentTime + fPeriod;
210 	(*fFunctor)();
211 	if (fFunctor->Result())
212 		return true;
213 
214 	// if call didn't terminate the task yet, check if timeout is due
215 	return currentTime > fTimeoutAfter;
216 }
217 
218 
219 RunWhenIdleTask::RunWhenIdleTask(FunctionObjectWithResult<bool>* functor,
220 	bigtime_t initialDelay, bigtime_t idleFor, bigtime_t heartBeat)
221 	:
222 	PeriodicDelayedTask(functor, initialDelay, heartBeat),
223 	fIdleFor(idleFor),
224 	fState(kInitialDelay)
225 {
226 }
227 
228 
229 //	#pragma mark - RunWhenIdleTask
230 
231 
232 RunWhenIdleTask::~RunWhenIdleTask()
233 {
234 }
235 
236 
237 bool
238 RunWhenIdleTask::RunIfNeeded(bigtime_t currentTime)
239 {
240 	if (currentTime < fRunAfter)
241 		return false;
242 
243 	fRunAfter = currentTime + fPeriod;
244 //	PRINT(("runWhenIdle: runAfter %Ld, current time %Ld, period %Ld\n",
245 //		fRunAfter, currentTime, fPeriod));
246 
247 	if (fState == kInitialDelay) {
248 //		PRINT(("run when idle task - past intial delay\n"));
249 		ResetIdleTimer(currentTime);
250 	} else if (fState == kInIdleState && !StillIdle(currentTime)) {
251 		fState = kInitialIdleWait;
252 		ResetIdleTimer(currentTime);
253 	} else if (fState != kInitialIdleWait || IdleTimerExpired(currentTime)) {
254 		fState = kInIdleState;
255 		(*fFunctor)();
256 		return fFunctor->Result();
257 	}
258 
259 	return false;
260 }
261 
262 
263 void
264 RunWhenIdleTask::ResetIdleTimer(bigtime_t currentTime)
265 {
266 	fActivityLevel = ActivityLevel();
267 	fActivityLevelStart = currentTime;
268 	fLastCPUTooBusyTime = currentTime;
269 	fState = kInitialIdleWait;
270 }
271 
272 
273 bool
274 RunWhenIdleTask::IsIdle(bigtime_t currentTime, float taskOverhead)
275 {
276 	bigtime_t currentActivityLevel = ActivityLevel();
277 	float load = (float)(currentActivityLevel - fActivityLevel)
278 		/ (float)(currentTime - fActivityLevelStart);
279 
280 	fActivityLevel = currentActivityLevel;
281 	fActivityLevelStart = currentTime;
282 
283 	load -= taskOverhead;
284 
285 	bool idle = true;
286 
287 	if (load > kIdleTreshold) {
288 //		PRINT(("not idle enough %f\n", load));
289 		idle = false;
290 	} else if ((currentTime - fLastCPUTooBusyTime) < fIdleFor
291 		|| idle_time() < fIdleFor) {
292 //		PRINT(("load %f, not idle long enough %Ld, %Ld\n", load,
293 //			currentTime - fLastCPUTooBusyTime,
294 //			idle_time()));
295 		idle = false;
296 	}
297 
298 #if xDEBUG
299 	else
300 		PRINT(("load %f, idle for %Ld sec, go\n", load,
301 			(currentTime - fLastCPUTooBusyTime) / 1000000));
302 #endif
303 
304 	return idle;
305 }
306 
307 
308 bool
309 RunWhenIdleTask::IdleTimerExpired(bigtime_t currentTime)
310 {
311 	return IsIdle(currentTime, 0);
312 }
313 
314 
315 bool
316 RunWhenIdleTask::StillIdle(bigtime_t currentTime)
317 {
318 	return IsIdle(currentTime, kIdleTreshold);
319 }
320 
321 
322 //	#pragma mark - TaskLoop
323 
324 
325 TaskLoop::TaskLoop(bigtime_t heartBeat)
326 	:
327 	fTaskList(10, true),
328 	fHeartBeat(heartBeat)
329 {
330 }
331 
332 
333 TaskLoop::~TaskLoop()
334 {
335 }
336 
337 
338 void
339 TaskLoop::RunLater(DelayedTask* task)
340 {
341 	AddTask(task);
342 }
343 
344 
345 void
346 TaskLoop::RunLater(FunctionObject* functor, bigtime_t delay)
347 {
348 	RunLater(new OneShotDelayedTask(functor, delay));
349 }
350 
351 
352 void
353 TaskLoop::RunLater(FunctionObjectWithResult<bool>* functor,
354 	bigtime_t delay, bigtime_t period)
355 {
356 	RunLater(new PeriodicDelayedTask(functor, delay, period));
357 }
358 
359 
360 void
361 TaskLoop::RunLater(FunctionObjectWithResult<bool>* functor, bigtime_t delay,
362 	bigtime_t period, bigtime_t timeout)
363 {
364 	RunLater(new PeriodicDelayedTaskWithTimeout(functor, delay, period,
365 		timeout));
366 }
367 
368 
369 void
370 TaskLoop::RunWhenIdle(FunctionObjectWithResult<bool>* functor,
371 	bigtime_t initialDelay, bigtime_t idleTime, bigtime_t heartBeat)
372 {
373 	RunLater(new RunWhenIdleTask(functor, initialDelay, idleTime, heartBeat));
374 }
375 
376 
377 //	#pragma mark - TaskLoop
378 
379 
380 void
381 TaskLoop::AccumulatedRunLater(AccumulatingFunctionObject* functor,
382 	bigtime_t delay, bigtime_t maxAccumulatingTime, int32 maxAccumulateCount)
383 {
384 	AutoLock<BLocker> autoLock(&fLock);
385 	if (!autoLock.IsLocked()) {
386 		return;
387 	}
388 	int32 count = fTaskList.CountItems();
389 	for (int32 index = 0; index < count; index++) {
390 		AccumulatedOneShotDelayedTask* task
391 			= dynamic_cast<AccumulatedOneShotDelayedTask*>(
392 				fTaskList.ItemAt(index));
393 		if (!task)
394 			continue;
395 
396 		if (task->CanAccumulate(functor)) {
397 			task->Accumulate(functor, delay);
398 			return;
399 		}
400 	}
401 	RunLater(new AccumulatedOneShotDelayedTask(functor, delay,
402 		maxAccumulatingTime, maxAccumulateCount));
403 }
404 
405 
406 bool
407 TaskLoop::Pulse()
408 {
409 	ASSERT(fLock.IsLocked());
410 
411 	int32 count = fTaskList.CountItems();
412 	if (count > 0) {
413 		bigtime_t currentTime = system_time();
414 		for (int32 index = 0; index < count; ) {
415 			DelayedTask* task = fTaskList.ItemAt(index);
416 			// give every task a try
417 			if (task->RunIfNeeded(currentTime)) {
418 				// if done, remove from list
419 				RemoveTask(task);
420 				count--;
421 			} else
422 				index++;
423 		}
424 	}
425 	return count == 0 && !KeepPulsingWhenEmpty();
426 }
427 
428 
429 bigtime_t
430 TaskLoop::LatestRunTime() const
431 {
432 	ASSERT(fLock.IsLocked());
433 	bigtime_t result = kInfinity;
434 
435 #if xDEBUG
436 	DelayedTask* nextTask = 0;
437 #endif
438 	int32 count = fTaskList.CountItems();
439 	for (int32 index = 0; index < count; index++) {
440 		bigtime_t runAfter = fTaskList.ItemAt(index)->RunAfterTime();
441 		if (runAfter < result) {
442 			result = runAfter;
443 
444 #if xDEBUG
445 			nextTask = fTaskList.ItemAt(index);
446 #endif
447 		}
448 	}
449 
450 
451 #if xDEBUG
452 	if (nextTask)
453 		PRINT(("latestRunTime : next task %s\n", typeid(*nextTask).name));
454 	else
455 		PRINT(("latestRunTime : no next task\n"));
456 #endif
457 
458 	return result;
459 }
460 
461 
462 void
463 TaskLoop::RemoveTask(DelayedTask* task)
464 {
465 	ASSERT(fLock.IsLocked());
466 	// remove the task
467 	fTaskList.RemoveItem(task);
468 }
469 
470 void
471 TaskLoop::AddTask(DelayedTask* task)
472 {
473 	AutoLock<BLocker> autoLock(&fLock);
474 	if (!autoLock.IsLocked()) {
475 		delete task;
476 		return;
477 	}
478 
479 	fTaskList.AddItem(task);
480 	StartPulsingIfNeeded();
481 }
482 
483 
484 //	#pragma mark - StandAloneTaskLoop
485 
486 
487 StandAloneTaskLoop::StandAloneTaskLoop(bool keepThread, bigtime_t heartBeat)
488 	:
489 	TaskLoop(heartBeat),
490 	fNeedToQuit(false),
491 	fScanThread(-1),
492 	fKeepThread(keepThread)
493 {
494 }
495 
496 
497 StandAloneTaskLoop::~StandAloneTaskLoop()
498 {
499 	fLock.Lock();
500 	fNeedToQuit = true;
501 	bool easyOut = (fScanThread == -1);
502 	fLock.Unlock();
503 
504 	if (!easyOut)
505 		for (int32 timeout = 10000; ; timeout--) {
506 			// use a 10 sec timeout value in case the spawned
507 			// thread is stuck somewhere
508 
509 			if (!timeout) {
510 				PRINT(("StandAloneTaskLoop timed out, quitting abruptly"));
511 				break;
512 			}
513 
514 			bool done;
515 
516 			fLock.Lock();
517 			done = (fScanThread == -1);
518 			fLock.Unlock();
519 			if (done)
520 				break;
521 
522 			snooze(1000);
523 		}
524 }
525 
526 
527 void
528 StandAloneTaskLoop::StartPulsingIfNeeded()
529 {
530 	ASSERT(fLock.IsLocked());
531 	if (fScanThread < 0) {
532 		// no loop thread yet, spawn one
533 		fScanThread = spawn_thread(StandAloneTaskLoop::RunBinder,
534 			"TrackerTaskLoop", B_LOW_PRIORITY, this);
535 		resume_thread(fScanThread);
536 	}
537 }
538 
539 
540 bool
541 StandAloneTaskLoop::KeepPulsingWhenEmpty() const
542 {
543 	return fKeepThread;
544 }
545 
546 
547 status_t
548 StandAloneTaskLoop::RunBinder(void* castToThis)
549 {
550 	StandAloneTaskLoop* self = (StandAloneTaskLoop*)castToThis;
551 	self->Run();
552 	return B_OK;
553 }
554 
555 
556 void
557 StandAloneTaskLoop::Run()
558 {
559 	for(;;) {
560 		AutoLock<BLocker> autoLock(&fLock);
561 		if (!autoLock)
562 			return;
563 
564 		if (fNeedToQuit) {
565 			// task loop being deleted, let go of the thread allowing the
566 			// to go through deletion
567 			fScanThread = -1;
568 			return;
569 		}
570 
571 		if (Pulse()) {
572 			fScanThread = -1;
573 			return;
574 		}
575 
576 		// figure out when to run next by checking out when the different
577 		// tasks wan't to be woken up, snooze until a little bit before that
578 		// time
579 		bigtime_t now = system_time();
580 		bigtime_t latestRunTime = LatestRunTime() - 1000;
581 		bigtime_t afterHeartBeatTime = now + fHeartBeat;
582 		bigtime_t snoozeTill = latestRunTime < afterHeartBeatTime ?
583 			latestRunTime : afterHeartBeatTime;
584 
585 		autoLock.Unlock();
586 
587 		if (snoozeTill > now)
588 			snooze_until(snoozeTill, B_SYSTEM_TIMEBASE);
589 		else
590 			snooze(1000);
591 	}
592 }
593 
594 
595 void
596 StandAloneTaskLoop::AddTask(DelayedTask* delayedTask)
597 {
598 	_inherited::AddTask(delayedTask);
599 	if (fScanThread < 0)
600 		return;
601 
602 	// wake up the loop thread if it is asleep
603 	thread_info info;
604 	get_thread_info(fScanThread, &info);
605 	if (info.state == B_THREAD_ASLEEP) {
606 		suspend_thread(fScanThread);
607 		snooze(1000);	// snooze because BeBook sez so
608 		resume_thread(fScanThread);
609 	}
610 }
611 
612 
613 //	#pragma mark - PiggybackTaskLoop
614 
615 
616 PiggybackTaskLoop::PiggybackTaskLoop(bigtime_t heartBeat)
617 	:
618 	TaskLoop(heartBeat),
619 	fNextHeartBeatTime(0),
620 	fPulseMe(false)
621 {
622 }
623 
624 
625 PiggybackTaskLoop::~PiggybackTaskLoop()
626 {
627 }
628 
629 
630 void
631 PiggybackTaskLoop::PulseMe()
632 {
633 	if (!fPulseMe)
634 		return;
635 
636 	bigtime_t time = system_time();
637 	if (fNextHeartBeatTime < time) {
638 		AutoLock<BLocker> autoLock(&fLock);
639 		if (Pulse())
640 			fPulseMe = false;
641 		fNextHeartBeatTime = time + fHeartBeat;
642 	}
643 }
644 
645 
646 bool
647 PiggybackTaskLoop::KeepPulsingWhenEmpty() const
648 {
649 	return false;
650 }
651 
652 
653 void
654 PiggybackTaskLoop::StartPulsingIfNeeded()
655 {
656 	fPulseMe = true;
657 }
658