Introduction
This installment is entirely dedicated to a central BREW design pattern; I’ve called it “Beware of the Watchdog.” Maybe this is the most significant distinction between BREW and other mobile platforms (a notable exception being C++ Blackberry): for the sake of simplicity, BREW doesn’t hide all the details of the operating system; developers are forced to take additional responsibility. After describing the problem, the article presents a C++ idiom designed to solve the problem in an automatic way.
Beware of the Watchdog
Problem:
- In BREW programming, it is very important not to use a busy loop or poll for data
Context:
- The Rex OS uses a watchdog to check the health of the running processes (threads). The watchdog has to be “petted” periodically to signal a “healthy” state.
Forces:
- A linear, synchronous programming model is easier to understand and use.
- But, BREW offers a centrally scheduled, asynchronous, event-based model.
- On mobile, cooperative multitasking systems, tasks have to avoid tight loops but there is no real constraint (specifically time constraints) in yielding.
- In BREW, a developer’s responsibility is to pet the watchdog constantly (forced yield model).
Solution [1]:
- Periodically yield control to the system during long operations such as tight loops
- The time spent inside the loop should be at most one cycle of the watchdog
- Use non-blocking calls or techniques.
Example:
Let’s start with a very simple example, in pseudo-code:
while(whileBody ()) {} doSomethingElse();
This is everyday programming, based on some well-established assumptions and expectations:
- whileBody() returns false or throws during execution.
- After exiting, the loop control is passed to the next instruction (doSomethingElse()), in a linear fashion.
But, look what happens when moving from our well-behaved platform to BREW. First of all, the initial code might fail miserably; the long loop triggers the watchdog and a reset is guaranteed. The next try might be something like this pseudo-code:
long time = getCurrentTime(); while(whileBody ()) { if ((getCurrentTime()-time )> watchdog_cycle) yield(); } doSomethingElse();
Since BREW 2.1, is possible to use the IThread API to mimic blocking calls, but this is not always the easiest or the most convenient way—especially when all you want to do is to iterate through a container, for example!
BREW way is asynchronous:
- Register a callback, yield control back to BREW, and you’ll be called later (can use Set|Timer or Resume)
- Or post an event and yield control back to BREW (PostEvent)
Note: You always have to “yield control back to BREW.” Basically, this means that you have to explicitly exit your code:
void fun() long time = getCurrentTime(); while(whileBody ()) { if ((getCurrentTime()-time )> watchdog_cycle) { registerCallback(fun); return; } } doSomethingElse(); }
Looks simple enough. Unfortunately, this is not the case:
- whileBody has to internally keep its state
- Replacing <while> with <for> adds additional problems because the state of the iterator also has to be maintained
- Maintaining state is not trivial as static variables are not allowed
- fun() has to have a certain signature; otherwise, it cannot be used as a callback
One C++ idiom, presented in the accompanying code, is to package the loops separately, resulting in a usage pattern like this:
//get the executor from a factory TASK* t = getTaskInstance<UserClass>(& UserClass:: whileBody, & UserClass:: doSomethingElse); //sanity check if (!t) return; //set parameters t->setLoop(); // t->setLoop(stop, start, step); //run the show t->run();
Because every task prepackages a loop, there are actually two types of tasks, corresponding to <for> and <while>. Of course, it makes sense to offer them as singletons [2], like this:
template <class C > static TASK_TYPE* getTaskInstance(TASK_TYPE::EXECUTION_FUN tf, TASK_TYPE::COMPLETION_FUN cf) { typedef TASK_TYPE::PARENT_TYPE P; P * c = getInstance< P >(); if (c) { if (c->task_==0) c->task_ = new TASK_TYPE (getInstance<C>(), tf, cf); return c->task_; } return 0; }
A task is defined as:
typedef Task<ParentClass, ClassToBeProcessed, TaskPolicy> TASK_TYPE;
A parent class P is assumed, whose type can be deduced from TASK_TYPE. <task> is a singleton and as such is a member of P [2]. The class used for tests is a singleton too, but this is not required by the framework; the only requirement is to be capable of maintaining its internal test for <while> loops (and absolutely no requirements for <for>).
TaskPolicy is actually a time policy and defines the maximum time to be spent in the loop:
struct TaskPolicy { static long getDurationLimit() { return 500; //millisecs - } };
ClassToBeProcessed has to expose a completion member function, always of type:
typedef void (ClassToBeProcessed::*COMPLETION_FUN)();
and a body member function:
// for loops typedef void (ClassToBeProcessed::*EXECUTION_FUN)(int); // while loops typedef bool (ClassToBeProcessed::*EXECUTION_FUN)(int);
The int parameter in EXECUTION_FUN is the actual index of the loop, passed as a convenience back to the caller.
Anatomy of a Task
template <class M> struct Task { typedef M PARENT_TYPE; void run() { ISHELL_Resume (M::getInstance<M>()->m_pIShell, &cbk_); } protected: Task() {} ~Task() { CALLBACK_Cancel(&cbk_); } void init(PFNNOTIFY pfn) { CALLBACK_Cancel(&cbk_); CALLBACK_Init(&cbk_, pfn, this); cbk_.pfnCancel = 0; } private: AEECallback cbk_; private: Task(const Task&); Task& operator=(const Task&); }; template <class M, class T, class P> struct Task4 : public Task<M> { typedef void (T::*COMPLETION_FUN)(); typedef void (T::*EXECUTION_FUN)(int); Task4(T* t, EXECUTION_FUN pfn, COMPLETION_FUN cfn): t_(t), pfn_(pfn), cfn_(cfn), start_(0), stop_(0), step_(0), duration_(0) { } void setLoop(int stop, int start=0, int step=1) { start_ = start; stop_ = stop; step_ = step; init((PFNNOTIFY)execTskImpl); } private: static void execTskImpl(Task4* t) { T* a = t->t_; t->duration_ = GETUPTIMEMS(); for (int i = t->start_; i < t->stop_; i+=t->step_) { (*a.*(t->pfn_))(i); long duration = GETUPTIMEMS() - t->duration_; if (duration>P::getDurationLimit()) { t->run(); t->start_=i; return; } } t->start_ = t->start_; (*a.*(t->cfn_))(); } private: int start_; int stop_; int step_; long duration_; T* t_; EXECUTION_FUN pfn_; COMPLETION_FUN cfn_; private: Task4(const Task4&); Task4& operator=(const Task4&); }; template <class M, class T, class P> struct TaskW : public Task<M> { typedef void (T::*COMPLETION_FUN)(); typedef bool (T::*EXECUTION_FUN)(int); TaskW(T* t, EXECUTION_FUN pfn, COMPLETION_FUN cfn): t_(t), pfn_(pfn), cfn_(cfn), duration_(0) { } void setLoop() { i_ = 0; init((PFNNOTIFY)execTskImpl); } private: static void execTskImpl(TaskW* t) { T* a = t->t_; t->duration_ = GETUPTIMEMS(); while( (*a.*(t->pfn_))(t->i_++) ) { long duration = GETUPTIMEMS() - t->duration_; if (duration>P::getDurationLimit()) { t->run(); return; } } (*a.*(t->cfn_))(); } private: long duration_; T* t_; int i_; EXECUTION_FUN pfn_; COMPLETION_FUN cfn_; private: TaskW(const TaskW&); TaskW& operator=(const TaskW&); };
<Task> encapsulates the BREW concepts: IShell::Resume() and the CALLBACK family of helper functions.
<Task> is further specialized in <Task4> (masquerading <for> loops) and <TaskW> (<while>). The actual specialization is done in the callback method:
static void execTskImpl();
This is a Test class that uses the above presented concepts:
struct Test { Test() : stop_(1000) {} void forBody(int idx) { Writer writer; char ch[12]; SPRINTF(ch, "%ld", idx); writer.DisplayOutput(2,ch); } void completionRoutine() { Writer writer; writer.WriteLine("DONE"); } bool whileBody(int idx) { Writer writer; char ch[12]; SPRINTF(ch, "%ld", idx); writer.DisplayOutput(3,ch); return !(idx == stop_); } private: int stop_; };
Resulting Context:
- Safe, simplified, and uniform loop programming
- No direct interaction with the callback mechanism
- Watchdog-related problems were eliminated
- But, the syntax is awkward and rigid
The Code
Download the accompany code file here.
References
- Cooperative Multitasking in BREW: A Possible Solution http://developer.com/ws/brew/article.php/1497121
- Discovering C++ Idioms in BREW http://18.220.208.18/ws/brew/article.php/3355691