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
42DelayedTask::DelayedTask(bigtime_t delay)
43	:	fRunAfter(system_time() + delay)
44{
45}
46
47DelayedTask::~DelayedTask()
48{
49}
50
51OneShotDelayedTask::OneShotDelayedTask(FunctionObject* functor,
52	bigtime_t delay)
53	:	DelayedTask(delay),
54		fFunctor(functor)
55{
56}
57
58
59OneShotDelayedTask::~OneShotDelayedTask()
60{
61	delete fFunctor;
62}
63
64
65bool
66OneShotDelayedTask::RunIfNeeded(bigtime_t currentTime)
67{
68	if (currentTime < fRunAfter)
69		return false;
70
71	(*fFunctor)();
72	return true;
73}
74
75
76PeriodicDelayedTask::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
86PeriodicDelayedTask::~PeriodicDelayedTask()
87{
88	delete fFunctor;
89}
90
91
92bool
93PeriodicDelayedTask::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
104PeriodicDelayedTaskWithTimeout::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
113bool
114PeriodicDelayedTaskWithTimeout::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
129RunWhenIdleTask::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
138RunWhenIdleTask::~RunWhenIdleTask()
139{
140}
141
142
143bool
144RunWhenIdleTask::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
168static bigtime_t
169ActivityLevel()
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
181void
182RunWhenIdleTask::ResetIdleTimer(bigtime_t currentTime)
183{
184	fActivityLevel = ActivityLevel();
185	fActivityLevelStart = currentTime;
186	fLastCPUTooBusyTime = currentTime;
187	fState = kInitialIdleWait;
188}
189
190const float kTaskOverhead = 0.01f;
191	// this should really be specified by the task itself
192const float kIdleTreshold = 0.15f;
193
194bool
195RunWhenIdleTask::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
229bool
230RunWhenIdleTask::IdleTimerExpired(bigtime_t currentTime)
231{
232	return IsIdle(currentTime, 0);
233}
234
235
236bool
237RunWhenIdleTask::StillIdle(bigtime_t currentTime)
238{
239	return IsIdle(currentTime, kIdleTreshold);
240}
241
242
243TaskLoop::TaskLoop(bigtime_t heartBeat)
244	:	fTaskList(10, true),
245		fHeartBeat(heartBeat)
246{
247}
248
249
250TaskLoop::~TaskLoop()
251{
252}
253
254
255void
256TaskLoop::RunLater(DelayedTask* task)
257{
258	AddTask(task);
259}
260
261
262void
263TaskLoop::RunLater(FunctionObject* functor, bigtime_t delay)
264{
265	RunLater(new OneShotDelayedTask(functor, delay));
266}
267
268
269void
270TaskLoop::RunLater(FunctionObjectWithResult<bool>* functor,
271	bigtime_t delay, bigtime_t period)
272{
273	RunLater(new PeriodicDelayedTask(functor, delay, period));
274}
275
276
277void
278TaskLoop::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
286void
287TaskLoop::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
294class AccumulatedOneShotDelayedTask : public OneShotDelayedTask {
295	// supports accumulating functors
296public:
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
333private:
334	int32 maxAccumulateCount;
335	int32 accumulateCount;
336	bigtime_t maxAccumulatingTime;
337	bigtime_t initialTime;
338};
339
340void
341TaskLoop::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
366bool
367TaskLoop::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
388const bigtime_t kInfinity = B_INFINITE_TIMEOUT;
389
390bigtime_t
391TaskLoop::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
423void
424TaskLoop::RemoveTask(DelayedTask* task)
425{
426	ASSERT(fLock.IsLocked());
427	// remove the task
428	fTaskList.RemoveItem(task);
429}
430
431void
432TaskLoop::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
445StandAloneTaskLoop::StandAloneTaskLoop(bool keepThread, bigtime_t heartBeat)
446	:	TaskLoop(heartBeat),
447		fNeedToQuit(false),
448		fScanThread(-1),
449		fKeepThread(keepThread)
450{
451}
452
453
454StandAloneTaskLoop::~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
483void
484StandAloneTaskLoop::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
495bool
496StandAloneTaskLoop::KeepPulsingWhenEmpty() const
497{
498	return fKeepThread;
499}
500
501status_t
502StandAloneTaskLoop::RunBinder(void* castToThis)
503{
504	StandAloneTaskLoop* self = (StandAloneTaskLoop*)castToThis;
505	self->Run();
506	return B_OK;
507}
508
509void
510StandAloneTaskLoop::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
547void
548StandAloneTaskLoop::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
564PiggybackTaskLoop::PiggybackTaskLoop(bigtime_t heartBeat)
565	:	TaskLoop(heartBeat),
566		fNextHeartBeatTime(0),
567		fPulseMe(false)
568{
569}
570
571
572PiggybackTaskLoop::~PiggybackTaskLoop()
573{
574}
575
576void
577PiggybackTaskLoop::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
591bool
592PiggybackTaskLoop::KeepPulsingWhenEmpty() const
593{
594	return false;
595}
596
597void
598PiggybackTaskLoop::StartPulsingIfNeeded()
599{
600	fPulseMe = true;
601}
602