/* * Quasar: lightweight threads and actors for the JVM. * Copyright (c) 2013-2015, Parallel Universe Software Co. All rights reserved. * * This program and the accompanying materials are dual-licensed under * either the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation * * or (per the licensee's choosing) * * under the terms of the GNU Lesser General Public License version 3.0 * as published by the Free Software Foundation. */ package co.paralleluniverse.remote.galaxy; import co.paralleluniverse.common.io.Streamable; import co.paralleluniverse.fibers.FiberUtil; import co.paralleluniverse.fibers.SuspendExecution; import co.paralleluniverse.galaxy.Cluster; import co.paralleluniverse.galaxy.TimeoutException; import co.paralleluniverse.galaxy.quasar.Grid; import co.paralleluniverse.galaxy.quasar.Messenger; import co.paralleluniverse.galaxy.quasar.Store; import co.paralleluniverse.io.serialization.Serialization; import co.paralleluniverse.remote.RemoteException; import co.paralleluniverse.strands.SuspendableRunnable; import co.paralleluniverse.strands.Timeout; import co.paralleluniverse.strands.channels.SendPort; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.io.Serializable; import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; import java.util.Collections; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author pron */ public class GlxRemoteChannel<Message> implements SendPort<Message>, Serializable { private static final Logger LOG = LoggerFactory.getLogger(GlxRemoteChannel.class); private static final Grid grid; private static final ExecutorService sendThreadPool = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setDaemon(true).build()); //Executors.newCachedThreadPool(); private static Canonicalizer<GlxGlobalChannelId, GlxRemoteChannel> canonicalizer = new Canonicalizer<>(); static { try { grid = Grid.getInstance(); } catch (InterruptedException ex) { throw new RuntimeException(ex); } } static Messenger getMessenger() { return grid.messenger(); } static Store getStore() { return grid.store(); } static Cluster getCluster() { return grid.cluster(); } private final GlxGlobalChannelId id; /** * Used on the creating (receiving) side * * @param channel */ public GlxRemoteChannel(SendPort<Message> channel, Object globalId) { final boolean global = globalId != null; final long topic = global ? -1 : RemoteChannelReceiver.getReceiver(channel).getTopic(); final short ownerNodeId = global ? -1 : getCluster().getMyNodeId(); this.id = new GlxGlobalChannelId(global, global ? (Long) globalId : ownerNodeId, topic); } public GlxGlobalChannelId getId() { return id; } @Override public void send(Message message) throws SuspendExecution { send(message, getId()); } @Override public boolean send(Message message, long timeout, TimeUnit unit) throws SuspendExecution, InterruptedException { throw new UnsupportedOperationException("Not supported yet."); } @Override public boolean send(Message message, Timeout timeout) throws SuspendExecution, InterruptedException { return send(message, timeout.nanosLeft(), TimeUnit.NANOSECONDS); } @Override public boolean trySend(final Message message) { try { FiberUtil.runInFiberRuntime(new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { send(message); } }); return true; } catch (InterruptedException e) { throw new RuntimeException(e); } } @Override public void close() { try { FiberUtil.runInFiberRuntime(new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { ((GlxRemoteChannel) GlxRemoteChannel.this).send(new CloseMessage()); } }); } catch (InterruptedException e) { throw new RuntimeException(e); } } @Override public void close(final Throwable t) { try { FiberUtil.runInFiberRuntime(new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { ((GlxRemoteChannel) GlxRemoteChannel.this).send(new CloseMessage(t)); } }); } catch (InterruptedException e) { throw new RuntimeException(e); } } @Override public int hashCode() { int hash = 5; hash = 43 * hash + Objects.hashCode(this.id); return hash; } @Override public boolean equals(Object obj) { if (obj == null) return false; if (!(obj instanceof GlxRemoteChannel)) return false; final GlxRemoteChannel<Message> other = (GlxRemoteChannel<Message>) obj; if (!Objects.equals(this.id, other.id)) return false; return true; } protected Object readResolve() throws java.io.ObjectStreamException, SuspendExecution { GlxRemoteChannel<Message> self = canonicalizer.get(getId(), this); new RCPhantomReference(self).register(); return self; } private static void registerRemoteRef(final short myNodeId, GlxGlobalChannelId id) throws SuspendExecution { submitSend(new RefMessage(true, myNodeId), id); } private static void unregisterRemoteRef(final short myNodeId, GlxGlobalChannelId id) throws SuspendExecution { submitSend(new RefMessage(false, myNodeId), id); } private static void submitSend(final Object message, final GlxGlobalChannelId id) { LOG.debug("sending: " + message); sendThreadPool.submit(new Runnable() { @Override public void run() { try { send(message, id); } catch (SuspendExecution e) { throw new AssertionError(e); } } }); } private static void send(Object message, GlxGlobalChannelId id) throws SuspendExecution { LOG.debug("sent {}", message); try { if (id.isGlobal()) { final long ref = id.getAddress(); if (message instanceof Streamable) { getStore().send(ref, (Streamable) message); } else { final byte[] buf = Serialization.getInstance().write(message); getStore().send(ref, buf); } } else { final short node = (short) id.getAddress(); if (message instanceof Streamable) { getMessenger().send(node, (Long) id.getTopic(), (Streamable) message); } else { final byte[] buf = Serialization.getInstance().write(message); getMessenger().send(node, (Long) id.getTopic(), buf); } } } catch (TimeoutException e) { throw new RemoteException(e); } } private short getNodeId() { return getCluster().getMyNodeId(); } static class CloseMessage implements Serializable { private final Throwable t; public CloseMessage(Throwable t) { this.t = t; } public CloseMessage() { this(null); } public Throwable getException() { return t; } } static class RefMessage implements Serializable { final boolean add; final short nodeId; public boolean isAdd() { return add; } public short getNodeId() { return nodeId; } public RefMessage(boolean add, short nodeId) { this.add = add; this.nodeId = nodeId; } @Override public String toString() { return "RefMessage{" + "add=" + add + ", nodeId=" + nodeId + '}'; } } static class RCPhantomReference extends PhantomReference<GlxRemoteChannel> { private final static Set<RCPhantomReference> rcs = Collections.newSetFromMap(new ConcurrentHashMap<RCPhantomReference, Boolean>()); private final static ReferenceQueue<GlxRemoteChannel> q = new ReferenceQueue<>(); final short myNodeId; final public GlxGlobalChannelId id; public RCPhantomReference(GlxRemoteChannel referent) { super(referent, q); this.id = referent.id; this.myNodeId = referent.getNodeId(); } public void unregister() throws SuspendExecution { GlxRemoteChannel.unregisterRemoteRef(myNodeId, id); rcs.remove(this); } public void register() throws SuspendExecution { rcs.add(this); GlxRemoteChannel.registerRemoteRef(myNodeId, id); } static { Thread collector = new Thread(new Runnable() { @Override public void run() { try { while (!Thread.interrupted()) { try { final RCPhantomReference ref = (RCPhantomReference) q.remove(); FiberUtil.runInFiber(new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { ref.unregister(); } }); } catch (ExecutionException e) { LOG.error(e.toString()); } } } catch (InterruptedException e) { LOG.info(this.toString() + " has been interrupted"); } } }, "remote-references-collector"); collector.setDaemon(true); collector.start(); } } }