ColdFrame: Active Classes

Background

A task is an independent thread of execution within a program. Tasks are supported by the operating system in use, which may well have a different name for the concept (VxWorks uses task, Linux uses thread, Solaris uses light-weight process).

Desktop operating systems like Windows and Linux have an additional concept process. Typically, processes don't share memory but can contain multiple threads/tasks, which do share process memory (and other resources, such as file handles).

VxWorks only has tasks.

Some programming languages provide tasking (concurrency) constructs as part of the language - for example, Ada, Java, C#. If your language doesn't have inbuilt support, you can call operating system primitives (for example, the VxWorks task creation operation is taskSpawn) or rely on support libraries such as pthreads.

With multiple CPUs, under an operating system which can use them (for example, an SMP-aware Linux), the program's tasks can be truly independent, so that more than one is running at any instant. Of course, there are still likely to be many fewer CPUs than tasks! The normal situation is to have one CPU per computer, shared between the tasks.

This might seem to be adding complexity, and indeed it does bring its own difficulties for programmers (especially in getting tasks to share data safely).

A key benefit is that it's much easier to deal with a multiply concurrent problem if the separate aspects can be designed independently. If the computer has to receive LAN updates from a radar system at the same time as initializing a different comms device using a complex multi-step protocol, it's as well if the two concerns can be kept separate (including, if possible, implementation by different teams, which ColdFrame supports by domains).

Motivation

Aside from the tasks which are semi-incidentally involved with event handling, tasks are needed for two purposes:

Blocking input-output

If your application has to wait for something to arrive from the outside (for example, for an Ethernet message from another part of the system), the natural way to program it is to use a blocking read.

One example would be calling GNAT.Sockets.Accept_Socket, which blocks until a client (probably in another processor) chooses to call Connect_Socket.

It would be quite incorrect to make these calls within ColdFrame's normal event processing, because the event queue concerned would be effectively stopped until the blocking call completed. They need to be made from distinct tasks.

Hard real time responses

Two (related) approaches to managing multiple concurrent jobs which have to meet deadlines are Rate Monotonic Analysis (RMA) and Deadline Monotonic Analysis (DMA).

Both are concerned mainly with periodic or cyclic jobs; for example, driving a tracker radar to point at its target at 64 Hz, where you mustn't miss the correct time to output the demands because you'll lose the target (or perhaps rattle the tracker to pieces!)

Both rely on preemptive scheduling (high-priority tasks forcibly suspend, or preempt, lower-priority tasks). Each job is allocated to a task (usually one job per task, but two jobs with the same scheduling requirements could be allocated to one task, other things being equal).

RMA allocates the highest priority to the task which has to run most frequently, while DMA allocates it to the task with the shortest deadline (ie, the shortest period between when it can start a cycle of processing and when it has to have completed).

Modelling

You specify a class as active by marking it with the stereotype «active».

Procedure operations (ie, those without a return value) of an active class can be stereotyped «entry»; see ARM 9.5 for a discussion on entries.

It is permitted for an «entry» operation to have the same name as an ordinary operation.

You can specify the run-time priority of the task using the tag priority. The form is

{priority = priority relative to system default}

You can specify the stack size of the task using the tag stack. The form is

{stack = stack size}

You can also specify a class as active by setting the isActive checkbox in the modifiers section of the ArgoUML Properties tab, but

  1. this is much less obvious in the model, and
  2. it doesn't allow the use of the {priority} and {stack} tags.

Translation

Each instance of an «active» class has an associated task. The task's name, if supported by the operating system, is Domain.Class.

You access the task using This.The_T; so if there is an «entry» Start, you would call it as This.The_T.Start.

If a «protected» type's «entry» operation's visibility is public or protected, it is implemented as a public entry; otherwise as a private entry. You would use this for requeuing.

The visibility of task entries is always public (but only to the class in which it's defined).

Use

Accessing instance attributes and calling operations

The task is constrained by This : access Instance, so within the task you can quite often treat This as if it were a Handle. However, it isn't quite, so if the compiler complains use a view conversion:

Handle (This)

Deleting the instance

The generated Delete operation of an «active» class aborts the task before freeing the task and instance memory. This means that if you call Delete from the task you are pulling the rug from under your feet!

What's needed is another task. The nearest candidate is the task in the event queue for the domain; so the canonical solution is to post an event to the event queue which will Delete the instance when it fires.

Some examples of this idiom have posted the event to run after a delay. This should not be necessary.

To avoid the need for a special state machine, a «class event» should be used (if there can be more than one instance, it will need a parameter of type Class, translating to Handle, to indicate which instance is to be deleted).

State machines

If an active class has a state machine, you have to be careful about instance initialization.


Simon Wright