package helpers; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.net.ServerSocket; import java.net.Socket; import java.util.BitSet; /** * Controls the number of crashes so no more than a minority of * processes are crashed simultaneously. Before crashing, * processes request permission to crash. It is granted only if * the crash doesn't violate model assumptions. Processes also * inform when they recover. * * TODO: Change the protocol, so that it's this class that decides * when a process should crash and informs the processes. This * allows centralizing the logic for controlling the crashes, * instead of doing it in the replicas. * * @author Nuno Santos (LSR) */ public class ProcessCrashController implements Runnable { public static final int PORT = 5678; private final Object lock = new Object(); private BitSet processState; private int replicaCount = -1; public static enum Type {Initialize, Crash, Recover, Grant, Deny}; public static class Request implements Serializable { public final int replicaCount; public final int id; public final Type type; public Request(Type type, int replicaNo, int replicaCount) { this.type = type; this.id = replicaNo; this.replicaCount = replicaCount; } } public static class Reply implements Serializable { public final Type type; public final String msg; public Reply(Type type, String msg) { this.type = type; this.msg = msg; } public Reply(Type type) { this(type, null); } } class ClientConnection implements Runnable { private final Socket s; private ObjectInputStream ois; private ObjectOutputStream oos; private int pid = -1; public ClientConnection(Socket s) throws IOException { this.s = s; // If the order is reversed (first ois then oos), // then getInputStream() blocks. (???) this.oos = new ObjectOutputStream(s.getOutputStream()); this.ois = new ObjectInputStream(s.getInputStream()); } @Override public void run() { try { while (true) { Request req = (Request) ois.readObject(); assert pid == -1 || req.id == pid : "Process pid changed? Expected: " + pid + ", received: " + req.id; synchronized (lock) { switch (req.type) { case Initialize: if (processState == null) { // All processes are up processState = new BitSet(req.replicaCount); replicaCount = req.replicaCount; System.out.println("Initialized to " + req.replicaCount + " nodes"); } if (replicaCount != req.replicaCount) { String error = "Replica count mismatch. Requested: " + req.replicaCount + ", " + "previous: " + replicaCount; oos.writeObject(new Reply(Type.Deny, error)); System.out.println(error); break; } pid = req.id; processState.set(pid); oos.writeObject(new Reply(Type.Grant)); System.out.println("Process: " + pid + " initialized"); break; case Crash: if (processState == null) { oos.writeObject(new Reply(Type.Deny, "Not initialized")); break; } if (!processState.get(req.id)) { oos.writeObject(new Reply(Type.Deny, "Process " + req.id + " is already crashed")); break; } int alive = processState.cardinality(); if (alive-1 <= replicaCount / 2) { oos.writeObject(new Reply(Type.Deny, "Maximum crashes already reached. Alive: " + alive)); break; } // Process is allowed to crash processState.flip(req.id); assert !processState.get(req.id); oos.writeObject(new Reply(Type.Grant)); System.out.println("Process " + req.id + " crashed"); break; case Recover: if (processState == null) { oos.writeObject(new Reply(Type.Deny, "Not initialized")); break; } if (processState.get(req.id)) { oos.writeObject(new Reply(Type.Deny, "Process " + req.id + " is already alive")); break; } System.out.println("Process " + req.id + " recovered"); processState.flip(req.id); oos.writeObject(new Reply(Type.Grant)); break; default: throw new AssertionError("Illegal request type: " + req.type); } } oos.flush(); } } catch (Exception e) { System.out.println("Process " + s.getInetAddress() + " disconnected"); } } } ServerSocket ss; public ProcessCrashController(int listenPort) throws IOException { ss = new ServerSocket(listenPort); } public void run() { System.out.println("Waiting for connections on port: " + ss.getLocalPort()); while (true) { try { Socket s = ss.accept(); System.out.println("New connection received: " + s.getInetAddress()); Thread t = new Thread(new ClientConnection(s)); t.start(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public static void main(String[] args) throws IOException { ProcessCrashController server = new ProcessCrashController(PORT); new Thread(server).start(); } }