SOURCE FILE: robotShell.cpp


/*

In this version of the program, The mother creates a specific
number of robots.  Each is randomly assigned a direction of
travel.  After creating the robots, the mother blocks "forever"
by waiting on a semaphore that has been initialized to 0.

Each robot remains alive and repeatedly crosses the hall in its
assigned direction.  In other words, after crossing the hall each
time, it is in effect "re-born" back at its starting point,
whereupon it starts another crossing.  It is as if the robot
exited, and at the same time the mother created a new robot to
take its place.

You have to do ctrl-C to stop this program, else it runs
"forever".

The delays in this particular version of the program allow the
program to deadlock easily if no protocol is added to arbitrate
between north- and south-bound robots. That is undoubtedly what
will happen if you just compile and run this program.


Suppose a protocol is added that assures 

1. only one direction of traffic at a time is allowed in the hall, 
2. the (text book) progress requirement holds, 
3. the (text book) bounded waiting requirement does *not* hold.

In that case the delays here are likely to starve south-bound
robots.

Therefore the delays here should be quite useful for
troubleshooting several stages of the development of an advanced
protocol for synchronizing the robots.

Nonetheless, the programmer assigned to adding a synchronizing
protocol should experiment with a variety of configurations of
delay in order to fully test the protocols.  In my testing of
your program for the purpose of assigning a grade, I may very
well alter the delays to see if I can "break" your code.

*/

#include <iostream.h>
#include <stdlib.h>
#include <sched.h>
#include <time.h>
#include <pthread.h>
#include "sem.h"

#define NORTH 0
#define SOUTH 1
#define CHILD 0
#define MOTHER 1

#define NUM_CELLS 16
#define NUM_ROBOTS 25

extern sim_semaphore create_sim_sem(int) ;
extern void wait_sem (sim_semaphore) ;
extern void signal_sem (sim_semaphore) ;
extern long random(void);

	/* WHEN DEBUGGING, WE NEED TO USE THIS LOCK TO GET
	   INTELLIGIBLE PRINTOUTS OF WHAT THE VARIOUS THREADS ARE
	   DOING.  WARNING:  DON'T CHANGE HOW THIS IS DONE UNTIL
	   YOU'VE THOUGHT A *WHOLE* LOT ABOUT THE CONSEQUENCES.
	   */
pthread_mutex_t stdoutLock ;

int checking ;   /* JUST FOR DEBUGGING */

		/* GLOBAL VARIABLE TO REPRESENT THREADS CREATED
		   DYNAMICALLY.  */
pthread_t thr[NUM_ROBOTS] ; 

sim_semaphore blockMother ; /* SOMETHING TO FORCE THE MOTHER TO
			       BLOCK AFTER CREATING THE CHILD
			       THREADS (ROBOTS). */

		/* SEMAPHORES TO GUARD THE CELL DIVISIONS OF THE
		   CORRIDOR */
sim_semaphore hall_cell[NUM_CELLS] ; 

/* .. ADD THE OTHER DECLARATIONS YOU MAY NEED HERE .. */

/* ################################################## */
/*                         init                       */
/* ################################################## */
void init() 
{ 
  int i ;

  srandom(time((time_t *) 0)); /* INITIALIZE RANDOM NUMBER
                                  GENERATOR */

  checking = 0 ; /* JUST FOR DEBUGGING: CHANGE TO 1 IF YOU WANT
		    MESSAGES. */

	          /* INITIALIZE THE SPECIAL MUTEX FOR LOCKING
		     STDOUT */

  if ( 0!=pthread_mutex_init(&stdoutLock, NULL) )
  {  cout << "MUTEX INITIALIZATION FAILURE!" << endl ;
     exit(-1) ;}

  blockMother = create_sim_sem(0) ; /* INITIALIZE "blockMother"
				       TO 0 TO FORCE A BLOCK. */

		/* INITIALIZE THE SEMAPHORES GUARDING THE CELLS
		   IN THE HALL. */
  for (i=0;i<NUM_CELLS;i++) hall_cell[i]=create_sim_sem(1);


        /* .. HERE, PLACE OTHER INITIALIZATIONS YOU MAY NEED ..
              */

}
/* ################################################## */
/*                    PrintStartMsg                   */
/* ################################################## */
void printStartMsg(int serNum, int direction)
{
  pthread_mutex_lock(&stdoutLock) ;

  if   (direction==NORTH)
       cout << "Child " << serNum << " starts north." << endl ;
  else cout << "Child " << serNum << " starts south." << endl ;

  pthread_mutex_unlock(&stdoutLock);
}
/* ################################################## */
/*                    PrintFinishMsg                  */
/* ################################################## */
void printFinishMsg(int serNum, int direction)
{
  pthread_mutex_lock(&stdoutLock) ;

  if   (direction==NORTH)
       cout << "Child " << serNum << " finishes north." << endl ;
  else cout << "Child " << serNum << " finishes south." << endl ;

  pthread_mutex_unlock(&stdoutLock);
}
/* ################################################## */
/*                    DelayAsMuchAs                   */
/* ################################################## */
void delayAsMuchAs (int limit)
{
  int time, step;
  time=(int)random()%limit;
  for (step=0;step<time;step++) sched_yield() ;
}
/* ################################################## */
/*                    SetDirection                    */
/* ################################################## */
void setDirection(int direction,
                  int & first, int  & last, int  & incr)
{
  if (direction==NORTH)
       { first=0; last=NUM_CELLS-1; incr=1; }
  else {first=NUM_CELLS-1; last=0; incr= -1;}
}
/* ################################################## */
/*                    CheckInToHall                   */
/* ################################################## */
void checkInToHall(int direction, int first, int serNum)
{

/* add synchronization actions someplace(s) here as needed.. */

  delayAsMuchAs(100);
  wait_sem(hall_cell[first]);
  printStartMsg(serNum,direction);

}
/* ################################################## */
/*                    CheckOutOfHall                  */
/* ################################################## */
void checkOutOfHall(int direction, int last, int serNum)
{

/* add synchronization actions someplace(s) here as needed.. */

  printFinishMsg(serNum, direction);
  signal_sem(hall_cell[last]);

}
/* ################################################## */
/*                   PassThroughHall                  */
/* ################################################## */

/* 

      This function simulates a robot travelling from one end of the
      hall to the other.  The values of first, last, and incr
      determine the direction of travel.  It is a precondition that
      the robot has already reserved the first cell it must travel
      through.  It then loops doing (reserve next cell ; release
      previous cell) until the last cell has been reserved.  It does
      not release the last cell.  It is assumed that the caller will
      take care of this detail after PassThroughHall returns.  The
      pattern of acquire and release simulates a walking kind of
      action.  The threads executing this protocol are synchronized up
      to a point: No two robots can be in the same cell at the same
      time.  Thus collisions are impossible.  There is no way for one
      robot to pass another robot.  This applies whether the robots
      are going in the same or different directions.

*/

void passThroughHall(int first, int last, int incr)
{
  int i;
  
  for (i=first;i!=last;i=i+incr)
  {

     /*
        This delay makes robots go thru slowly.  This has a
	tendency to keep the hall non-empty all of the time,
	unless something special is done in the protocol to cause
	the hall to "drain."
     */

    delayAsMuchAs(1000);
    wait_sem(hall_cell[i+incr]);
    signal_sem(hall_cell[i]);
  }
}
/* ################################################## */
/*                    DoRobotNorth                    */
/* ################################################## */
void * doRobotNorth(void * serNumPtr)
{
  int serNum = (int) serNumPtr ;
  int first, last, i, incr, time, step ;

  setDirection(NORTH, first, last, incr);
  while (true)
  {
    checkInToHall(NORTH, first, serNum) ;
    passThroughHall(first, last, incr);
    checkOutOfHall(NORTH, last, serNum);
  }
  pthread_exit ((void *) 0); 
}
/* ################################################## */
/*                    DoRobotSouth                    */
/* ################################################## */
void * doRobotSouth(void * serNumPtr)
{
  int serNum = (int) serNumPtr ;
  int first, last, i, incr, time, step ;

  setDirection(SOUTH, first, last, incr);
  while (true)
  {
      /* 
          The delay below makes south-bound robots slow to
	  attempt to check in to the hall.  Thus, north-bound
	  robots are likely to start using the hall first.
	  Suppose the protocol is doing something to prevent
	  traffic in both directions from occupying the hall
	  simultaneously.  In that case, once occupied by
	  north-bound robots, the hall is likely to remain
	  occupied by north-bound robots.  The reason is that
	  other delays have been added which force most robots to
	  pass through the hall very slowly.  Thus the hall is
	  not likely ever to be emptied of north-bound robots,
	  unless the protocol does something special to force the
	  hall to drain.
      */

    delayAsMuchAs(1000);
    checkInToHall(SOUTH, first, serNum) ;
    passThroughHall(first, last, incr);
    checkOutOfHall(SOUTH, last, serNum);
  }
  pthread_exit ((void *) 0); 
}
/* ################################################## */
/*                         MAIN                       */
/* ################################################## */
void main() 
{
  int serNum, direction ;

  init();

  for (serNum=0; serNum < NUM_ROBOTS; serNum++)
  {
    direction=(int)random()%2;

    if (direction == NORTH)
    {
       cout << "Creating robot #" << serNum << " ... " << endl ;
       cout << "Direction is North" << endl ;
       if    ( 0 != pthread_create
             ( &thr[serNum], NULL, doRobotNorth, (void *) serNum ) )
       { cout << "THREAD CREATION FAILURE!" << endl ; exit(-1) ; }

       if ( 0!=pthread_detach(thr[serNum]) )
       {  cout << "THREAD DETACHMENT FAILURE!" << endl ; exit(-1) ;}
    }

    else
    {
       cout << "Creating robot #" << serNum << " ... " << endl ;
       cout << "Direction is South" << endl ;
       if    ( 0 != pthread_create
             ( &thr[serNum], NULL, doRobotSouth, (void *) serNum ) )
       { cout << "THREAD CREATION FAILURE!" << endl ; exit(-1) ; }

       if ( 0!=pthread_detach(thr[serNum]) )
       {  cout << "THREAD DETACHMENT FAILURE!" << endl ; exit(-1) ;}
    }

  }

  wait_sem(blockMother);

  exit(0) ;
}