SDSU CS 696 Emerging Technologies: Java Distributed Computing
Spring Semester, 1999
Auction - A Transaction Example
Previous    Lecture Notes Index    Next    
© 1999, All Rights Reserved, SDSU & Roger Whitney
San Diego State University -- This page last updated 22-Apr-99

Contents of Doc 29, Auction - A Transaction Example


References

Jini Transaction Specification 1.0 January 25, 1999
This document is available at: http://www.sun.com/jini/specs/index.html

Jini API, Local on-line version at: http://www-rohan.sdsu.edu/doc/jini/doc/api/index.html

Doc 29, Auction - A Transaction Example Slide # 2

Bidding Example


This example is meant to be a complete Jini service. The draft version does not include activation or restarting the service.

Classes involved
AuctionInterface
Remote interface that client uses to interact with Auctioneer. Contains two methods. bid() allows the client to make a bid. earnings() returns the amount of money the auctioneer has made in all auctions.
Source Code
Auctioneer
Implements AuctionInterface. The bid phase of the auction is 30 seconds. In that phase clients can make bids. Bids are handled as transactions. The client with the highest bid wins the auction. When the bid phase is over all bids are processed. The client with the highest bid wins the auction. If the high bidder does not have the money to pay for the bid, an attempt is made to award the auction to the second highest bidder.
Source Code

AuctionClient
Each client has a bank account and a bid amount. When a client wins an auction, it reduces it bank account. The client is written to possible make a bid that it can not pay for. Almost none of the proper checks are done in the client. The auctioneer is long enough.
Source Code
Bid
A container object returned to the client with information about the bid: transaction ID, etc.
Source Code

AuctionInterface

public interface AuctionInterface extends Remote 
   {
   public Bid bid( int amount, TransactionParticipant bidder) 
      throws RemoteException, LeaseDeniedException;
   public int earnings() throws RemoteException;
   }


Doc 29, Auction - A Transaction Example Slide # 3

Bid

public class Bid implements Serializable
   {
   public ServerTransaction bidTransaction;
   public int data;
   
   public Bid( TransactionManager clerk, long id, int bidInfo )
      {
      bidTransaction = new ServerTransaction( clerk, id );
      data = bidInfo;
      }
   }

Doc 29, Auction - A Transaction Example Slide # 4

AuctionClient

public class AuctionClient extends Thread implements TransactionParticipant
   {
   AuctionInterface auctioneer;
   int bidAmount;
   Bid bidTicket;
   int bank = 100;
   int bidsWon = 0;
   int bidAttempts = 0;
   
   public static void main (String[] args) throws Exception
      {
      ProgramProperties flags = new ProgramProperties( args);
      int commandBid = flags.getInt( "bid", 5 );
      System.setSecurityManager (new RMISecurityManager ());
      LookupLocator lookup = new LookupLocator ("jini://eli.sdsu.edu");
      ServiceRegistrar registrar = lookup.getRegistrar ();
      AuctionClient aClient = new AuctionClient( commandBid, registrar );
      aClient.start();
      }
      
   public AuctionClient( int bid, ServiceRegistrar registrar ) 
      throws RemoteException
      {
      this.bidAmount = bid;
      Entry[] serverAttributes = new Entry[1];
      serverAttributes[0] = new Name ("Auction");
      ServiceTemplate template = new ServiceTemplate (null, null, serverAttributes);
      
      auctioneer = (AuctionInterface) registrar.lookup (template);
      UnicastRemoteObject.exportObject( this );
      }

Doc 29, Auction - A Transaction Example Slide # 5

AuctionClient Making Bids

The run method makes bids as long as the bank as money. Once a bid is made, wait until the auctioneer processes the bid. The auctioneer will cause the abort or commit methods to be called. These methods will notify the thread it can continue. See next slide.

   public synchronized void run()
      {
      try
         {
         do
            {
            System.out.println( "Make bid" );
            bidTicket = auctioneer.bid( bidAmount, this );
            bidAttempts++;
            while (bidTicket != null )
               wait();
            
            System.out.println( "Bid attempts: " + bidAttempts + 
               " bids won " + bidsWon + " bank " + bank );
            }
         while (bank > 0 );
         }
      catch (Exception rmiError ) { rmiError.printStackTrace(); }
      finally { finalize(); }
      }
   public void finalize()
      {
      try
         {
         UnicastRemoteObject.unexportObject( this, true );
         }
      catch (Exception problem ) { problem.printStackTrace(); }
      }

Doc 29, Auction - A Transaction Example Slide # 6

AuctionClient TransactionParticipant Methods


These methods handle the bid transaction. The should check for valid transaction ID etc. They assume the auctioneer does not make errors.
   public synchronized void abort(TransactionManager mgr, long transactionID) 
      {
      System.out.println( "Abort" );
      bidTicket = null;
      notifyAll();
      }
      
   public synchronized void commit(TransactionManager mgr, long transactionID) 
      {
      System.out.println( "Commit" );
      bank = bank - bidAmount;
      bidTicket = null;
      bidsWon++;
      notifyAll();
      }
      
   public int prepare(TransactionManager mgr, long transactionID) 
      {
      System.out.println( "Prepare" );
      if ( bidAmount > bank)
         {
         bidAmount = bank;
         abort(mgr,transactionID );
         return ABORTED;
         }
      else
         return PREPARED;
      }

Doc 29, Auction - A Transaction Example Slide # 7
AuctionClient prepareAndCommit

This is the standard implementation of prepareAndCommit. It may be possible to implement it more efficiently, but it should have the behavior given here.

   public int prepareAndCommit(TransactionManager mgr, long transactionID) 
      {
      System.out.println( "prepare & commit" );
      int result = prepare( mgr, transactionID );
      if ( result == PREPARED )
         {
         commit(  mgr, transactionID );
         result = COMMITTED;
         }
      return result;
      }
   }

Doc 29, Auction - A Transaction Example Slide # 8

Auctioneer

Fields


public class Auctioneer extends UnicastRemoteObject 
   implements AuctionInterface, ServiceIDListener, TransactionParticipant
   {
   private ServiceID auctionServiceID;
   int earnings = 0;
   int itemsToSell = 100;
   
   TransactionManager clerk;
   // Times the bid phase. 
   AuctionClock timer;
   // Contains all the pending bids collected in the bid phase
   Map pendingBids = Collections.synchronizedMap( new HashMap());
   // Used to save state of the auctioner. From SDSU Java library
   LocalRepository dataStore;
   
   int winningBid = NOT_SET;
   
   // Labeleds used with dataStore to save state in files
   static final int NOT_SET = -1;
   static final String PENDING_BIDS = "pendingBids";
   static final String SERVICE_ID = "serviceID";
   static final String EARNINGS = "earnings";
   static final String ITEMS_TO_SELL = "itemsToSell";

Doc 29, Auction - A Transaction Example Slide # 9

Initialization

Constructor

   public Auctioneer( String[] groups, String database) throws Exception
      {
      try
         {
         dataStore = new LocalRepository( database );
         if ( dataStore.exists() )
            restoreState();
         else
            dataStore.create();
            
         ServiceRegistrar lookupSevice = joinReggie( groups );
         setClerk( lookupSevice );
         // If recovering from a crash, need to check bids and rejoin
         if ( pendingBids.size() > 0 )
            validateBids();
         // Start the clock on a bid phase
         timer = new AuctionClock( 1000 * 30 );
         timer.setDaemon( false);
         timer.start();
         }
      catch (Exception error)
         {
         System.out.println( "Startup error" );
         //Logger is from the SDSU library,
         // In this program is set to log to the screen
         Logger.error( error );
         throw error;
         }
      }

Doc 29, Auction - A Transaction Example Slide # 10

joinReggie

   //This method does not yet properly use the old service ID recovered
   // from a crash state
   //
   private ServiceRegistrar joinReggie( String[] groups )
      {
      JoinManager myManager = null;
      ServiceRegistrar[] joinSet = null;
      try
         {
         Entry[] labels = new Entry[1];
         labels[0] = new Name("Auction");
         myManager = new JoinManager (this, labels,groups,null, this, 
               new LeaseRenewalManager () );
         // Make sure we have joined. 
         // It would be better to use wait & notify here rather than sleep
         do
            {
            System.out.println( "Looking for reggie" );
            Thread.sleep( 1000 * 10 ); 
            joinSet =  myManager.getJoinSet();
            }
         while ( joinSet.length == 0 );
         }
      catch (Exception startupProblem)
         {
         System.err.println( "Could not start Auction server: " + startupProblem );
         Logger.error( "Could not start Auction server: " + startupProblem);
         System.exit( 0 );
         }
       return joinSet[0];
      }

Doc 29, Auction - A Transaction Example Slide # 11

setClerk


   // On the machine used for this example, mahalo is registered
   // with the same reggie as the Auctioneer. Hence we can use the same 
   // registrar
   private void setClerk( ServiceRegistrar registrar  ) throws RemoteException
      {
      Entry[] serverAttributes = new Entry[1];
      serverAttributes[0] = new Name ("TransactionManager");
      ServiceTemplate template = new ServiceTemplate (null, null, serverAttributes);
      clerk = (TransactionManager) registrar.lookup (template);
      }

serviceIDNotify


   // This method is called by a reggie service when it gives us 
   // our srevice ID
   public void serviceIDNotify (ServiceID uniqueID)
      {
      auctionServiceID = uniqueID;
      System.out.println("server: ID set: " + auctionServiceID );
      try
         {
         dataStore.put( PENDING_BIDS, uniqueID );
         }
      catch (IOException writeProblem)
         {
         Logger.error( "Could not store serviceID" );
         }
      }

Doc 29, Auction - A Transaction Example Slide # 12

Saving/Recovering State

saveState

   private void saveState()
      {
      try
         {
         dataStore.put( PENDING_BIDS, pendingBids );
         dataStore.put( SERVICE_ID, auctionServiceID );
         dataStore.put( EARNINGS, new Integer( earnings) );
         dataStore.put( ITEMS_TO_SELL, new Integer( itemsToSell) );
         }
      catch (IOException saveError )
         {
         Logger.error( "IOException on saving state " + saveError );
         }
      }

Doc 29, Auction - A Transaction Example Slide # 13

restoreState

There seems to be an error in LocalRepository.containsKey. If entire state is saved restoreState seems ok. If partial state is saved, the get exception on reading part of state not saved. The checks should prevent this from happening, but don’t.

   private void restoreState()
      {
      try
         {
         if (dataStore.containsKey( PENDING_BIDS ) )
            pendingBids = (Map) dataStore.get( PENDING_BIDS );
            
         if (dataStore.containsKey( SERVICE_ID ) )
            auctionServiceID = (ServiceID) dataStore.get( SERVICE_ID );
         if (dataStore.containsKey( EARNINGS ) )
            {
            Integer wrapped = (Integer) dataStore.get( EARNINGS );
            earnings = wrapped.intValue();
            }
         if (dataStore.containsKey( ITEMS_TO_SELL ) )
            {
            Integer wrapped = (Integer) dataStore.get( EARNINGS );
            itemsToSell = wrapped.intValue();
            }
         }
      catch (IOException saveError )
         {
         Logger.error( "IOException on restoring state " + saveError );
         }
      }

Doc 29, Auction - A Transaction Example Slide # 14

Validating Bids after Crash


This code has not yet been debugged. It should be correct, but until it is tested properly it can contain bugs

validateBids

   private void validateBids()
      {
      synchronized (pendingBids) // can not allow more bids until done
         {
         Iterator bids = pendingBids.keySet().iterator();
         
         while (bids.hasNext() )
            {
            Long wrapped =(Long) bids.next();
            int status = getStatus( wrapped.longValue() );
            switch (status)
               {
               case VOTING:
               case ABORTED:
                  bids.remove();
                  break;
               case COMMITTED:
                  Integer bidAmount = (Integer) pendingBids.get( wrapped );
                  commitBid( bidAmount.intValue() );
                  bids.remove();
                  break;
               case ACTIVE:
                  boolean rejoined = rejoinTransaction( wrapped.longValue() );
                  if (!rejoined)
                     bids.remove();
               }
            }
         }
      }

Doc 29, Auction - A Transaction Example Slide # 15

getStatus


   // Return the status of the transaction
   //
   private int getStatus( long bid )
      {
      try
         {
         return clerk.getState( bid );
         }
      catch (UnknownTransactionException removeIt)
         {
         return ABORTED;
         }
      catch (RemoteException whatToDoHere)
         {// should retry after a short delay. How many times to retry?
         // If we cant connect to the clerk do what? Log and retry for ever?
         Logger.error( "Remote exception on validating a bid " + whatToDoHere );
         return ABORTED;
         }
      }

Doc 29, Auction - A Transaction Example Slide # 16

rejoinTransaction

The third parameter in join is the crash count. Since we save all state, we should not loose any state. Hence keep crash count at zero.

   /** Return true if we can rejoin the transaction
    * Return false otherwise.
    */
   private boolean rejoinTransaction( long transactionID )
      {
      try
         {
         clerk.join( transactionID, this, 0  );
         return true;
         }
      catch (RemoteException connectionProblem )
         {// should retry here
         return false;
         }
      catch (Exception cantJoin)
         {
         return false;
         }
      }

Doc 29, Auction - A Transaction Example Slide # 17

AutioneerInterface Methods - Getting Bids


   public int earnings() { return earnings; }
I don’t manage the transaction leases for two reasons. First the example is already too long. Second, the bids are to be processed long before leases expire.

   /**
    * Return data client needs to check bids
    */
   public synchronized Bid bid( int amount, TransactionParticipant bidder)
      throws RemoteException, LeaseDeniedException
      {
      System.out.println( "bid: " + amount );
      try
         {
         Created leasedTransaction = clerk.create( 1000 * 60 * 2 );
         long bidID = leasedTransaction.id;
         clerk.join( bidID, this, 0 );
         clerk.join(bidID, bidder, 0 );
         putBid( bidID , amount );
         saveState();
         return new Bid( clerk, bidID, 1);
         }
      catch ( Exception bidProblem )
         {
         bidProblem.printStackTrace();
         throw new LeaseDeniedException(" Can not accept bid " );
         }
      }

Doc 29, Auction - A Transaction Example Slide # 18

TransactionParticipant Methods

Abort Bid

   public void abort(TransactionManager manager, long transactionID) 
      throws UnknownTransactionException, RemoteException
      {
      System.out.println( "Abort" );
      identifyTransaction( manager, transactionID );
      int invalidBid = getBid( transactionID );
      removeBid(  transactionID );
      if (winningBid == invalidBid )
         winningBid = getMaxBid();
      }

Doc 29, Auction - A Transaction Example Slide # 19

Commit to Bid

   public void commit(TransactionManager manager, long transactionID) 
      throws UnknownTransactionException, RemoteException
      {
      System.out.println( "commit" );
      identifyTransaction( manager, transactionID );
      commitBid( getBid( transactionID ));
      removeBid(  transactionID  );
      }
   /*
    * Change state to reflect the aceptance of the bid
    * Save state change to disk for crash recovery
    */
   private void commitBid( int bidAmount )
      {
      earnings = earnings +  bidAmount;
      System.out.println( "commit: bid " + bidAmount + " earnings " + earnings );
      itemsToSell--;
      try
         {
         dataStore.put( EARNINGS, new Integer( earnings) );
         dataStore.put( ITEMS_TO_SELL, new Integer( itemsToSell) );
         }
      catch (IOException writeError )
         {
         Logger.error( "IO exception on committing bid "  );
         Logger.error( writeError  );
         }
      }
      

Doc 29, Auction - A Transaction Example Slide # 20

Prepare the Bid


Here is were the Auctioneer decides to accept or reject a bid
Only accept the highest bid. This method assumes we process bids from high to low. If bids are processed in a different order all bids may be rejected

   public int prepare(TransactionManager manager, long transactionID) 
      throws UnknownTransactionException, RemoteException
      {
      System.out.println( "prepare" );
      identifyTransaction( manager, transactionID );
         
      int bidValue = getBid( transactionID );
      System.out.println( "prepare bidValue: " + bidValue );
      
      if ( bidValue == 0 )
         return NOTCHANGED;
      
      if (winningBid == NOT_SET)
         winningBid = getMaxBid();
      if ( bidValue < winningBid )
         {
         abort( manager, transactionID );
         return ABORTED;
         }      
      return PREPARED;
      }

Doc 29, Auction - A Transaction Example Slide # 21

prepareAndCommit


The standard prepareAndCommit method. The transaction manager will use this method on the last transaction participant on the transaction’s participant list. It saves some time rather than calling prepare and then commit. This works since all other participants have already indicated they can commit.

   public int prepareAndCommit(TransactionManager mgr, long transactionID) 
      throws UnknownTransactionException, RemoteException
      {
      System.out.println( "prepare & commit" );
      int result = prepare( mgr, transactionID );
      if ( result == PREPARED )
         {
         commit(  mgr, transactionID );
         result = COMMITTED;
         }
      return result;
      }

identifyTransaction


This method validates both the manager and the transaction ID. Since there is only one transaction manager and the auctioneer manages the bids, this is probably not needed. It does show how to handle the case where the data could be mixed up.

   private void identifyTransaction(TransactionManager manager, 
      long transactionID) throws UnknownTransactionException
      {
      if ( !manager.equals(clerk) )
         throw  new UnknownTransactionException( 
            "Unknown Transaction Manager");
      
      Long id = new Long( transactionID );
      if ( !pendingBids.containsKey( id ) )
         throw  new UnknownTransactionException( "Unknown bid id " + id);
      }

Doc 29, Auction - A Transaction Example Slide # 22

Processing an Auction

   void processAuction()
      {
      System.out.println( "Process auction Bids" );
      Object bids[];
      
      synchronized (pendingBids)
         {
         bids = pendingBids.keySet().toArray();
         }
      
      // Process bids from largest to smallest in case largest bid aborts
      // Seems like the loop should go backwards to do that. Will be interesting
      // to see why this is the case.
      Arrays.sort( bids );
      for ( int k = 0; K <  bids.length; k++ )
         {
         long bidID = ((Long) bids[k]).longValue();
         try
            {
            clerk.commit( bidID );
            }
         catch (UnknownTransactionException badTransaction )
            { removeBid( bidID ); }
         catch (CannotCommitException  killBid )
            { removeBid( bidID ); }
         catch ( RemoteException retryLater )
            {// Should retry this later, but have not implement that yet
            Logger.error("RemoteException on commit: " + retryLater );
            }
         }
      //prepare for next auction
      winningBid = NOT_SET;
      saveState();
      }

Doc 29, Auction - A Transaction Example Slide # 23

Managing pendingBids


These access methods are used to access pendingBids map. This allows the state changes to be automatically saved. The policy used in this class is rather aggressive. We could get by with less saving if we are willing to invalidate some bids.

putBid

   private void putBid( long bidID, int bidAmount ) throws IOException
      {
      try
         {
         pendingBids.put( new Long( bidID ), new Integer( bidAmount ) );
         dataStore.put( PENDING_BIDS, pendingBids );
         }
      catch (IOException writeProblem)
         {
         Logger.error( "IOException in writing pendingBids on put");
         Logger.error( writeProblem );
         pendingBids.remove(  new Long( bidID ));
         throw writeProblem;
         }
      }

removeBid

   private void removeBid(long bidID)
      {
      try
         {
         pendingBids.remove( new Long( bidID ) );
         dataStore.put( PENDING_BIDS, pendingBids );
         }
      catch (IOException writeProblem)
         {
         Logger.error("IOException in writing pendingBids on remove"); 
         Logger.error(writeProblem );
         }
      }

Doc 29, Auction - A Transaction Example Slide # 24

getBid

   private int getBid(long bidID)
      {
      Integer bid =(Integer) pendingBids.get( new Long( bidID ) );
      return bid.intValue();
      }

getMaxBid

   private int getMaxBid()
      {
      Integer maxBid;
      synchronized (pendingBids)
         {
         Collection values = pendingBids.values();
         maxBid = (Integer) Collections.max( values );
         }
      return maxBid.intValue();
      }

Doc 29, Auction - A Transaction Example Slide # 25

AuctionClock - Auction Duration


This thread times the duration of an individual auction. It will start a new auction (or biding phase) after the end of an auction.

   private class AuctionClock extends Thread
      {
      long auctionDuration;
      public AuctionClock( long duration )
         {
         auctionDuration = duration;
         }
      
      public void run()
         {
         System.out.println( "Start clock" );
         try
            {
            while( true )
               {
               System.out.println( "Sleep" );
               sleep( auctionDuration );
               processAuction();
               }
            }
         catch (InterruptedException  exitTime )
            {
            Logger.log( "AuctionClock stoped" );
            }
         }
      }

Doc 29, Auction - A Transaction Example Slide # 26

Main - Starting an Auctioneer


   public static void main( String[] args ) throws Exception
      {
      System.setSecurityManager(new RMISecurityManager());
      System.out.println("Start main");
      // Where to store state data
      String database = "/export/home/whitney/java/whitney/jini/examples/transaction/AuctionData";
      ProgramProperties flags = new ProgramProperties( args);
      String groupsString = flags.getString( "groups", "NONE" );
      String[] groups = formateGroupList( groupsString );
      Auctioneer anAuctioneer = new Auctioneer( groups, database );
      
      System.out.println("Class activated" + anAuctioneer.earnings() );
      }

Doc 29, Auction - A Transaction Example Slide # 27
formateGroupList
   /**
    * Format the groups listed in the command line format (
    * separated by commas) to an array of strings 
   private static String[] formateGroupList( String groupList )
      {
      if (groupList.equals( "NONE") )
         {
         System.out.println( "Usage: java HelloServer -groups=group1,group2 " );
         System.exit( 0 );
         }
      if (groupList.equals( "ALL") )
         return LookupDiscovery.ALL_GROUPS;
      
      if ( groupList.indexOf( ',' ) < 0 )
         return new String[] { groupList.trim() };
      
      StringTokenizer groups = new StringTokenizer( groupList, ",'");
      
      String[] formatedGroups = new String[ groups.countTokens() ];
      
      for ( int k = 0; k < formatedGroups.length; k++ )
         {
         formatedGroups[k] = groups.nextToken().trim();
         }
      return formatedGroups;
      }
   }

Copyright ©, All rights reserved.
1999 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
OpenContent license defines the copyright on this document.

Previous    visitors since 22-Apr-99    Next