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