TASK(3C++)

NAME
task - coroutines, multiple threads of control, C++ task library

SYNOPSIS
#include <task.h>
typedef int (*PFIO)(int,object*);
typedef void (*PFV)();
extern	int _hwm;
class object {
public:
	enum objtype { OBJECT, TIMER, TASK, QHEAD, QTAIL, INTHANDLER };
	object*		o_next;
			object();
			~object();
	void		alert();
	void		forget(task*);
	virtual objtype	o_type();
	virtual int	pending();
	virtual	void	print(int, int =0);
	void		remember(task*);
	static int	task_error(int, object*);
	int		task_error(int);
	static task*	this_task();
	static	PFIO	error_fct;
};
class sched : public object {
protected:
			sched();
public:
	enum statetype { IDLE=1, RUNNING=2, TERMINATED=4 };
	static	task*	clock_task;
	void		cancel(int);
	int		dont_wait();
	static long	get_clock();
	sched*		get_priority_sched();
	static sched*	get_run_chain();
	static int	get_exit_status();
	int		keep_waiting();
	int		pending();
	void		print(int, int =0);
	statetype	rdstate();
	long		rdtime();
	int		result();
	void		setclock(long);
	static void	set_exit_status(int);
	virtual void	setwho(object*);
	static	PFV	exit_fct;
};
#define	DEFAULT_MODE	DEDICATED
class task : public sched {
public:
	enum modetype { DEDICATED=1, SHARED=2 };
protected:
			task(char* name=0, modetype mode=DEFAULT_MODE, int stacksize=SIZE);
public:
	task*		t_next;
	char*		t_name;
			~task();
	void		cancel(int);
	void		delay(long);
	static task*	get_task_chain();
	objtype		o_type();
	long		preempt();
	void		print(int, int =0);
	void		resultis(int);
	void		setwho(object*);
	void		sleep(object* =0);
	void		wait(object*);
	int		waitlist(object* ...);
	int		waitvec(object**);
	object*		who_alerted_me();
};
class timer : public sched {
public:
			timer(long);
			~timer();
	objtype		o_type();
	void		print(int, int =0);
	void		reset(long);
	void		setwho(object*);
};

DESCRIPTION

A task is an object with an associated program and thread of control. To use the task system, the programmer must derive a class from class task, and supply a constructor for the derived class to serve as the task's main program. (Note, however, that only one level of derivation is permitted from class task. See the BUGS section.)

Control in the task system is based on a concept of operations which may succeed immediately or be blocked, and objects which may be ready or pending (not ready). When a task executes a blocking operation on an object that is ready, the operation succeeds immediately and the task continues running, but if the object is pending, the task waits. Control then returns to the scheduler, which chooses the next task from the ready list or run chain. Eventually, the pending object may become ready, and it will notify all the tasks that are waiting for it, causing the waiting tasks to be put back on the run chain .

A task can be in one of three states:

RUNNING
    The task is running or it is ready to run.

IDLE
    The task is waiting for a pending object.

TERMINATED
    The task has completed its work. It cannot be resumed, but its result can be retrieved.

The function sched::rdstate() returns the state. These states are enumerations of type statetype. These enumerations are in the scope of class sched.

Most classes in the task system are derived from class object. Each different kind of object can have its own way of determining whether it is ready, which makes it easy to add new capabilities to the system. However, each kind of object can have only one criterion for readiness (although it may have several blocking operations). The criterion for readiness is defined by the virtual function pending(). For all classes derived from object, pending() returns TRUE if the object is not ready. This invariant should be maintained for user-defined derived classes as well.

Each pending object contains a list (the remember chain) of the tasks that are waiting for it. When a task attempts an operation on a pending object (that is, it calls a blocking function), that task is put on the remember chain for the object via object::remember(), and the task is suspended. When the state of an object changes from pending to ready, object::alert() must be called for the object. (Note, this is done for classes in the task system. Programmers who write classes for which tasks can wait, must ensure that object::alert() is called on a state change.) alert() changes the state of all tasks "remembered" by the object from IDLE to RUNNING and puts them on the scheduler's run chain.

The base class, sched, is responsible for scheduling and for the functionality that is common to tasks and timers. Class sched can only be used as a base class, that is, it is illegal to create objects of class sched. Class sched also provides facilities for measuring simulated time. A unit of simulated time can represent any amount of real time, and it is possible to compute without consuming simulated time. The system clock is initialized to 0 and can be set with sched::setclock() once only. Thereafter, only a call to task::delay() will cause the clock to advance. sched::getclock() can be used to read the clock.

Class timer provides a facility for implementing time-outs and other time-dependent phenomena. A timer is similar to a task with a constructor consisting of the single statement:
    delay(d);
That is, when a timer is created it simply waits for the number of time units given to it as its argument, and then wakes up any tasks waiting for it. A timer's state can be either RUNNING or TERMINATED.

A task cannot return a value with the usual function return mechanism. Instead, a task sets the value of its result (using task::resultis() or task::cancel()), at which time the task becomes TERMINATED. Then this result can be retrieved by other tasks via a call to sched::result().

The task constructor takes three optional arguments: a name, a mode, and a stacksize. The name is a character string pointer, which is used to initialize the class task variable t_name. This name can be used to provide more readable output and does not affect the behavior of the task.

The mode argument can be DEDICATED (the default when none is specified) or SHARED, (the enumerations of type modetype in class task's scope). DEDICATED tasks each have their own stack, allocated from the free store. SHARED tasks share stack space with the task that creates them. When a SHARED task is running, it occupies the shared stack space, while copies of the active portions of the other tasks' stacks occupy save areas. SHARED tasks trade speed for space: they use less storage than DEDICATED tasks use, but task switches among SHARED tasks often involve copying stacks to and from the save area.

The stacksize argument to the task constructor represents the maximum space that a task's stack can occupy. The default is 750 machine words. Overflowing the stack is a fatal error.

When an object of a class derived from class task is created, its constructor becomes a new task that runs in parallel with the other tasks that have been created. When the first task is created, main() automatically becomes a task itself.

The public member functions supplied in the task system classes task, object, sched, and timer are listed and described in the next four sections. The following symbols are used:

    t   a task object

    o   an object object

    s   a sched object

    tm  a timer object

    op  a pointer to an object

    tp  a pointer to a task

    sp  a pointer to a sched

    cp  a pointer to a char

    i, j
        ints

    l   a long int

    eo  an objtype enumeration

    es  a statetype enumeration

    em  a modetype enumeration

  Class Task

Class task has one form of constructor, which is protected:

task t( cp, em, j )
    Constructs a task object, t. All three arguments are optional and have default values. If cp is given, the character string it points to is used as t's name. em represents the mode (see above), and can be DEDICATED or SHARED. DEDICATED is the default. The default mode can be changed to SHARED by recompiling the task library with _SHARED_ONLY defined. See the NOTES section. j represents the maximum size of t's stack; the default is 750 machine words.

Most public member functions of class task are conditional or unconditional requests for suspension. They are (in alphabetical order):

t.cancel( i )
    Puts t into the TERMINATED state, without suspending the calling task (that is, without invoking the scheduler), and sets t's result (or "return value") to i.

t.delay( l )
    Suspends t for the time specified by l. A delayed task is in the RUNNING state. t will resume at the current time on the task system clock + l. Only a call to delay() causes the clock to advance.

    tp = task::get_task_chain()
    tp = t.get_task_chain()
    Returns a pointer to the first task on the list of all tasks (linked by t_next pointers).

eo = t.o_type()
    Returns the class type of t (object::TASK). o_type() is a virtual function.

l = t.preempt()
    Suspends RUNNING task t, making it IDLE. Returns the number of time units left in t's delay. Calling preempt() for an IDLE or TERMINATED task causes a runtime error.

t.print( i )
    Prints the contents of t on stdout. The first argument, i, specifies the amount of information to be printed. It can be 0, for the minimum amount of information, VERBOSE, for more information, CHAIN, for information about each object on the chain of all tasks, or STACK, for information about the runtime stack. These argument constants can be combined with the or operator, e.g., print(VERBOSE|CHAIN). A second integer argument is for internal use and defaults to 0. print() is a virtual function.

t.resultis( i )
    Sets the result (or "return value") of t to be the value of i and puts t in the TERMINATED state. The result can be examined by calling t.result() (result() is a member function of class sched). tasks cannot return a value using the usual function return mechanism. A call to task::resultis() should appear at the end of every task constructor body (unless the constructor will execute infinitely). A task is pending (see sched::pending() ) until it is TERMINATED.

t.setwho( op )
    Records the object denoted by op as the one that alerted t when it was IDLE. *op is meant to be the object whose state change from pending to ready caused t to be put back on the run chain. This information can be retrieved with task::who_alerted_me().

    t.sleep( op )
    t.sleep()
    Suspends t unconditionally (puts the t in the IDLE state). The op argument is optional. If task::sleep() is given a pointer to a pending object as an argument, t will be "remembered" by the denoted object, so that when that object becomes ready, t will be "alerted" and put back on the run chain (via object::alert() ). If no argument is given to task::sleep(), the event that will cause t to be resumed is unspecified. Contrast sleep() with wait(), which suspends a task conditionally. task::sleep() does not check whether the object denoted by op is pending.

t.wait( op )
    If op points to a pending object, then t will be suspended (put in the IDLE state) until that object is ready. If op points to an object that is not pending (that is ready), then t will not be suspended at all. Any class derived from class object that is ever going be waited for must have rules for when it is pending and ready. Each object can only have one definition of pending.

i = t.waitlist( op ...)
    Suspends t to wait for one of a list of objects to become ready. waitlist() takes a list of object pointers terminated by a 0 argument. If any of the arguments points to a "ready" object, then t will not be suspended at all. waitlist() returns when one of the objects on the list is ready. It returns the position in the list of the object that caused the return, with positions numbered starting from 0. Note that objects on the list other than the one denoted by the return value might also be ready.

i = t.waitvec( op* )
    Is the same as waitlist(), except that it takes as an argument the address of a vector holding a list of object pointers.

op = t.who_alerted_me()
    Returns a pointer to the object whose state change from pending to ready caused t to be put back on the run chain (put in the RUNNING state).

_hwm = 1;
    Causes the task system to keep track of the "high water mark" for each task's stack; that is, the most stack ever used by each task. This information is printed by task::print(STACK). This information is intended primarily for debugging purposes, and will affect performance speed. _hwm must be set before any tasks whose high water marks are of interest are created. Note that two tasks are created by a static constructor: the internal Interrupt_alerter task and the "main" task. If you need accurate information about the high water mark for "main," then _hwm must be set by a static constructor which is called before that for the Interrupt_alerter task.

  Class Object

Class object has one form of constructor:

object o;
    Construct an object object, o, which is not on any lists. The constructor takes no arguments.

Public member functions of class object are (in alphabetical order):

o.alert()
    Changes the state of all tasks "remembered" by o from IDLE to RUNNING, puts them on the scheduler's run chain, and removes them from o's remember chain.

o.forget( tp )
    Removes all occurrences of the task denoted by tp from o's remember chain.

eo = o.o_type()
    Returns the class type of the object, o (object::OBJECT). o_type() is a virtual function.

i = o.pending()
    Returns the ready status of an object. It returns FALSE if o is ready, and TRUE if it is pending. Classes derived from class object must define pending() if they are to be waited for. object::pending() returns TRUE by default. pending() is a virtual function.

o.print( i )
    Prints the contents of o on stdout. It is called by the print() functions for classes derived from object. See task::print() for a description of the arguments. print() is a virtual function.

o.remember( tp )
    Adds the task denoted by tp to o's remember chain. Remembered tasks will be alerted when o's state becomes ready.

    i = object::task_error( i, op )
    i = o.task_error( i, op )
    The central error function called by task system functions when a run time error occurs. i represents the error number (see the DIAGNOSTICS section for a list of error numbers and their meanings). op is meant to be a pointer to the object which called task_error() or 0. object::task_error() examines the variable error_fct, and if this variable denotes a function, that function will be called with i and op as arguments, respectively. (See error_fct, below.) Otherwise, i will be given as an argument to print_error(), which will print an error message on stderr and call exit(i), terminating the program. The non-static, single argument form of task_error() is obsolete, but remains for compatibility.

    tp = object::this_task()
    tp = o.this_task()
    Returns a pointer to the task that is currently running.

    PFIO user-defined-error-function;
    error_fct =  user-defined-error-function
    error_fct is a pointer to a function that returns an int and takes two arguments: an int representing the error number and an object* representing the object* that called task_error. If error_fct is set, task_error() will call the user-defined-error-function with the error number and the object* as arguments. (The object* will be 0 if task_error was not called by an object.) If user-defined-error-function does not return 0, task_error() will call exit(i). If the user-defined-error-function does return 0, task_error() will retry the operation that caused the error.

  Class Sched

Both class task and class timer are derived from class sched. Class sched provides one form of constructor, which is protected:

sched s;
    Constructs a sched object, s, initialized to be IDLE and to have a 0 delay.

Class sched is responsible for the functionality that is common to tasks and timers. Class sched provides the following public member functions:

s.cancel( i )
    Puts s into the TERMINATED state, without suspending the caller (that is, without invoking the scheduler), and sets the result of s to be i.

i = s.dont_wait()
    Returns the number of times keep_waiting() has been called, minus the number of times dont_wait() has been called (excluding the current call). If these functions are used as intended, the return value represents the number of objects that were waiting for external events before the current call. See keep_waiting(). See interrupt(3C++) for a description of how tasks can wait for external events.

    l = sched::get_clock()
    l = s.get_clock()
    Returns the value of the task system clock.

    i = sched::get_exit_status()
    i = s.get_exit_status()
    Returns the exit status of the task program. When a task program terminates normally (that is, task_error is not called), the program will call exit(i), where i is the value passed by the last caller of sched::set_exit_status().

sp = s.get_priority_sched()
    Returns a pointer to a system task, interrupt_alerter, if a signal that was being waited for has occurred. If no interrupt has occurred, get_priority_sched() returns 0.

    sp = sched::get_run_chain()
    sp = s.get_run_chain()
    Returns a pointer to the run chain, the linked list of ready sched objects (tasks and timers).

i = s.keep_waiting()
    Returns the number of times keep_waiting() has been called (not counting the current call), minus the number of times dont_wait() has been called. keep_waiting() is meant to be called when an object that will wait for an external event is created. For example, it is called when an Interrupt_handler object is created by the Interrupt_handler constructor (see interrupt(3C++)). The inverse function, dont_wait(), should be called when such an object is deleted. keep_waiting() causes the scheduler to keep waiting (not to exit) when there are no runnable tasks (because an external event may make an IDLE task runnable).

i = s.pending()
    Returns FALSE if s (task or timer) is in the TERMINATED state, TRUE otherwise. pending() is a virtual function.

s.print( i )
    Prints the contents of s on stdout. It is called by the print() functions for classes derived from sched. See task::print() and timer::print() for a description of the arguments. print() is a virtual function.

es = s.rdstate()
    Returns the state of s: RUNNING, IDLE, or TERMINATED.

l = s.rdtime()
    Returns the clock time at which s is to run.

i = s.result()
    Returns the result of s (as set by task::resultis(), task::cancel(), or sched::cancel() ). If s is not yet TERMINATED, the calling task will be suspended to wait for s to terminate. If a task calls result() for itself, it will cause a run time error.

    sched::setclock( l )
    s.setclock( l )
    Initializes the system clock to the time given by l. Causes a run time error if used more than once.

    sched::set_exit_status( i )
    s.set_exit_status( i )
    Sets the exit status of the task program. When a task program terminates normally (that is, task_error is not called), the program will call exit(i), where i is the value passed by the last caller of set_exit_status().

s.setwho( op )
    Is a virtual function defined for tasks and timers; see its definition for those classes. The argument is meant to be a pointer to the object that caused s to be alerted.
    PFV user-defined-exit-function;
    exit_fct = user-defined-exit-function
    exit_fct is a pointer to a function taking no arguments and returning void. If set, the task system scheduler will call the user-defined-exit-function before the program exits.

clock_task = tp;
    Sets tp to be a task that will be scheduled each time the system clock advances, before any other tasks. The clock_task must be IDLE when it is resumed by the scheduler. The clock_task can suspend itself by calling task::sleep() to ensure this.

  Class Timer

Class timer provides one form of constructor:

timer tm( l );
    Constructs a timer object, tm, and inserts it on the scheduler's run chain. l represents the number of time units tm is to wait.

The following public member functions are provided for timers:

eo = tm.o_type()
    Returns the class type of the object (object::TIMER). o_type() is a virtual function.

tm.reset( l )
    Resets tm's delay to l. This makes repeated use of timers possible. A timer can be reset even when it is TERMINATED.

tm.setwho( op )
    Is defined to be null for timers. setwho() is a virtual function.

tm.print( i )
    Prints the contents of tm on stdout. The argument is ignored. print() is a virtual function.

FILES
LIBDIR/libtask.a

NOTES
The task library is supplied only for the following machines: WE32000-series machines (e.g., the AT&T 3B2), AT&T 3B20, AT&T 6386 WGS, Sun-2 and Sun-3, and the VAX. It must be ported to work on other platforms.

WARNINGS
Beware of optimizing compilers that inline constructors for classes derived from class task!

Although the task library was engineered to be as free as possible from dependencies on compilation systems and dynamic call chains, it does depend on the existence of stack frames for the task constructor and constructors for classes derived from class task. If these constructors are inlined by an optimizing compiler, unpredictable behavior will result.

For related reasons, although you must derive a class from class task to use the task library, you can only have one level of derivation from class task. That is, the system will not work reliably if you derive a class from a class derived from class task.

BUGS
DEDICATED tasks are implemented by building task stacks in the free store. Because UNIX System V Release 2 (SVR2) for the WE32000-series machines does not allow stack pointers to point into the free store, DEDICATED tasks cannot be used on these machines with SVR2. In such cases, compile the task library with _SHARED_ONLY defined, which will make SHARED the default mode for tasks. (Note: it is insufficient to declare all tasks as SHARED without compiling a _SHARED_ONLY version of the task library, because there is an internal system task (the interrupt alerter task, see interrupt(3C++)) which is DEDICATED by default.)

UNIX System V Releases 3.1 and 3.2 (SVR3.1 and SVR3.2) for the Intel 386 machine will not call a signal handler when the current task is running on a stack in the free store, that is, when the current task has a DEDICATED stack. If you need to use the signal handling mechanisms (described on the tasksim(3C++) manual page) on that configuration, you cannot use tasks which have DEDICATED stacks. In this case, compile the task library with _SHARED_ONLY defined, which will make SHARED the default mode for tasks.

For implementation reasons, it is not possible to derive a class from a class derived from class task; only one level of derivation is permitted. Use of multi-level derivation is not detected, and will usually result in an unexpected core dump. One possible workaround for this limitation is to use multiple inheritance rather than multi-level derivation. For example, you can derive a class from class task and from another class; the derived class will serve as the task's main program just as it does in single inheritance.

DIAGNOSTICS
When a task system function encounters a run time error, it calls object::task_error(), with one of the following error numbers as an argument. The table below lists the run time errors the task system detects, the associated error messages, and explanations of the errors.
Error Name | Message | Explanation
1 E_OLINK | "object::delete(): has chain" | Attempt to delete an object which remembers a task.
2 E_ONEXT | "object::delete(): on chain" | Attempt to delete an object which is still on some chain.
3 E_GETEMPTY | "qhead::get(): empty" | Attempt to get from an empty queue in E_MODE.
4 E_PUTOBJ | "qtail::put(): object on other queue" | Attempt to put an object already on some queue.
5 E_PUTFULL | "qtail::put(): full" | Attempt to put to a full queue in E_MODE.
6 E_BACKOBJ | "qhead::putback(): object on other queue" | Attempt to putback an object already on some queue.
7 E_BACKFULL | "qhead::putback(): full" | Attempt to putback to a full queue in E_MODE.
8 E_SETCLOCK | "sched::setclock(): clock!=0" | Clock was non-zero when setclock() was called.
9 E_CLOCKIDLE | "sched::schedule(): clock_task not idle" | The clock_task was not IDLE when the clock was advanced.
10 E_RESTERM | "sched::schedule: terminated" | Attempt to resume a TERMINATED task.
11 E_RESRUN | "sched::schedule: running" | Attempt to resume a RUNNING task.
12 E_NEGTIME | "sched::schedule: clock<0" | Negative argument to delay().
13 E_RESOBJ | "sched::schedule: task or timer on other queue" | Attempt to resume task or timer already on some queue.
14 E_HISTO | "histogram::histogram(): bad arguments" | Bad arguments for histogram constructor.
15 E_STACK | "task::restore(): stack overflow" | Task run time stack overflow.
16 E_STORE | "new: free store exhausted" | No more free store--new() failed.
17 E_TASKMODE | "task::task(): bad mode" | Illegal mode argument for task constructor.
18 E_TASKDEL | "task::~task(): not terminated" | Attempt to delete a non-TERMINATED task.
19 E_TASKPRE | "task::preempt(): not running" | Attempt to preempt a non-RUNNING task.
Error Name | Message | Explanation
20 E_TIMERDEL | "timer::~timer(): not terminated" | Attempt to delete a non-TERMINATED timer.
21 E_SCHTIME | "schedule: bad time" | Scheduler run chain is corrupted: bad time.
22 E_SCHOBJ | "sched object used directly (not as base)" | Sched object used directly instead of as a base class.
23 E_QDEL | "queue::~queue(): not empty" | Attempt to delete a non-empty queue.
24 E_RESULT | "task::result(): thistask->result()" | A task attempted to obtain its own result().
25 E_WAIT | "task::wait(): wait for self" | A task attempted to wait() for itself to TERMINATE.
26 E_FUNCS | "FrameLayout::FrameLayout(): function start" | Internal error--cannot determine the call frame layout.
27 E_FRAMES | "FrameLayout::FrameLayout(): frame size" | Internal error--cannot determine frame size.
28 E_REGMASK | "task::fudge_return(): unexpected register mask" | Internal error--unexpected register mask.
29 E_FUDGE_SIZE | "task::fudge_return(): frame too big" | Internal error--fudged frame too big.
30 E_NO_HNDLR | "sigFunc - no handler for signal" | No handler for the generated signal.
31 E_BADSIG | "illegal signal number" | Attempt to use a signal number that is out of range.
32 E_LOSTHNDLR | "Interrupt_handler::~Interrupt_handler(): |
 | signal handler not on chain" |

SEE ALSO
TASK.INTRO(3C++), interrupt(3C++), queue(3C++), tasksim(3C++)
Stroustrup, B. and Shopiro, J. E., "A Set of C++ Classes for Co-routine Style Programming," in AT&T C++ Language System Release 2.0 Library Manual.
Shopiro, J. E., "Extending the C++ Task System for Real-Time Control," in AT&T C++ Language System Release 2.0 Library Manual.
Keenan, S. A., "A Porting Guide for the C++ Coroutine Library," in AT&T C++ Language System Release 2.0 Library Manual.