#!/usr/bin/perl

# server for PerlDSM

use IO::Socket;
use IO::Select;

# globals:

$ListenSocket;  # listening socket, to accept new connections
$Sock;  # individual socket
$Slct;  # object used in the "select" operation
$NumNodes;  # number of application nodes
$NumNodesLeft;  # number of application nodes still connected
$NextNode = 0;  # ID number to be assigned to the next node to check in
$Debug;  # true if we are in debug mode
%SharedVars;  # hash for the shared variables and their values
@LockQueue;  # queue of sockets waiting for the lock
@Barrier = (0,0);  # 2-element array for 2-phase barrier
$BarrParity = 0;  # records which element of @Barrier we're using now
@BarrQueue;  # queue of sockets waiting for the barrier

# set up network access
InitNet();

# get command-line arguments
$NumNodes = $ARGV[1];
$NumNodesLeft = $NumNodes;
$Debug = ($ARGV[2] eq "d");

# accept connections from all the application nodes
foreach $I (1..$NumNodes)  {
   $Sock = $ListenSocket->accept();
   if ($Debug)  {
      print "accepted socket $$Sock\n";
   }
   $Slct->add($Sock);
}

# in each iteration of the loop, find all the sockets that have data to
# read, place those sockets in the array @Sockets, and read from them

while (@Sockets = $Slct->can_read())  {
   # read from each socket that is ready
   for $Sock (@Sockets)  {
      $Line = <$Sock>;
      if ($Debug)  {
         print "from socket $$Sock:  ", $Line;
      }
      ($Op,$VarName,$Value) = split(" ", $Line);
      if ($Op eq "create")  {
         Create($VarName);
      }
      elsif ($Op eq "read")  {
         Read($VarName);
      }
      elsif ($Op eq "write")  {
         Write($VarName,$Value)
      }
      elsif ($Op eq "close")  {
         Close();
      }
      elsif ($Op eq "checkin")  {
         CheckIn();
      }
   }
}

sub InitNet {
   $| = 1;   # turn buffering off
   my $Port = $ARGV[0];  # get port number
   $ListenSocket = IO::Socket::INET->new(Proto=>'tcp', LocalPort=>$Port,
      Listen=>1, Reuse=>1) || die $!;
   # set up object for "select" operation
   $Slct = IO::Select->new($ListenSocket);
}

sub CheckIn {
   my $TheirNode = $NextNode++;
   print $Sock $NumNodes, " ", $TheirNode, "\n";
}

sub Create {
   $VarName = shift;
   if (defined($SharedVars{$VarName}))  {
      print $Sock "established previously, OK\n";
      if ($Debug)  {
         print "$VarName established previously, OK\n";
      }
   }
   else  {
      $SharedVars{$VarName} = -1;
      print $Sock "OK\n";
      if ($Debug)  {
         print "$VarName created\n";
      }
   }
}

sub Read {
   $VarName = shift;
   if ($VarName eq '$LOCK')  {
      Lock();
   }
   elsif ($VarName eq '$BARR')  {
      DoBarrier();
   }
   else  {
      print $Sock $SharedVars{$VarName}, "\n";
   }
}

sub Write {
   $VarName = shift;
   if ($VarName eq '$LOCK')  {
      Unlock();
   }
   else  {
      $Value = shift;
      $SharedVars{$VarName} = $Value;
      print $Sock "OK\n";
   }
}

sub Lock {
   if ($SharedVars{'$LOCK'} == 0 ||
       $SharedVars{'$LOCK'} == -1)  {
      $SharedVars{'$LOCK'} = 1;
      print $Sock "go ahead\n";
   }
   else  {
      push(@LockQueue,$Sock);
      if ($Debug)  {
         print "pushing socket $$Sock onto lock queue\n";
      }
   }
}

sub Unlock() {
   $SharedVars{'$LOCK'} = 0;
   print $Sock "unlock completed\n";
   if (scalar(@LockQueue) > 0)  {   
      $WaitingSock = shift(@LockQueue);
      $SharedVars{'$LOCK'} = 1;
      if ($Debug)  {
         print "shifting socket $$WaitingSock out of lock queue\n";
      }
      print $WaitingSock "lock free; go ahead\n";
   }
}

sub DoBarrier {
   $Barrier[$BarrParity]++;
   if ($Barrier[$BarrParity] == $NumNodes)  {
      $Barrier[$BarrParity] = 0;
      $BarrParity = 1 - $BarrParity;
      foreach $I (1..$NumNodes-1)  {
         $WaitingSock = shift(@BarrQueue);
         print $WaitingSock "barrier reached by all; go ahead\n";
      }
      print $Sock "barrier reached by all; go ahead\n";
   }
   else  {
      push(@BarrQueue,$Sock);
   }
}

sub Close {
   $Slct->remove($Sock);
   close $Sock;
   $NumNodesLeft--;
   if ($NumNodesLeft == 0)  {
      exit;
   }
}

