CS452 - Real-Time Programming - Winter 2018

Lecture 27 - Pathologies II.

Public Service Annoucements

  1. Train Control II demo on Thursday, 22 March.
  2. Lecture menu:
  3. The exam will start at 09.00, April 22, 2018 and finish at 11.30, 23 April 2018.


Pathologies II

As we go down this list both pathology detection and the length of the edit-compile-test cycle grow without bound.

1. Deadlock

One or more tasks will never run again.

One train trying to go into a siding; a second train trying to get out of the siding. Requests queued within server. Notice that you need explicit code to get you out of this.

1. Deadlock

One or more tasks will never run again. For example

  1. Task sends to itself (local: rest of system keeps running, task itself will never run)
  2. Every task does Receive( ) (global: nothing is running, except possibly the idle task; all tasks are SEND_BL)
  3. Cycle of tasks sending around the cycle. All tasks in the cycle are RCV_BL. (local: other tasks keep running)
  4. One train is on a siding trying to get out; another train is on the switch at the end of the siding trying to get in. (external: application okay but the world is in an unanticipated configuration. "Unanticipated" means that no code was implemented to deal with this case.)

Kernel can detect the first three; only the train application can detect the fourth.

Potential deadlock can be detected at compile time

2. Livelock (Deadly Embrace)

Definition

Two or more tasks are READY. For each task, the state of other tasks prevents progress being made regardless of which task is ACTIVE.

A higher level of coordination is required.

There are two types of livelock

  1. Ones that are the result of bad coding
  2. Ones that are inherent in the application definition

Livelock usually occurs in the context of resource contention

Livelock that's Really Deadlock

Solutions

  1. Make a single compound resourse, BUT
  2. Impose a global order on resource requests that all clients must follow.
  3. Create a mega-server that handles all resource requests

Real Livelock

Proprietor1 & proprietor2 fail the requests

Livelock that's Really a Critical Race

We could try to make the clients a little more considerate

    while ( no resources ) {
      Send( prop1, getres1, result );
      while ( result == "sorry" ) {
         Delay( ... );
         Send( prop1, getres1, result );
      }
      Send( prop2, getres2, result );
      while ( result == "sorry" ) {
        Send( prop1, relres1, ... );
        Delay( ... );
      }
    }
  
or even more considerate
    while ( true ) {
      Send( prop1, getres1, result );
      while ( result == "sorry" ) {
         Delay( ... );
         Send( prop1, getres1, result );
      }
      Send( prop2, getres2, result );
      if ( result == "sorry" ) {
        Send( prop1, relres1, ... );
      } else {
	break;
      }
      Delay( ... );
      }
    }
  
This we call a critical race because avoiding what is effectively an infinite loop depends on the timing of execution.

How quickly code like this escapes from the critical race depends on the argument you give to Delay(...).
If it's the same constant, which is common because both clients are running the same code, the delay can persist for a long time.
If it's random, and long enough to re-order the execution order of the code, then the deadlock will not long persist.

Inherent Livelock

Remember the example where two trains come face to face, each waiting for the other to move. They will wait facing each other until the demo is over, probably polling.

What's hard about solving this problem?

In real life, human-human conversations, which are very versatile, solve the problem. In your program

What's most easy for you to do is to programme each driver with

  1. detection, e.g.,
  2. work around, e.g.,

3. Critical Races

Example

  1. Two tasks, A & B, at the same priority
  2. A is doing a lot of debugging IO
  3. B always reserves a section of track before A, and all is fine.
  4. Debugging IO is removed
  5. A reserves the section before B can get it, and execution collapses.
  6. Lower priority of A to the same level as C.
  7. Now C executes faster and gets a resource before D .
  8. You shuffle priorities forever, eventually reverting to put back in the debugging IO.

Definition

The order in which computation is done is an important factor in determining whether or not it is successful. Without knowing it you have created a program the correctness of which is execution order dependent.

Critical races, like Livelock can be

Symptoms

  1. Small changes in priorities change execution unpredictably, and drastically.
  2. Debugging output changes execution drastically.
  3. Changes in train speeds change execution drastically. How do you tell this apart from a bad calibration? (Your application knows where the train is and it doesn't help.)

Solutions

Critical races are very hard to debug. Often depending only on the order of input they occur in all programs that interact asynchronously with an environment, which include distributed programs, games, web content, programs with user interfaces such as word processors and spreadsheets.

The suggestions that follow are not actually solutions, only suggestions

  1. Design algorithms that work for any order of inputs. Servers (Receive( )) help with this sort of design.
  2. Explicit synchronization
  3. Gating is a technique of global synchronization which can be provided by a detective


Return to: