xref: /haiku/src/kits/tracker/TaskLoop.cpp (revision b247f935d133a42c427cad8a759a1bf2f65bc290)
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 //	#pragma mark - RunWhenIdleTask
220 
221 
222 RunWhenIdleTask::RunWhenIdleTask(FunctionObjectWithResult<bool>* functor,
223 	bigtime_t initialDelay, bigtime_t idleFor, bigtime_t heartBeat)
224 	:
225 	PeriodicDelayedTask(functor, initialDelay, heartBeat),
226 	fIdleFor(idleFor),
227 	fState(kInitialDelay),
228 	fActivityLevelStart(0),
229 	fActivityLevel(0),
230 	fLastCPUTooBusyTime(0)
231 {
232 }
233 
234 
235 RunWhenIdleTask::~RunWhenIdleTask()
236 {
237 }
238 
239 
240 bool
241 RunWhenIdleTask::RunIfNeeded(bigtime_t currentTime)
242 {
243 	if (currentTime < fRunAfter)
244 		return false;
245 
246 	fRunAfter = currentTime + fPeriod;
247 //	PRINT(("runWhenIdle: runAfter %Ld, current time %Ld, period %Ld\n",
248 //		fRunAfter, currentTime, fPeriod));
249 
250 	if (fState == kInitialDelay) {
251 //		PRINT(("run when idle task - past intial delay\n"));
252 		ResetIdleTimer(currentTime);
253 	} else if (fState == kInIdleState && !StillIdle(currentTime)) {
254 		fState = kInitialIdleWait;
255 		ResetIdleTimer(currentTime);
256 	} else if (fState != kInitialIdleWait || IdleTimerExpired(currentTime)) {
257 		fState = kInIdleState;
258 		(*fFunctor)();
259 		return fFunctor->Result();
260 	}
261 
262 	return false;
263 }
264 
265 
266 void
267 RunWhenIdleTask::ResetIdleTimer(bigtime_t currentTime)
268 {
269 	fActivityLevel = ActivityLevel();
270 	fActivityLevelStart = currentTime;
271 	fLastCPUTooBusyTime = currentTime;
272 	fState = kInitialIdleWait;
273 }
274 
275 
276 bool
277 RunWhenIdleTask::IsIdle(bigtime_t currentTime, float taskOverhead)
278 {
279 	bigtime_t currentActivityLevel = ActivityLevel();
280 	float load = (float)(currentActivityLevel - fActivityLevel)
281 		/ (float)(currentTime - fActivityLevelStart);
282 
283 	fActivityLevel = currentActivityLevel;
284 	fActivityLevelStart = currentTime;
285 
286 	load -= taskOverhead;
287 
288 	bool idle = true;
289 
290 	if (load > kIdleTreshold) {
291 //		PRINT(("not idle enough %f\n", load));
292 		idle = false;
293 	} else if ((currentTime - fLastCPUTooBusyTime) < fIdleFor
294 		|| idle_time() < fIdleFor) {
295 //		PRINT(("load %f, not idle long enough %Ld, %Ld\n", load,
296 //			currentTime - fLastCPUTooBusyTime,
297 //			idle_time()));
298 		idle = false;
299 	}
300 
301 #if xDEBUG
302 	else
303 		PRINT(("load %f, idle for %Ld sec, go\n", load,
304 			(currentTime - fLastCPUTooBusyTime) / 1000000));
305 #endif
306 
307 	return idle;
308 }
309 
310 
311 bool
312 RunWhenIdleTask::IdleTimerExpired(bigtime_t currentTime)
313 {
314 	return IsIdle(currentTime, 0);
315 }
316 
317 
318 bool
319 RunWhenIdleTask::StillIdle(bigtime_t currentTime)
320 {
321 	return IsIdle(currentTime, kIdleTreshold);
322 }
323 
324 
325 //	#pragma mark - TaskLoop
326 
327 
328 TaskLoop::TaskLoop(bigtime_t heartBeat)
329 	:
330 	fTaskList(10, true),
331 	fHeartBeat(heartBeat)
332 {
333 }
334 
335 
336 TaskLoop::~TaskLoop()
337 {
338 }
339 
340 
341 void
342 TaskLoop::RunLater(DelayedTask* task)
343 {
344 	AddTask(task);
345 }
346 
347 
348 void
349 TaskLoop::RunLater(FunctionObject* functor, bigtime_t delay)
350 {
351 	RunLater(new OneShotDelayedTask(functor, delay));
352 }
353 
354 
355 void
356 TaskLoop::RunLater(FunctionObjectWithResult<bool>* functor,
357 	bigtime_t delay, bigtime_t period)
358 {
359 	RunLater(new PeriodicDelayedTask(functor, delay, period));
360 }
361 
362 
363 void
364 TaskLoop::RunLater(FunctionObjectWithResult<bool>* functor, bigtime_t delay,
365 	bigtime_t period, bigtime_t timeout)
366 {
367 	RunLater(new PeriodicDelayedTaskWithTimeout(functor, delay, period,
368 		timeout));
369 }
370 
371 
372 void
373 TaskLoop::RunWhenIdle(FunctionObjectWithResult<bool>* functor,
374 	bigtime_t initialDelay, bigtime_t idleTime, bigtime_t heartBeat)
375 {
376 	RunLater(new RunWhenIdleTask(functor, initialDelay, idleTime, heartBeat));
377 }
378 
379 
380 //	#pragma mark - TaskLoop
381 
382 
383 void
384 TaskLoop::AccumulatedRunLater(AccumulatingFunctionObject* functor,
385 	bigtime_t delay, bigtime_t maxAccumulatingTime, int32 maxAccumulateCount)
386 {
387 	AutoLock<BLocker> autoLock(&fLock);
388 	if (!autoLock.IsLocked())
389 		return;
390 
391 	int32 count = fTaskList.CountItems();
392 	for (int32 index = 0; index < count; index++) {
393 		AccumulatedOneShotDelayedTask* task
394 			= dynamic_cast<AccumulatedOneShotDelayedTask*>(
395 				fTaskList.ItemAt(index));
396 		if (task == NULL)
397 			continue;
398 		else if (task->CanAccumulate(functor)) {
399 			task->Accumulate(functor, delay);
400 			return;
401 		}
402 	}
403 
404 	RunLater(new AccumulatedOneShotDelayedTask(functor, delay,
405 		maxAccumulatingTime, maxAccumulateCount));
406 }
407 
408 
409 bool
410 TaskLoop::Pulse()
411 {
412 	ASSERT(fLock.IsLocked());
413 
414 	int32 count = fTaskList.CountItems();
415 	if (count > 0) {
416 		bigtime_t currentTime = system_time();
417 		for (int32 index = 0; index < count; ) {
418 			DelayedTask* task = fTaskList.ItemAt(index);
419 			// give every task a try
420 			if (task->RunIfNeeded(currentTime)) {
421 				// if done, remove from list
422 				RemoveTask(task);
423 				count--;
424 			} else
425 				index++;
426 		}
427 	}
428 	return count == 0 && !KeepPulsingWhenEmpty();
429 }
430 
431 
432 bigtime_t
433 TaskLoop::LatestRunTime() const
434 {
435 	ASSERT(fLock.IsLocked());
436 	bigtime_t result = kInfinity;
437 
438 #if xDEBUG
439 	DelayedTask* nextTask = 0;
440 #endif
441 	int32 count = fTaskList.CountItems();
442 	for (int32 index = 0; index < count; index++) {
443 		bigtime_t runAfter = fTaskList.ItemAt(index)->RunAfterTime();
444 		if (runAfter < result) {
445 			result = runAfter;
446 
447 #if xDEBUG
448 			nextTask = fTaskList.ItemAt(index);
449 #endif
450 		}
451 	}
452 
453 
454 #if xDEBUG
455 	if (nextTask)
456 		PRINT(("latestRunTime : next task %s\n", typeid(*nextTask).name));
457 	else
458 		PRINT(("latestRunTime : no next task\n"));
459 #endif
460 
461 	return result;
462 }
463 
464 
465 void
466 TaskLoop::RemoveTask(DelayedTask* task)
467 {
468 	ASSERT(fLock.IsLocked());
469 	// remove the task
470 	fTaskList.RemoveItem(task);
471 }
472 
473 void
474 TaskLoop::AddTask(DelayedTask* task)
475 {
476 	AutoLock<BLocker> autoLock(&fLock);
477 	if (!autoLock.IsLocked()) {
478 		delete task;
479 		return;
480 	}
481 
482 	fTaskList.AddItem(task);
483 	StartPulsingIfNeeded();
484 }
485 
486 
487 //	#pragma mark - StandAloneTaskLoop
488 
489 
490 StandAloneTaskLoop::StandAloneTaskLoop(bool keepThread, bigtime_t heartBeat)
491 	:
492 	TaskLoop(heartBeat),
493 	fNeedToQuit(false),
494 	fScanThread(-1),
495 	fKeepThread(keepThread)
496 {
497 }
498 
499 
500 StandAloneTaskLoop::~StandAloneTaskLoop()
501 {
502 	fLock.Lock();
503 	fNeedToQuit = true;
504 	bool easyOut = (fScanThread == -1);
505 	fLock.Unlock();
506 
507 	if (!easyOut)
508 		for (int32 timeout = 10000; ; timeout--) {
509 			// use a 10 sec timeout value in case the spawned
510 			// thread is stuck somewhere
511 
512 			if (!timeout) {
513 				PRINT(("StandAloneTaskLoop timed out, quitting abruptly"));
514 				break;
515 			}
516 
517 			bool done;
518 
519 			fLock.Lock();
520 			done = (fScanThread == -1);
521 			fLock.Unlock();
522 			if (done)
523 				break;
524 
525 			snooze(1000);
526 		}
527 }
528 
529 
530 void
531 StandAloneTaskLoop::StartPulsingIfNeeded()
532 {
533 	ASSERT(fLock.IsLocked());
534 	if (fScanThread < 0) {
535 		// no loop thread yet, spawn one
536 		fScanThread = spawn_thread(StandAloneTaskLoop::RunBinder,
537 			"TrackerTaskLoop", B_LOW_PRIORITY, this);
538 		resume_thread(fScanThread);
539 	}
540 }
541 
542 
543 bool
544 StandAloneTaskLoop::KeepPulsingWhenEmpty() const
545 {
546 	return fKeepThread;
547 }
548 
549 
550 status_t
551 StandAloneTaskLoop::RunBinder(void* castToThis)
552 {
553 	StandAloneTaskLoop* self = (StandAloneTaskLoop*)castToThis;
554 	self->Run();
555 	return B_OK;
556 }
557 
558 
559 void
560 StandAloneTaskLoop::Run()
561 {
562 	for(;;) {
563 		AutoLock<BLocker> autoLock(&fLock);
564 		if (!autoLock)
565 			return;
566 
567 		if (fNeedToQuit) {
568 			// task loop being deleted, let go of the thread allowing the
569 			// to go through deletion
570 			fScanThread = -1;
571 			return;
572 		}
573 
574 		if (Pulse()) {
575 			fScanThread = -1;
576 			return;
577 		}
578 
579 		// figure out when to run next by checking out when the different
580 		// tasks wan't to be woken up, snooze until a little bit before that
581 		// time
582 		bigtime_t now = system_time();
583 		bigtime_t latestRunTime = LatestRunTime() - 1000;
584 		bigtime_t afterHeartBeatTime = now + fHeartBeat;
585 		bigtime_t snoozeTill = latestRunTime < afterHeartBeatTime ?
586 			latestRunTime : afterHeartBeatTime;
587 
588 		autoLock.Unlock();
589 
590 		if (snoozeTill > now)
591 			snooze_until(snoozeTill, B_SYSTEM_TIMEBASE);
592 		else
593 			snooze(1000);
594 	}
595 }
596 
597 
598 void
599 StandAloneTaskLoop::AddTask(DelayedTask* delayedTask)
600 {
601 	_inherited::AddTask(delayedTask);
602 	if (fScanThread < 0)
603 		return;
604 
605 	// wake up the loop thread if it is asleep
606 	thread_info info;
607 	get_thread_info(fScanThread, &info);
608 	if (info.state == B_THREAD_ASLEEP) {
609 		suspend_thread(fScanThread);
610 		snooze(1000);	// snooze because BeBook sez so
611 		resume_thread(fScanThread);
612 	}
613 }
614 
615 
616 //	#pragma mark - PiggybackTaskLoop
617 
618 
619 PiggybackTaskLoop::PiggybackTaskLoop(bigtime_t heartBeat)
620 	:
621 	TaskLoop(heartBeat),
622 	fNextHeartBeatTime(0),
623 	fPulseMe(false)
624 {
625 }
626 
627 
628 PiggybackTaskLoop::~PiggybackTaskLoop()
629 {
630 }
631 
632 
633 void
634 PiggybackTaskLoop::PulseMe()
635 {
636 	if (!fPulseMe)
637 		return;
638 
639 	bigtime_t time = system_time();
640 	if (fNextHeartBeatTime < time) {
641 		AutoLock<BLocker> autoLock(&fLock);
642 		if (Pulse())
643 			fPulseMe = false;
644 		fNextHeartBeatTime = time + fHeartBeat;
645 	}
646 }
647 
648 
649 bool
650 PiggybackTaskLoop::KeepPulsingWhenEmpty() const
651 {
652 	return false;
653 }
654 
655 
656 void
657 PiggybackTaskLoop::StartPulsingIfNeeded()
658 {
659 	fPulseMe = true;
660 }
661