package geniusweb.protocol.session.mopac; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.logging.Level; import java.util.stream.Collectors; import geniusweb.actions.Action; import geniusweb.actions.EndNegotiation; import geniusweb.actions.Offer; import geniusweb.actions.PartyId; import geniusweb.actions.Votes; import geniusweb.deadline.Deadline; import geniusweb.events.ProtocolEvent; import geniusweb.inform.Agreements; import geniusweb.inform.Finished; import geniusweb.inform.Inform; import geniusweb.inform.Settings; import geniusweb.inform.Voting; import geniusweb.inform.YourTurn; import geniusweb.issuevalue.Bid; import geniusweb.progress.ProgressFactory; import geniusweb.protocol.CurrentNegoState; import geniusweb.protocol.ProtocolException; import geniusweb.protocol.partyconnection.ProtocolToPartyConn; import geniusweb.protocol.partyconnection.ProtocolToPartyConnFactory; import geniusweb.protocol.partyconnection.ProtocolToPartyConnections; import geniusweb.protocol.session.SessionProtocol; import geniusweb.protocol.session.SessionSettings; import geniusweb.protocol.session.mopac.phase.Phase; import geniusweb.references.Parameters; import geniusweb.references.PartyWithProfile; import geniusweb.references.ProfileRef; import geniusweb.references.ProtocolRef; import geniusweb.references.Reference; import tudelft.utilities.listener.DefaultListenable; import tudelft.utilities.logging.Reporter; import tudelft.utilities.repository.NoResourcesNowException; /** * * All parties are first sent the {@link SessionSettings}. Only parties * specified initially in the settings do participate (no late joining in) * *
power
* containing a Integer. This parameter is picked up by this protocol, for
* handling the agreement extraction from the offers. Power=1 for parties that
* do not have the power parameter.
* * A party can send {@link EndNegotiation} at any time to remove itself from the * negotiation. The other parties may continue anyawy without him. *
* Parties receive the {@link Finished} information at the very end when all * agreements have been collected. *
* This logs to "Protocol" logger if there are issues *
* This object is mutable: the internal state changes as parties interact with * the protocol. *
* Threadsafe: all entrypoints are synhronized.
*/
public class MOPAC extends DefaultListenable
* This is 'protected' to allow junit testing, this code is not a 'public'
* part of the interface.
*
* @param connectionfactory the connectionfactory for making party
* connections
*
* @throws InterruptedException if the connection procedure is unterrupted
*
* @throws IOException if this fails to properly conect to the
* parties, eg interrupted or server not
* responding..
*/
protected synchronized void connect(
ProtocolToPartyConnFactory connectionfactory)
throws InterruptedException, IOException {
List
* This is 'protected' to allow junit testing, this code is not a 'public'
* part of the interface.
*
* @param now the current time.
*/
protected synchronized void setupParties(long now) {
for (ProtocolToPartyConn conn : connections) {
conn.addListener(action -> actionRequest(conn, action,
System.currentTimeMillis()));
}
for (ProtocolToPartyConn connection : connections) {
try {
sendSettings(connection);
} catch (IOException e) {
state = state.with(new ProtocolException("Failed to initialize",
connection.getParty()));
}
}
}
/**
* Send the {@link Inform} for the current phase to the remaining parties
* and start the time-out checker
*/
private void startPhase(long now) {
Inform info = state.getPhase().getInform();
for (PartyId pid : state.getPhase().getPartyStates().getNotYetActed()) {
try {
connections.get(pid).send(info);
} catch (IOException e) {
state = state.with(new ProtocolException(
"Party seems to have disconnected", pid, e));
}
}
startTimer(1 + state.getPhase().getDeadline() - now);
}
/**
* Check at given deadline if we already ended the phase.
*
* @param deadn the deadline
*/
private void startTimer(long deadln) {
if (timer != null)
throw new IllegalStateException("Timer is still running!");
timer = new Timer("deadlinetimer");
timer.schedule(new TimerTask() {
@Override
public void run() {
checkEndPhase(System.currentTimeMillis());
}
}, deadln);
}
/**
* Inform a party about its settings
*
* @param connection
* @throws IOException if party got disconnected
*/
private synchronized void sendSettings(ProtocolToPartyConn connection)
throws IOException {
PartyId partyid = connection.getParty();
ProfileRef profile = state.getPartyProfiles().get(partyid).getProfile();
Parameters params = state.getPartyProfiles().get(partyid).getParty()
.getParameters();
connection.send(new Settings(connection.getParty(), profile, getRef(),
state.getProgress(), params));
}
/**
* This is called when one of the {@link ProtocolToPartyConn}s does an
* action. Synchronized so that we always handle only 1 action at a time.
*
* @param partyconn the connection on which the action came in
* @param action the {@link Action} taken by some party
* @param now current time
*/
protected synchronized void actionRequest(
final ProtocolToPartyConn partyconn, final Action action,
long now) {
if (finished)
return;
state = state.with(partyconn.getParty(), action, now);
checkEndPhase(System.currentTimeMillis());
}
/**
* The current phase may be completed. We check, because it may already been
* handled. Proceed to next phase as needed. Reset the deadline timers and
* inform parties. Increase progress if necessary. Must only be called
* through {@link #endPhase} to ensure this is called only once.
*
*/
private synchronized void checkEndPhase(long now) {
if (!state.getPhase().isFinal(now))
return;
// phase indeed ended. Check what's up.
if (timer != null) {
timer.cancel();
timer = null;
}
state = state.finishPhase();
if (state.isFinal(now)) {
endNegotiation();
return;
}
state = state.nextPhase(now);
startPhase(now);
}
/**
* To be called when we reach final state. Must only be called if
* {@link MOPACState#isFinal(long)}. Send finished info to all parties.
* Double calls are automatically ignored using the global finished flag.
*/
private synchronized void endNegotiation() {
if (finished)
return;
finished = true;
Finished info = new Finished(state.getAgreements());
for (ProtocolToPartyConn conn : connections) {
try {
conn.send(info);
conn.close();
} catch (Exception e) {
log.log(Level.INFO, "Failed to send Finished to " + conn, e);
}
}
notifyListeners(new CurrentNegoState(state));
}
}