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