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