xref: /haiku/src/kits/tracker/TaskLoop.cpp (revision 99d027cd0238c1d86da86d7c3f4200509ccc61a6)
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 DelayedTask::DelayedTask(bigtime_t delay)
43 	:	fRunAfter(system_time() + delay)
44 {
45 }
46 
47 DelayedTask::~DelayedTask()
48 {
49 }
50 
51 OneShotDelayedTask::OneShotDelayedTask(FunctionObject* functor,
52 	bigtime_t delay)
53 	:	DelayedTask(delay),
54 		fFunctor(functor)
55 {
56 }
57 
58 
59 OneShotDelayedTask::~OneShotDelayedTask()
60 {
61 	delete fFunctor;
62 }
63 
64 
65 bool
66 OneShotDelayedTask::RunIfNeeded(bigtime_t currentTime)
67 {
68 	if (currentTime < fRunAfter)
69 		return false;
70 
71 	(*fFunctor)();
72 	return true;
73 }
74 
75 
76 PeriodicDelayedTask::PeriodicDelayedTask(
77 	FunctionObjectWithResult<bool>* functor, bigtime_t initialDelay,
78 	bigtime_t period)
79 	:	DelayedTask(initialDelay),
80 		fPeriod(period),
81 		fFunctor(functor)
82 {
83 }
84 
85 
86 PeriodicDelayedTask::~PeriodicDelayedTask()
87 {
88 	delete fFunctor;
89 }
90 
91 
92 bool
93 PeriodicDelayedTask::RunIfNeeded(bigtime_t currentTime)
94 {
95 	if (!currentTime < fRunAfter)
96 		return false;
97 
98 	fRunAfter = currentTime + fPeriod;
99 	(*fFunctor)();
100 	return fFunctor->Result();
101 }
102 
103 
104 PeriodicDelayedTaskWithTimeout::PeriodicDelayedTaskWithTimeout(
105 	FunctionObjectWithResult<bool>* functor, bigtime_t initialDelay,
106 	bigtime_t period, bigtime_t timeout)
107 	:	PeriodicDelayedTask(functor, initialDelay, period),
108 		fTimeoutAfter(system_time() + timeout)
109 {
110 }
111 
112 
113 bool
114 PeriodicDelayedTaskWithTimeout::RunIfNeeded(bigtime_t currentTime)
115 {
116 	if (currentTime < fRunAfter)
117 		return false;
118 
119 	fRunAfter = currentTime + fPeriod;
120 	(*fFunctor)();
121 	if (fFunctor->Result())
122 		return true;
123 
124 	// if call didn't terminate the task yet, check if timeout is due
125 	return currentTime > fTimeoutAfter;
126 }
127 
128 
129 RunWhenIdleTask::RunWhenIdleTask(FunctionObjectWithResult<bool>* functor,
130 	bigtime_t initialDelay, bigtime_t idleFor, bigtime_t heartBeat)
131 	:	PeriodicDelayedTask(functor, initialDelay, heartBeat),
132 		fIdleFor(idleFor),
133 		fState(kInitialDelay)
134 {
135 }
136 
137 
138 RunWhenIdleTask::~RunWhenIdleTask()
139 {
140 }
141 
142 
143 bool
144 RunWhenIdleTask::RunIfNeeded(bigtime_t currentTime)
145 {
146 	if (currentTime < fRunAfter)
147 		return false;
148 
149 	fRunAfter = currentTime + fPeriod;
150 	// PRINT(("runWhenIdle: runAfter %Ld, current time %Ld, period %Ld\n",
151 	//	fRunAfter, currentTime, fPeriod));
152 
153 	if (fState == kInitialDelay) {
154 //		PRINT(("run when idle task - past intial delay\n"));
155 		ResetIdleTimer(currentTime);
156 	} else if (fState == kInIdleState && !StillIdle(currentTime)) {
157 		fState = kInitialIdleWait;
158 		ResetIdleTimer(currentTime);
159 	} else if (fState != kInitialIdleWait || IdleTimerExpired(currentTime)) {
160 		fState = kInIdleState;
161 		(*fFunctor)();
162 		return fFunctor->Result();
163 	}
164 	return false;
165 }
166 
167 
168 static bigtime_t
169 ActivityLevel()
170 {
171 	// stolen from roster server
172 	bigtime_t time = 0;
173 	system_info	sinfo;
174 	get_system_info(&sinfo);
175 	for (int32 index = 0; index < sinfo.cpu_count; index++)
176 		time += sinfo.cpu_infos[index].active_time;
177 	return time / ((bigtime_t) sinfo.cpu_count);
178 }
179 
180 
181 void
182 RunWhenIdleTask::ResetIdleTimer(bigtime_t currentTime)
183 {
184 	fActivityLevel = ActivityLevel();
185 	fActivityLevelStart = currentTime;
186 	fLastCPUTooBusyTime = currentTime;
187 	fState = kInitialIdleWait;
188 }
189 
190 const float kTaskOverhead = 0.01f;
191 	// this should really be specified by the task itself
192 const float kIdleTreshold = 0.15f;
193 
194 bool
195 RunWhenIdleTask::IsIdle(bigtime_t currentTime, float taskOverhead)
196 {
197 	bigtime_t currentActivityLevel = ActivityLevel();
198 	float load = (float)(currentActivityLevel - fActivityLevel)
199 		/ (float)(currentTime - fActivityLevelStart);
200 
201 	fActivityLevel = currentActivityLevel;
202 	fActivityLevelStart = currentTime;
203 
204 	load -= taskOverhead;
205 
206 	bool idle = true;
207 
208 	if (load > kIdleTreshold) {
209 //		PRINT(("not idle enough %f\n", load));
210 		idle = false;
211 	} else if ((currentTime - fLastCPUTooBusyTime) < fIdleFor
212 		|| idle_time() < fIdleFor) {
213 //		PRINT(("load %f, not idle long enough %Ld, %Ld\n", load,
214 //			currentTime - fLastCPUTooBusyTime,
215 //			idle_time()));
216 		idle = false;
217 	}
218 
219 #if xDEBUG
220 	else
221 		PRINT(("load %f, idle for %Ld sec, go\n", load,
222 			(currentTime - fLastCPUTooBusyTime) / 1000000));
223 #endif
224 
225 	return idle;
226 }
227 
228 
229 bool
230 RunWhenIdleTask::IdleTimerExpired(bigtime_t currentTime)
231 {
232 	return IsIdle(currentTime, 0);
233 }
234 
235 
236 bool
237 RunWhenIdleTask::StillIdle(bigtime_t currentTime)
238 {
239 	return IsIdle(currentTime, kIdleTreshold);
240 }
241 
242 
243 TaskLoop::TaskLoop(bigtime_t heartBeat)
244 	:	fTaskList(10, true),
245 		fHeartBeat(heartBeat)
246 {
247 }
248 
249 
250 TaskLoop::~TaskLoop()
251 {
252 }
253 
254 
255 void
256 TaskLoop::RunLater(DelayedTask* task)
257 {
258 	AddTask(task);
259 }
260 
261 
262 void
263 TaskLoop::RunLater(FunctionObject* functor, bigtime_t delay)
264 {
265 	RunLater(new OneShotDelayedTask(functor, delay));
266 }
267 
268 
269 void
270 TaskLoop::RunLater(FunctionObjectWithResult<bool>* functor,
271 	bigtime_t delay, bigtime_t period)
272 {
273 	RunLater(new PeriodicDelayedTask(functor, delay, period));
274 }
275 
276 
277 void
278 TaskLoop::RunLater(FunctionObjectWithResult<bool>* functor, bigtime_t delay,
279 	bigtime_t period, bigtime_t timeout)
280 {
281 	RunLater(new PeriodicDelayedTaskWithTimeout(functor, delay, period,
282 		timeout));
283 }
284 
285 
286 void
287 TaskLoop::RunWhenIdle(FunctionObjectWithResult<bool>* functor,
288 	bigtime_t initialDelay, bigtime_t idleTime, bigtime_t heartBeat)
289 {
290 	RunLater(new RunWhenIdleTask(functor, initialDelay, idleTime, heartBeat));
291 }
292 
293 
294 class AccumulatedOneShotDelayedTask : public OneShotDelayedTask {
295 	// supports accumulating functors
296 public:
297 	AccumulatedOneShotDelayedTask(AccumulatingFunctionObject* functor,
298 		bigtime_t delay, bigtime_t maxAccumulatingTime = 0,
299 		int32 maxAccumulateCount = 0)
300 		:	OneShotDelayedTask(functor, delay),
301 			maxAccumulateCount(maxAccumulateCount),
302 			accumulateCount(1),
303 			maxAccumulatingTime(maxAccumulatingTime),
304 			initialTime(system_time())
305 		{}
306 
307 	bool CanAccumulate(const AccumulatingFunctionObject* accumulateThis) const
308 		{
309 			if (maxAccumulateCount && accumulateCount > maxAccumulateCount)
310 				// don't accumulate if too may accumulated already
311 				return false;
312 
313 			if (maxAccumulatingTime && system_time() > initialTime
314 					+ maxAccumulatingTime) {
315 				// don't accumulate if too late past initial task
316 				return false;
317 			}
318 
319 			return static_cast<AccumulatingFunctionObject*>(fFunctor)->
320 				CanAccumulate(accumulateThis);
321 		}
322 
323 	virtual void Accumulate(AccumulatingFunctionObject* accumulateThis,
324 		bigtime_t delay)
325 		{
326 			fRunAfter = system_time() + delay;
327 				// reset fRunAfter
328 			accumulateCount++;
329 			static_cast<AccumulatingFunctionObject*>(fFunctor)->
330 				Accumulate(accumulateThis);
331 		}
332 
333 private:
334 	int32 maxAccumulateCount;
335 	int32 accumulateCount;
336 	bigtime_t maxAccumulatingTime;
337 	bigtime_t initialTime;
338 };
339 
340 void
341 TaskLoop::AccumulatedRunLater(AccumulatingFunctionObject* functor,
342 	bigtime_t delay, bigtime_t maxAccumulatingTime, int32 maxAccumulateCount)
343 {
344 	AutoLock<BLocker> autoLock(&fLock);
345 	if (!autoLock.IsLocked()) {
346 		return;
347 	}
348 	int32 count = fTaskList.CountItems();
349 	for (int32 index = 0; index < count; index++) {
350 		AccumulatedOneShotDelayedTask* task
351 			= dynamic_cast<AccumulatedOneShotDelayedTask*>(
352 				fTaskList.ItemAt(index));
353 		if (!task)
354 			continue;
355 
356 		if (task->CanAccumulate(functor)) {
357 			task->Accumulate(functor, delay);
358 			return;
359 		}
360 	}
361 	RunLater(new AccumulatedOneShotDelayedTask(functor, delay,
362 		maxAccumulatingTime, maxAccumulateCount));
363 }
364 
365 
366 bool
367 TaskLoop::Pulse()
368 {
369 	ASSERT(fLock.IsLocked());
370 
371 	int32 count = fTaskList.CountItems();
372 	if (count > 0) {
373 		bigtime_t currentTime = system_time();
374 		for (int32 index = 0; index < count; ) {
375 			DelayedTask* task = fTaskList.ItemAt(index);
376 			// give every task a try
377 			if (task->RunIfNeeded(currentTime)) {
378 				// if done, remove from list
379 				RemoveTask(task);
380 				count--;
381 			} else
382 				index++;
383 		}
384 	}
385 	return count == 0 && !KeepPulsingWhenEmpty();
386 }
387 
388 const bigtime_t kInfinity = B_INFINITE_TIMEOUT;
389 
390 bigtime_t
391 TaskLoop::LatestRunTime() const
392 {
393 	ASSERT(fLock.IsLocked());
394 	bigtime_t result = kInfinity;
395 
396 #if xDEBUG
397 	DelayedTask* nextTask = 0;
398 #endif
399 	int32 count = fTaskList.CountItems();
400 	for (int32 index = 0; index < count; index++) {
401 		bigtime_t runAfter = fTaskList.ItemAt(index)->RunAfterTime();
402 		if (runAfter < result) {
403 			result = runAfter;
404 
405 #if xDEBUG
406 			nextTask = fTaskList.ItemAt(index);
407 #endif
408 		}
409 	}
410 
411 
412 #if xDEBUG
413 	if (nextTask)
414 		PRINT(("latestRunTime : next task %s\n", typeid(*nextTask).name));
415 	else
416 		PRINT(("latestRunTime : no next task\n"));
417 #endif
418 
419 	return result;
420 }
421 
422 
423 void
424 TaskLoop::RemoveTask(DelayedTask* task)
425 {
426 	ASSERT(fLock.IsLocked());
427 	// remove the task
428 	fTaskList.RemoveItem(task);
429 }
430 
431 void
432 TaskLoop::AddTask(DelayedTask* task)
433 {
434 	AutoLock<BLocker> autoLock(&fLock);
435 	if (!autoLock.IsLocked()) {
436 		delete task;
437 		return;
438 	}
439 
440 	fTaskList.AddItem(task);
441 	StartPulsingIfNeeded();
442 }
443 
444 
445 StandAloneTaskLoop::StandAloneTaskLoop(bool keepThread, bigtime_t heartBeat)
446 	:	TaskLoop(heartBeat),
447 		fNeedToQuit(false),
448 		fScanThread(-1),
449 		fKeepThread(keepThread)
450 {
451 }
452 
453 
454 StandAloneTaskLoop::~StandAloneTaskLoop()
455 {
456 	fLock.Lock();
457 	fNeedToQuit = true;
458 	bool easyOut = (fScanThread == -1);
459 	fLock.Unlock();
460 
461 	if (!easyOut)
462 		for (int32 timeout = 10000; ; timeout--) {
463 			// use a 10 sec timeout value in case the spawned
464 			// thread is stuck somewhere
465 
466 			if (!timeout) {
467 				PRINT(("StandAloneTaskLoop timed out, quitting abruptly"));
468 				break;
469 			}
470 
471 			bool done;
472 
473 			fLock.Lock();
474 			done = (fScanThread == -1);
475 			fLock.Unlock();
476 			if (done)
477 				break;
478 
479 			snooze(1000);
480 		}
481 }
482 
483 void
484 StandAloneTaskLoop::StartPulsingIfNeeded()
485 {
486 	ASSERT(fLock.IsLocked());
487 	if (fScanThread < 0) {
488 		// no loop thread yet, spawn one
489 		fScanThread = spawn_thread(StandAloneTaskLoop::RunBinder,
490 			"TrackerTaskLoop", B_LOW_PRIORITY, this);
491 		resume_thread(fScanThread);
492 	}
493 }
494 
495 bool
496 StandAloneTaskLoop::KeepPulsingWhenEmpty() const
497 {
498 	return fKeepThread;
499 }
500 
501 status_t
502 StandAloneTaskLoop::RunBinder(void* castToThis)
503 {
504 	StandAloneTaskLoop* self = (StandAloneTaskLoop*)castToThis;
505 	self->Run();
506 	return B_OK;
507 }
508 
509 void
510 StandAloneTaskLoop::Run()
511 {
512 	for(;;) {
513 		AutoLock<BLocker> autoLock(&fLock);
514 		if (!autoLock)
515 			return;
516 
517 		if (fNeedToQuit) {
518 			// task loop being deleted, let go of the thread allowing the
519 			// to go through deletion
520 			fScanThread = -1;
521 			return;
522 		}
523 
524 		if (Pulse()) {
525 			fScanThread = -1;
526 			return;
527 		}
528 
529 		// figure out when to run next by checking out when the different
530 		// tasks wan't to be woken up, snooze until a little bit before that
531 		// time
532 		bigtime_t now = system_time();
533 		bigtime_t latestRunTime = LatestRunTime() - 1000;
534 		bigtime_t afterHeartBeatTime = now + fHeartBeat;
535 		bigtime_t snoozeTill = latestRunTime < afterHeartBeatTime ?
536 			latestRunTime : afterHeartBeatTime;
537 
538 		autoLock.Unlock();
539 
540 		if (snoozeTill > now)
541 			snooze_until(snoozeTill, B_SYSTEM_TIMEBASE);
542 		else
543 			snooze(1000);
544 	}
545 }
546 
547 void
548 StandAloneTaskLoop::AddTask(DelayedTask* delayedTask)
549 {
550 	_inherited::AddTask(delayedTask);
551 	if (fScanThread < 0)
552 		return;
553 
554 	// wake up the loop thread if it is asleep
555 	thread_info info;
556 	get_thread_info(fScanThread, &info);
557 	if (info.state == B_THREAD_ASLEEP) {
558 		suspend_thread(fScanThread);
559 		snooze(1000);	// snooze because BeBook sez so
560 		resume_thread(fScanThread);
561 	}
562 }
563 
564 PiggybackTaskLoop::PiggybackTaskLoop(bigtime_t heartBeat)
565 	:	TaskLoop(heartBeat),
566 		fNextHeartBeatTime(0),
567 		fPulseMe(false)
568 {
569 }
570 
571 
572 PiggybackTaskLoop::~PiggybackTaskLoop()
573 {
574 }
575 
576 void
577 PiggybackTaskLoop::PulseMe()
578 {
579 	if (!fPulseMe)
580 		return;
581 
582 	bigtime_t time = system_time();
583 	if (fNextHeartBeatTime < time) {
584 		AutoLock<BLocker> autoLock(&fLock);
585 		if (Pulse())
586 			fPulseMe = false;
587 		fNextHeartBeatTime = time + fHeartBeat;
588 	}
589 }
590 
591 bool
592 PiggybackTaskLoop::KeepPulsingWhenEmpty() const
593 {
594 	return false;
595 }
596 
597 void
598 PiggybackTaskLoop::StartPulsingIfNeeded()
599 {
600 	fPulseMe = true;
601 }
602