package org.scribble.net.session; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.spi.AbstractSelectableChannel; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import org.scribble.main.RuntimeScribbleException; import org.scribble.net.ScribInterrupt; import org.scribble.net.ScribMessage; public abstract class BinaryChannelEndpoint { //protected MPSTEndpoint<?, ?> se; protected SessionEndpoint<?, ?> se; //protected BinaryChannelEndpoint parent; private AbstractSelectableChannel c; private ByteBuffer bb; //private final SocketChannel; protected final List<ScribMessage> msgs = new LinkedList<>(); private boolean isClosed = false; private int count = 0; // How many ScribMessages read so far // volatile? private int ticket = 0; // Index of the next expected ScribMessage private final List<CompletableFuture<ScribMessage>> pending = new LinkedList<>(); // Server side //protected BinaryChannelEndpoint(MPSTEndpoint<?, ?> se, AbstractSelectableChannel c) throws IOException protected BinaryChannelEndpoint(SessionEndpoint<?, ?> se, AbstractSelectableChannel c) throws IOException { this.bb = ByteBuffer.allocate(16921); // FIXME: size // Use put mode as default init(se, c); } // Client side protected BinaryChannelEndpoint() { this.bb = ByteBuffer.allocate(16921); // FIXME: size // Use put mode as default } //public abstract void initClient(MPSTEndpoint<?, ?> se, String host, int port) throws IOException; public abstract void initClient(SessionEndpoint<?, ?> se, String host, int port) throws IOException; //protected void init(MPSTEndpoint<?, ?> se, AbstractSelectableChannel c) throws IOException protected void init(SessionEndpoint<?, ?> se, AbstractSelectableChannel c) throws IOException { this.se = se; this.c = c; this.c.configureBlocking(false); } // Pre: selector is paused //protected BinaryChannelEndpoint(BinaryChannelEndpoint c) public void wrapChannel(BinaryChannelEndpoint c) throws IOException { this.se = c.se; //this.msgs.addAll(c.msgs); // Guaranteed to be empty/0 for reconnect? //this.count = c.count; //this.ticket = c.ticket; //this.parent = c; this.c = c.c; this.bb = c.bb; //FIXME: complete all pending futures on parent chan -- no: not enough by itself, that is just reading the already-deserialized cache //FIXME: pull all pending data out of parent chan (due to selector not handling it yet -- in send states, we just need to clear all expected messages up to this point) // -- so that wrapper handshake is starting clean // --- futures must be completed before here, since selector is paused } public AbstractSelectableChannel getSelectableChannel() // For asynchrony (via nio Selector) -- maybe implement/extend instead { return this.c; } public void write(ScribMessage m) throws IOException { writeBytes(this.se.smf.toBytes(m)); } // Default CompletableFuture executed by common forkjoin pool -- so all messages that are received/async'd will eventually be pulled from the queue (no manual GC necessary) public synchronized CompletableFuture<ScribMessage> getFuture() { // FIXME: better exception handling (integrate with Future interface?) final int ticket = getTicket(); CompletableFuture<ScribMessage> fut = CompletableFuture.supplyAsync(() -> { try { ScribMessage m = read(ticket); if (m instanceof ScribInterrupt) // FIXME: hacked in { throw new RuntimeScribbleException((Throwable) ((ScribInterrupt) m).payload[0]); } return m; } catch(IOException e) { throw new RuntimeScribbleException(e); } finally { this.pending.remove(0); // Safe? } }); synchronized (this.pending) { this.pending.add(fut); } return fut; } protected void sync() throws IOException // Hacky { try { synchronized (this.pending) { if (!this.pending.isEmpty()) { this.pending.get(this.pending.size() - 1).get(); } } } catch (InterruptedException | ExecutionException e) { throw new IOException(e); } } private synchronized ScribMessage read(int ticket) throws IOException { try { while (this.count < ticket && !this.isClosed) // "Chain" futures directly? i.e. each future syncs on predecessor? { wait(); } while (this.msgs.isEmpty() && !this.isClosed) { wait(); } if (this.isClosed) { throw new IOException("Channel closed"); } this.count++; ScribMessage m = this.msgs.remove(0); notifyAll(); // A later future might have gone first and blocked, so wake up (with the message already enqueued, so no notify coming from there) // finally? return m; } catch (InterruptedException e) { throw new IOException(e); } } protected synchronized void enqueue(ScribMessage m) { this.msgs.add(m); //this.count++; notifyAll(); } public abstract void writeBytes(byte[] bs) throws IOException; protected abstract void readBytesIntoBuffer() throws IOException; // synchronized (against read) // bytes ready for reading: try to deserialize and then enqueue, or else cache for later public synchronized void readAndEnqueueMessages() throws ClassNotFoundException, IOException // Here for synchronisation { readBytesIntoBuffer(); ScribMessage m; while ((m = this.se.smf.fromBytes(this.bb)) != null) { enqueue(m); } } public synchronized void close() throws IOException { this.isClosed = true; notifyAll(); } public synchronized int getTicket() { //return ++this.ticket; return this.ticket++; } // post: bb:put public ByteBuffer getBuffer() { return this.bb; } }