/*** Bully Algorithm Description (No response, start an election) 1.0 - P attempts to do an update to the schema DB and notices that the coordinator is no longer responding to requests, so it initiates an election: 1.1 - P sends an election message to all higher priority processes. If no one responds, then P wins the election and becomes the new coordinator. If one of the high priority processes responds, then that h.p process takes over the election and P is all done. (Receive an election request) 2.0 - When a process receives an election message from a lower priority process: 2.1 - The receiver sends an 'ok' message back to the l.p process, indicating that it is alive and will take over the election process. 2.2 - The receiver holds an election, unless it is already holding one. 2.3 - Eventually all processes give up except one, the one with the highest priority. 2.4 - The new coordinator announces its victory by sending all other processes a message telling them that it is the new coordinator. (Restart) 3.0 - If a process that was previously down comes back up: 3.1 - It holds an election. If it happens to be the highest priority, then it wins the election and takes over the coordinator's job. 3.2 - The process with the highest priority always wins, hence the 'bully' title. ***/ // Schema code changes and Bully Algorithm Psuedo-code /******************************************************* * Changes to SchemaServlet or new replication classes??? *******************************************************/ /*** Global vars ***/ ... SchemaInstance instance; // but where to initialise??? doGet??? int schemaElectionId = 0; String masterSchemaLocation = ""; boolean isMasterSchema = false; boolean electionInProgess = false; ... /*************************************************** * Init - replication constructor **************************************************/ public SchemaReplicaSender() { ... // Add these to the Schema constructor // The schemaservlet's URL.hashcode() gives a unique process id schemaElectionId = schemaLocation.hashCode(); // Each time this servlet starts, attempt to get elected as the // master. (A response from a higher priority process stops the // election process. No response denotes this is the master) electMasterSchema(); // Once a Master has been elected, synchronise the schema DB // with the existing master, or a merging of the slave's data synchroniseSchemaDB(); ... }//end-constructor /******************************* * Changes to SchemaServlet doGet ********************************/ protected boolean doGet(String servletPath, HttpServletRequest request, ResponseWriter writer) throws RGMAException { ... // Check if the SchemaInstance has been initialised if (instance == null) { /* Initialisation is done here rather than in init() as expected. This is due to an integration issue with LCFG as MySQL is started after tomcat. So attempting to obtain a db connection will produce a nasty error. */ instance = createSchemaInstance(); } if (instance.getSchemaServletLocation().equals("")) { String servletLocation = getURL(request); instance.setSchemaServletLocation(servletLocation); } ... } else if(servletPath.equals("/masterNotification")) { masterNotification(masterSchemaLocation); } else if(servletPath.equals("/electionRequest")) { electionRequest(); }... }//end-doGet /************************************************************ * ADD THIS NEW METHOD * * Listen for and record identity of the remote Schema master * * @param master schema location * @throws RGMAException ************************************************************/ private void masterNotification(String masterSchemaLocation) throws RGMAException { // if this is not a master, then record new remote master's location if(!isMasterSchema) { this.masterSchemaLocation = masterSchemaLocation); } }//end-masterNotification /********************************************************************* * ADD THIS NEW METHOD * * Returns 'ok' (true) to caller. Must be alive to do this, so if this * routine executes then 'true' is the only response it will return. * * @return true * @throws RGMAException ********************************************************************/ private boolean electionRequest() throws RGMAException { // Start an election process and respond to the lower priority // process that made the request. As this is a higher priority // process, then return 'ok' (true) to the caller to stop it and // take over the election process. // initiate own election process if(!electionInProgress) electMasterSchema(); // always return true, because you must be alive to do so! return(true); }//end-electionRequest /************************************************************ * ADD THIS NEW METHOD * * Synchronise with the master schema, or if this is the new * master, then merge the slaves schema db data and use that * * @param * @throws RGMAException ************************************************************/ private void synchroniseSchemaDB(String masterSchemaLocation) throws RGMAException { // if this is not a master, then record new remote master's location if(!isMasterSchema) { // get access to the master schema Schema masterSchema = new Schema(this.masterSchemaLocation); // need some sort of routine in a schema to read through the DB // and return it as an XML string (probably). This XML will then // be used to construct an image of the DB in this local schema copySchemaTable(masterSchema.getAllTableData()); } // ...else this schema must be the master, so poll all the slaves else { try { LinkedList schemaDbList = new LinkedList(); // read XML schema list to get latest schema list LinkedList schemaList = readSchemaList(); // step through the remote schemas applying reading DB Iterator listIT = schemaList.iterator(); while(listIT.hasNext()) { Schema schema = (Schema)listIT.next(); // read remote schema db if(!schema.getLocation.equals(this.schemaLocation)) { try { schemaDbList.add(schema.getAllTableData()); } catch(Exception e) { // remote call exception response } }//end-if }//end-while // Now merge the schem db XML docs // If entries clash, use latest one // If entry in some but not all, keep entry // (for deletes, eventually the entry will time out) mergeSchemaDB(schemaDbList); } catch(Exception e) { // local call exception response } }//end-else }//end-synchroniseSchemaDB /********************************************************************* * When creating a new table (to be added to the schema DB), then call * this method instead of the existing method call to first check with * the master schema that the update can go ahead. The update is then * propagated to all other known (slave) schemas. ********************************************************************/ void createTable(String createStatement) { // while the update hasn't been applied to the master db... boolean done = false; while(!done) { // if this local Schema is the master attempt local DB update if(isMasterSchema) { try { done = this.instance.createTable(createStatement); // if update failed (perhaps due to duplicate table)... if(!done) { // ...then FAIL! // post exception??? } // read XML schema list to get latest list each time LinkedList schemaList = readSchemaList(); // step through the remote schemas applying update Iterator listIT = schemaList.iterator(); while(listIT.hasNext()) { Schema schema = (Schema)listIT.next(); // If remote schema, then do immediate update // (authorisation already received above) if(!schema.getLocation.equals(this.schemaLocation)) { try { schema.createTable(createStatement); } catch(Exception e) { // remote call exception response } }//end-if }//end-while // break out of loop now we're done break; } catch(Exception e) { // local call exception response } else { // else this is not the master, some remote schema is // the master so get authority to do the update try { // call remote schema to get authorisation and do DB update Schema masterSchema = new Schema(this.masterSchemaLocation); done = masterSchema.createTable(createStatement); // if successful, do immediate update to local db // (write-thru-cache) and break out of loop if(done) { try { this.instance.createTable(createStatement); break; } catch(Exception e) { // local call exception response } }//end-if }//end-try-1 catch(Exception e) { // remote call exception response } }//end-else // Remote master doUpdate call failed, so try to get another // one elected. Not done with update, so loop around and try // again if(!done) electMasterSchema(); }//end-while }//end-createTable /******************************************************************** * Initiates the election for the master schema. Returned value may be * this schema or some remote schema (whoever is 'live' and has the * highest priority) ********************************************************************/ void electMasterSchema() { electionInProgress = true; // read XML schema list to get latest list of schemas each time LinkedList schemaList = readSchemaList(); // build higher priority list of known remote schemas each time LinkedList highPrioritySchemaList = new LinkedList(); Iterator listIT = schemaList.iterator(); while(listIT.hasNext()) { Schema schema = (Schema)listIT.next(); // if current schema is not this schema, and its id > this.id, // then add current schema to the h.p. list if(schema.getLocation() != this.schemaLocation && schema.getLocation().hashCode() > schemaElectionId) { // Note: string.hashcode() is unique and consistent for the // location string of each schema highPrioritySchemaList.add(schema); }//end-if }//end-while // for each hp schema, send a message to get an 'alive' response // If there's no response then this.schema is the 'live' one boolean response = false; listIT = highPrioritySchemaList.iterator(); while(!response && listIT.hasNext()) { // get the current schema from the list Schema schema = (Schema)listIT.next(); // request a 'live' response from current h.p. schema try { response = schema.electionRequest(); } catch(Exception e) { electionInProgress = false; // remote call exception response } }//end-while // if no response from any other, then this is the highest priority // 'live' schema, so make it the master if(!response) { isMasterSchema = true; this.masterSchemaLocation = this.schemaLocation; // let everyone else know this is the master schema listIT = schemaList.iterator(); while(listIT.hasNext()) { // get the current schema from the list Schema schema = (Schema)listIT.next(); // send the location of the new master schema to the current // schema try { schema.masterNotification(this.masterSchemaLocation); } catch(Exception e) { // remote call exception response } }//end-while }//end-if(!response) electionInProgress = false; }//end-electMasterSchema