CS 696 Emerging Technologies: Java Distributed Computing Spring Semester, 1999 Auction - A Transaction Example |
||
---|---|---|
© 1999, All Rights Reserved, SDSU & Roger Whitney San Diego State University -- This page last updated 22-Apr-99 |
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
public interface AuctionInterface extends Remote { public Bid bid( int amount, TransactionParticipant bidder) throws RemoteException, LeaseDeniedException; public int earnings() throws RemoteException; }
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; } }
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 ); }
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(); } }
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; }
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; } }
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";
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; } }
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]; }
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" ); } }
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 ); } }
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 ); } }
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(); } } } }
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; } }
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; } }
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 " ); } }
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(); }
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 ); } }
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; }
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); }
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(); }
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 ); } }
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(); }
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" ); } } }
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() ); }
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