/* * Galaxy * Copyright (c) 2012-2014, 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.galaxy.jgroups; import co.paralleluniverse.common.monitoring.ThreadPoolExecutorMonitor; import co.paralleluniverse.galaxy.cluster.DistributedTree; import co.paralleluniverse.galaxy.core.AbstractCluster; import co.paralleluniverse.galaxy.core.CommThread; import co.paralleluniverse.galaxy.core.RefAllocator; import co.paralleluniverse.galaxy.core.RefAllocatorSupport; import co.paralleluniverse.galaxy.core.RootLocker; import static co.paralleluniverse.galaxy.jgroups.JGroupsConstants.*; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.ConstructorProperties; import java.util.Collection; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.locks.Lock; import org.jgroups.Address; import org.jgroups.ChannelListener; import org.jgroups.JChannel; import org.jgroups.Message; import org.jgroups.blocks.atomic.Counter; import org.jgroups.blocks.atomic.CounterService; import org.jgroups.blocks.locking.LockService; import org.jgroups.protocols.SEQUENCER; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; /** * * @author pron */ class JGroupsCluster extends AbstractCluster implements RootLocker, RefAllocator { private static final Logger LOG = LoggerFactory.getLogger(JGroupsCluster.class); private static final long INITIAL_REF_ID = 0xffffffffL + 1; private final String jgroupsClusterName; private JChannel channel; private CounterService counterService; private LockService lockService; private Counter refIdCounter; private Channel controlChannel; private Channel dataChannel; // private String jgroupsConfFile; private Element jgroupsConfXML; private ThreadPoolExecutor jgroupsThreadPool; private final RefAllocatorSupport refAllocatorSupport = new RefAllocatorSupport(); private final ExecutorService refAllocationExecutor = Executors.newFixedThreadPool(1); private volatile boolean counterReady; @ConstructorProperties({"name", "nodeId", "jgroupsClusterName"}) public JGroupsCluster(String name, short nodeId, String jgroupsClusterName) throws Exception { super(name, nodeId); this.jgroupsClusterName = jgroupsClusterName; } public void setJgroupsConfFile(String jgroupsConfFile) { assertDuringInitialization(); this.jgroupsConfFile = jgroupsConfFile; } public void setJgroupsConf(Element jgroupsConfXML) { assertDuringInitialization(); this.jgroupsConfXML = jgroupsConfXML; } public void setJgroupsThreadPool(ThreadPoolExecutor threadPool) { assertDuringInitialization(); this.jgroupsThreadPool = threadPool; } @Override protected void init() throws Exception { super.init(); if (jgroupsConfXML != null) this.channel = new JChannel(jgroupsConfXML); else if (jgroupsConfFile != null) this.channel = new JChannel(jgroupsConfFile); else throw new IllegalStateException("jgroupsConf or jgroupsConfFile must be set!"); if (jgroupsConfXML != null && jgroupsConfFile != null) throw new IllegalStateException("jgroupsConf or jgroupsConfFile cannot both be set!"); this.controlChannel = new ControlChannel(channel); if (!controlChannel.hasProtocol(SEQUENCER.class)) throw new RuntimeException("JChannel must have the SEQUENCER protocol"); addNodeProperty(JGROUPS_ADDRESS, true, true, JGROUPS_ADDRESS_READER_WRITER); channel.addChannelListener(new ChannelListener() { @Override public void channelConnected(org.jgroups.Channel channel) { } @Override public void channelDisconnected(org.jgroups.Channel channel) { LOG.warn("JGroups channel disconnected. Going offline!"); goOffline(); } @Override public void channelClosed(org.jgroups.Channel channel) { LOG.warn("JGroups channel closed. Going offline!"); goOffline(); } }); final DistributedTree tree = new DistributedTreeAdapter(new ReplicatedTree(controlChannel, null, 10000)); if (jgroupsThreadPool == null) throw new RuntimeException("jgroupsThreadPool property not set!"); jgroupsThreadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); jgroupsThreadPool.setThreadFactory(new ThreadFactoryBuilder().setNameFormat("jgroups-%d").setThreadFactory(new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new CommThread(r); } }).build()); ThreadPoolExecutorMonitor.register("jgroups", jgroupsThreadPool); channel.getProtocolStack().getTransport().setDefaultThreadPool(jgroupsThreadPool); channel.connect(jgroupsClusterName, null, 10000); setName(getMyAddress().toString()); setNodeProperty(JGROUPS_ADDRESS, getMyAddress()); initRefIdCounter(); if (!hasServer()) this.lockService = new LockService(channel); this.dataChannel = new JChannelAdapter(channel) { @Override public void send(Message msg) throws Exception { msg.setFlag(Message.Flag.NO_TOTAL_ORDER); super.send(msg); } }; setControlTree(tree); super.init(); // super.init() must be called after setControlTree() } private void initRefIdCounter() throws Exception { this.counterService = new CounterService(channel); this.refIdCounter = counterService.getOrCreateCounter("refIdCounter", 1); if (!hasServer()) this.setCounter(INITIAL_REF_ID); refAllocationExecutor.submit(new Callable<Void>() { @Override public Void call() throws Exception { LOG.info("Waiting for id counter to be set..."); long id; while ((id = refIdCounter.get()) < INITIAL_REF_ID) Thread.sleep(500); LOG.info("Id counter set: {}", id); counterReady = true; refAllocatorSupport.fireCounterReady(); return null; } }); } @Override public void shutdown() { super.shutdown(); refAllocationExecutor.shutdownNow(); channel.disconnect(); channel.close(); } @Override public Object getUnderlyingResource() { return channel; } @Override protected boolean isMe(NodeInfoImpl node) { return node.get(JGROUPS_ADDRESS).equals(getMyAddress()); } protected final Address getMyAddress() { return channel.getAddress(); } public Channel getDataChannel() { return dataChannel; } private boolean setCounter(long initialValue) { initialValue = Math.max(initialValue, INITIAL_REF_ID); LOG.info("Setting ref counter to {}", initialValue); for (;;) { long id = refIdCounter.get(); if (id >= initialValue) { LOG.info("Id counter set by someone else to {}", id); return false; } if (refIdCounter.compareAndSet(id, initialValue)) { LOG.info("Set id counter to {}", initialValue); return true; } } } @Override public void addRefAllocationsListener(RefAllocationsListener listener) { refAllocatorSupport.addRefAllocationsListener(listener); if (counterReady) listener.counterReady(); } @Override public void removeRefAllocationsListener(RefAllocationsListener listener) { refAllocatorSupport.addRefAllocationsListener(listener); } @Override public Collection<RefAllocationsListener> getRefAllocationsListeners() { return refAllocatorSupport.getRefAllocationListeners(); } @Override public void allocateRefs(final int count) { refAllocationExecutor.submit(new Runnable() { @Override public void run() { long end = refIdCounter.addAndGet(count); refAllocatorSupport.fireRefsAllocated(end - count, count); } }); } @Override public Object lockRoot(int id) { final Lock lock = lockService.getLock(Integer.toHexString(id)); lock.lock(); return lock; } @Override public void unlockRoot(Object obj) { ((Lock) obj).unlock(); } }