CSP for Java
(JCSP) 1.0-rc4

jcsp.lang
Class ProcessManager

java.lang.Object
  |
  +--jcsp.lang.ProcessManager
All Implemented Interfaces:
CSProcess

public class ProcessManager
extends Object
implements CSProcess

This enables a CSProcess to be spawned concurrently with the process doing the spawning.

Shortcut to the Constructor and Method Summaries.

Description

The ProcessManager class enables a CSProcess to be spawned concurrently with the process doing the spawning. The class provides methods to manage the spawned process: start, suspend, resume, join and stop. The spawned process may, of course, be a Parallel network of processes to any depth of nesting, in which case the whole network comes under this management.

Spawning processes is not the normal way of creating a network in JCSP - the normal method is to use the Parallel class. However, when we need to add processes in response to some run-time event, this capability is very useful.

Note that the methods in ProcessManager mirror those in java.lang.Thread (or, more accurately, those in java.lang.ThreadGroup). Those java.lang classes deprecate their suspend, resume and stop methods because of race-hazard and deadlock problems. We must be aware of the same dangers that await their careless use - see the warning below. Some security managers (e.g. in certain web browsers supporting applets) will not allow our current implementation of these methods (which uses ThreadGroups) to work - see the implementation note below. The stopOK() method lets us know if that is the case.

For completeness, ProcessManager is itself a CSProcess - running a ProcessManager simply runs the process it is managing.

Examples

Spawning a CSProcess

This first example demonstrates that the managed CSProcess is executed concurrently with the spawning process and that it dies when its manager terminates. The managed process is `infinite' and just counts and chatters.
 import jcsp.lang.*;
 
 public class ProcessManagerExample1 {
 
   public static void main (String[] argv) {
 
     final ProcessManager manager = new ProcessManager (
       new CSProcess () {
         public void run () {
           final CSTimer tim = new CSTimer ();
           long timeout = tim.read ();
           int count = 0;
           while (true) {
             System.out.println (count + " :-) managed process running ...");
             count++;
             timeout += 100;
             tim.after (timeout);   // every 1/10th of a second ...
           }
         }
       }
     );
 
     final CSTimer tim = new CSTimer ();
     long timeout = tim.read ();
 
     System.out.println ("\n\n\t\t\t\t\t*** start the managed process");
     manager.start ();
 
     for (int i = 0; i < 10; i++) {
       System.out.println ("\n\n\t\t\t\t\t*** I'm still executing as well");
       timeout += 1000;
       tim.after (timeout);         // every second ...
     }
 
     System.out.println ("\n\n\t\t\t\t\t*** I'm finishing now!");
 
   }
 
 }
 

Managing a CSProcess

This example demonstrates hot management of the spawned CSProcess. This is a slightly dangerous activity, but we get away with it on this occasion. However, please see the warning below.
 import jcsp.lang.*;
 
 public class ProcessManagerExample2 {
 
   public static void main (String[] argv) {
 
     final ProcessManager manager = new ProcessManager (
       new CSProcess () {
         public void run () {
           final CSTimer tim = new CSTimer ();
           long timeout = tim.read ();
           int count = 0;
           while (true) {
             System.out.println (count + " :-) managed process running ...");
             count++;
             timeout += 100;
             tim.after (timeout);   // every 1/10th of a second ...
           }
         }
       }
     );
 
     System.out.println ("\n\n\t\t\t\t\t*** start the managed process");
     manager.start ();
 
     final CSTimer tim = new CSTimer ();
     long timeout = tim.read ();
 
     for (int i = 0; i < 5; i++) {
       timeout += 2000;
       tim.after (timeout);         // after 2 seconds ...
       System.out.println (
         "\n\n\t\t\t\t\t*** suspend the managed process (" + i + "/" + 4 + ")"
       );
       manager.suspend ();
       timeout += 2000;
       tim.after (timeout);         // after 2 more seconds ...
       manager.resume ();
     }
 
     timeout += 2000;
     tim.after (timeout);           // after 2 more seconds ...
     System.out.println ("\n\n\t\t\t\t\t*** stop the managed process");
     manager.stop ();
     System.out.println ("\n\n\t\t\t\t\t*** and finish myself!");
 
   }
 
 }
 
In this example, the stop is redundant since the manager thread is about to terminate. In general, however, that thread may have other things to do.

Warning

ProcessManager is (almost) to CSProcess as Thread is to Runnable. In the above examples, if we exchange ProcessManager for Thread and Runnable for CSProcess, we will get the same execution behaviour. But the Thread class deprecates its suspend, resume and stop methods and we need to know why.

Whenever we work with a component of a parallel system, we need to know not just what computations it performs, but also how it interacts with the other components in that system. In CSP, processes interact by synchronising on shared events (such as channel communication) and one of its main achievements is a mathematically precise way of specifying that behaviour.

In the standard Java thread/monitor model, synchronisation is achieved through the invocation of synchronized methods (or blocks) and through the wait/notify mechanisms inherited from the Object class. However, such synchronisations are rarely spelt out in the documentation of classes that indulge in it. Reinforcing this, the javadoc tool in the latest JDK (version 1.2) has dropped the automatic documenting of synchronized from methods so declared. This has a reasonable excuse, since javadoc never reported any synchronized blocks in the body of a method, nor any synchronisations resulting from object methods invoked during method execution. All this information is needed by the user of a component of a parallel system and ought to be included in its formal, or natural language, specification. Failure to keep on top of this causes problems (often impossibly intermittent) of deadlock, livelock, process starvation and/or race-hazard.

A Nasty Deadlock

In the second of the above examples, ProcessManagerExample2, if we were innocently to add another print statement to report the impending resume:
       .
       .
       .
       System.out.println (
         "\n\n\t\t\t\t\t*** suspend the managed process (" + i + "/" + 4 + ")"
       );
       manager.suspend ();
       timeout += 2000;
       tim.after (timeout);         // after 2 more seconds ...
       System.out.println (
         "\n\n\t\t\t\t\t*** resume the managed process (" + i + "/" + 4 + ")"
       );
       manager.resume ();
       .
       .
       .
 
the program would possibly, although not certainly, deadlock the first time that the new print statement tried to execute. If we were lucky, we would get through the whole sequence of 5 suspensions/resumptions successfully. But deadlock would now pose an ever present threat to each run.

The problem is that the println methods of java.io.PrintStream synchronise on the PrintStream object - in this case, System.out. The managed process is in a printing loop and, therefore, continually laying claim to the monitor lock of System.out. If the manager process suspends the managed one whilst it has this lock (a definite possibility with the above code), then the manager had better not try any printing until resuming that managed process. Otherwise, the manager will block as it also tries to acquire the System.out lock and will be in no position to resume the managed process - which is the only way that the lock can get released. Deadlock!

The real problem is that println does not document its synchronisation on System.out. Although this is fairly obvious given what it has to do, it is not a danger that springs to mind when adding a print statement! To discover this synchronisation, we have to browse its source code (which is not something we always want or are able to do). [See also the note in Any2OneCallChannel.]

So, care must be taken when suspending a process. Obviously we must not attempt to interact directly with a suspended process (e.g. by a committed input or output to it over a channel). Less obviously, we must not interact with any resource that a suspended process may have acquired. To be aware of such pitfalls, a process should document all such interactions. Otherwise, we end up with examples where adding a print statement renders a system liable to deadlock.

Once we know about such interactions, action can be taken to guarantee deadlock-freedom. In the above case, all we need do is acquire and hold the System.out before suspending the managed process:

       synchronize (System.out) {
         manager.suspend ();
       }
 
Now, the suspension will only take place when the managed process does not have the lock. Any added print statements are now safe.

Stopping Race-Hazards

Stopping a process releases any locks it (or any sub-process) may be holding, so there is not the same danger of deadlock. Of course, we are assuming that nobody attempts a committed interaction afterwards. However, if the stopped process were in the middle of some synchronised transaction, the data update may be incomplete depending on the precise moment of the stopping. This is a race-hazard.

To avoid this, a managed process should only be stopped if we know it is in a state where it is not interacting with its environment - or, as in the above example, we do not care about such spoilt data. To know that it is in a stoppable state, the manager ought to listen for a message (e.g. channel communication) from the process that it is ready to be stopped. [An example of such cooperation between managed and manager processes is given in the Hamming demonstration (see jcsp-demos.hamming).]

If the managed process is purely serial, there is not much point in the above trick, since it could simply terminate. However, having a manager stop a complex process network means that the network does not have to make its own provision for termination. The latter can add considerable algorithmic complexity, often to the detriment of the clarity of individual process logic.

Stopping a network by setting a global volatile flag that each process polls from time to time (the first suggestion in the documentation of the deprecated stop method in java.lang.Thread) is not, in general, safe. For example, a thread blocked on a monitor wait will remain blocked if the thread that was going to notify it spots the shut-down flag and terminates. The JDK1.2 documentation describes some work-arounds, but they are not simple and depend in part on the application logic.

For JCSP processes, there is a general solution to this [`Graceful Termination and Graceful Resetting', P.H.Welch, Proceedings of OUG-10, pp. 310-317, Ed. A.W.P.Bakkers, IOS Press (Amsterdam), ISBN 90 5199 011 1, April, 1989], based on the careful distribution of poison over the network's normal communication channels. Future versions of JCSP may take account of this.

Implementation Note

ProcessManager creates a java.lang.ThreadGroup in which to run its managed process and any sub-processes. Some security managers (e.g. in certain web browsers supporting applets) will not allow a Thread to be added to a ThreadGroup and, consequently, the suspend(), resume(), and stop() methods will not work. In this case, no exceptions are raised if the methods are invoked - just nothing happens. Whether this is the case can be discovered by invoking the stopOK() method.

This may have an impact in the simple management control implemented for the ActiveApplet class. However, no problems occur if Sun's Java Plug-in is used in the web browser.

Author:
P.H.Welch and P.D.Austin
See Also:
CSProcess, Parallel, ActiveApplet

Constructor Summary
ProcessManager(CSProcess proc)
           
 
Method Summary
 void join()
          Join the managed process (that is wait for it to terminate).
 void resume()
          Resume the managed process.
 void run()
          Run the managed process (that is start it and wait for it to terminate).
 void start()
          Start the managed process (but keep running ourselves).
 void stop()
          Stop (permanently) the managed process.
 boolean stopOK()
          Returns whether suspend(), resume() and stop() are supported.
 void suspend()
          Suspend the managed process.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Constructor Detail

ProcessManager

public ProcessManager(CSProcess proc)
Parameters:
proc - the CSProcess to be executed by this ProcessManager
Method Detail

start

public void start()
Start the managed process (but keep running ourselves).

stop

public void stop()
Stop (permanently) the managed process.

suspend

public void suspend()
Suspend the managed process.

resume

public void resume()
Resume the managed process.

join

public void join()
Join the managed process (that is wait for it to terminate).

stopOK

public boolean stopOK()
Returns whether suspend(), resume() and stop() are supported.
Returns:
true iff suspend(), resume() and stop() are supported.

run

public void run()
Run the managed process (that is start it and wait for it to terminate). Note: this is the same as just running the process directly.
Specified by:
run in interface CSProcess

CSP for Java
(JCSP) 1.0-rc4

Submit a bug or feature to jcsp-team@ukc.ac.uk
Version 1.0-rc4 of the JCSP API Specification (Copyright 1997-2000 P.D.Austin and P.H.Welch - All Rights Reserved)
Java is a trademark or registered trademark of Sun Microsystems, Inc. in the US and other countries.