CS452 - Real-Time Programming - Winter 2017

Lecture 22 - Anthropomorphic Programming

Public Service Annoucements

  1. Train Control I demo on Tuesday, 7 March.
  2. The exam will start at 12.30 on 6 April, 2017, and end at 20 April, 2017.


How to give a demo.

You are in charge; don't forget it.

General: stay in control.

Don't leave dead air.

There are two of you.

Plan what you are going to do and who will do it.

When you are planning what to say try thinking about these questions.

Make sure that the things you are going to show actually work.

Keep a copy of an executable that was working before you start the next debugging step. (This can profitably be said more than once!)

How to get back in control

Sorted by amount of force applied, least first.

Inhale loudly.

Start setting up the next demo and explaining it.

If nothing else work talk over the person who is speaking. I will respond to you.

We exclude other students from your demo to remove one distraction that makes staying in charge harder.


Anthropomorphic Programming II

Servers and Attendant Tasks

The initialization code shown below is only one of many different was of initializating a cooperating set of tasks. The principle is that we make the code so that modifiying the trask structure within the set requires no modification elsewhere in an application.

1. Proprietor with a Notifier: Initialization

Proprietor

	not-tid = Create( notifier );
	Send( not-tid,  ... );
	RegisterAs( ... );
  
When Send returns Notifier is ready.

Notifier

	Receive( &proprietor-tid, ... );
	// When completely initialized
	Reply( proprietor-tid );
  

2. Using a Courier: Initialization

Proprietor

    courier-tid = Create( courier );
    notifier-tid = Create( notifier );
    Send( courier-tid, notifier-tid, ... );
    RegisterAs( "SerialIO" );
  

Courier

	Receive ( &proprietor-tid, ... );
	Send( notifier-tid, ... );
	Reply( proprietor-tid );
  

Notifier

	Receive( &courier-tid, ... );
	Reply( courier-tid );
  

This maintains the structure of the task set in the owner.

3. The Dealer

Extend as above.

4. Secretary, Receptionist

The warehouse guards the notifier from the effect of multiple clients on the server. Another method of guarding the notifier is to guard the proprietor so that its sendQ is always empty. The secretary/receptionist does so.

The secretary receives requests from many clients and forwards them one at a time to the proprietor. Because the secretary Receives it cannot Send. It communicates with the Proprietor using a Courier.

Notes

  1. When all the clients have been given service
  2. The Courier is almost always Reply-blocked. If it spends a significant amount of time Receive-blocked the system is overloaded.
  3. If there is a temporary overload of Client requests, the Courier may sit for a while in the Secretary's SendQ while the Proprietor continues to interact with the Notifier.
  4. If there is a permanent overload of requests you are in trouble.

Initialization

Proprietor

	receptionist-tid = Create( receptionist );
	courier-tid = Create( courier );
	Send( courier-tid, receptionist-tid );
	Send( receptionist-tid, ... );
  

Receptionist

	Receive( &proprietor-tid, ... )
	Reply( proprietor-tid, ... );
	RegisterAs( "SerialIO" );
  

Courier

	Receive( &proprietor-tid, receptionist-tid );
	Reply( proprietor-tid, ... );
  

5. Administrator, Worker

Administrator is a proprietor who does no work but only assigns work to others

Where do the workers come from?

Most workers prefer employee status to consultant status.

Worker code

	Send( administrator, nil, workOrder );
  	FOREVER {
    	  workResult = doWork( workOrder );
    	  Send( administrator, workResult, workOrder );
  	}
  

doWork might require further sends to servers or warehouses, which is harmless in this context.

What kind of work might a worker do?

Administrator code

Initialization

	for ( i = 0; i < NUM_WORKERS; i++ ) {
		worker[i] = Create( mypriority - n, workerCode );
		Receive( &requester, ...);
		enqueue( employeeQ, requester );
	}
  

Work

	FOREVER {
   		Receive( requester, request );
    		switch( request.type ) {
    		case CLIENT:
      			enqueue( orderQ, {order = {client, request}} );
      			if ( !empty( employeeQ ) )
				Reply( dequeue( employeeQ ) ), dequeue( orderQ ) ); 
      			break;
    		case WORKER:
      			enqueue( emploeeQ, requester);
      			if ( !empty( orderQ ) )
				Reply( dequeue( employeeQ ), { dequeue( orderQ ) );
      			if ( request != nil )
				Reply( request.client, request.request );
      			break;
    		}
	}
  

Note: Tid of the client is included in the workorder to the administrator does not have to maintain and search a list of jobs being done. (Administrators are by nature lazy!)

Alternative Worker/Administrator Model

  1. As above, Administrator includes Tid of the client in the order.
  2. Worker replies directly to client with result and to administrator with request for another order to process.

Comments

  1. The administrator can add a little more value.

6. The Detective

Simple Events

A simple notifier is a task that waits on events.

A Courier is a task that waits on messages. A Worker is a tASK THAT waits on results.

We could call any one of these three tasks a detective,

But they are restricted to detecting things that are very simple, and our train application requires the detection of complex events.

Complex Events

Inside the server code for serial I/O we detected more complex events, especially when transmitting. For example, when transmitting TO THE TRain we needed to find the conjunction (AND) of

In doing serial I/O we made this information available within a task that was coordinating a collection of tasks, but we could just as easily have retreived the information from three separate tasks using code like the following.
	Send( detector1, event1, ... );
	Send( detector2, event2, ... );
	Send( detector3, event3, ... );
  
When this code completes all three events have occurred. The detection is easy because conjunction (AND) is easy.

Disjunction, OR, on the other hand is hard. For it we need the special properties of Receive, because it has the ability to accept a message from two or more different sources. For the detective, we think of the sources as workers.

A typical example where a detective is needed is a time out. When a train is travelling toward a sensor you have an idea of when you expect it to arrive, and you know that if it hasn't arrived by the time you must do something to rescue the train. How is this done? A detective that searches for time outs has two workers,

If the sensor trigger comes first the detective responds with "success" and ignores the delay detector. If the delay comes first, the detective responds with "time out" and ignores the sensor report. ("Ignores" means that the the message is received and ignored.)

How does a time-out detective work?

Not

We can say that an event has not happened yet.

Only at the end of the universe can we say that an event simply has not happened.

Time-outs are needed for NOT: how to do them is shown above.

Who is the client of the detective

  1. Initiator of a normal action

7. Housekeeper

Housekeeper of the system who will detect, and possibly clean up pathologies.

8. The Grim Reaper

Most often pathologies are handled by destroying one or more tasks, the recreating them. Destroying is usually done in the context of programming conventions. One convention followed here is that when tasks work together as a unit task creation is done internally with one master task directly or indirectly creating all tasks in the unit. Then

Resources must be returned for reuse. Some are easy.

Others are hard. Suppose a task owns a piece of track. It must be given back or your project won't survive very much task destruction. How do we keep the time for this process bounded above?


Return to: