/* * 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.core; import co.paralleluniverse.common.MonitoringType; import co.paralleluniverse.common.io.Persistables; import static co.paralleluniverse.common.logging.LoggingUtils.hex; import co.paralleluniverse.common.util.DegenerateInvocationHandler; import co.paralleluniverse.galaxy.Cluster; import co.paralleluniverse.galaxy.cluster.NodeChangeListener; import static co.paralleluniverse.galaxy.core.Cache.isReserved; import co.paralleluniverse.galaxy.core.Message.BACKUP; import co.paralleluniverse.galaxy.core.Message.BACKUP_PACKET; import co.paralleluniverse.galaxy.core.Message.LineMessage; import co.paralleluniverse.galaxy.server.MainMemoryDB; import co.paralleluniverse.galaxy.server.MainMemoryEntry; import com.google.common.base.Throwables; import java.beans.ConstructorProperties; import java.lang.reflect.Proxy; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicLong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author pron */ public class MainMemory extends ClusterService implements MessageReceiver, NodeChangeListener { private static final Logger LOG = LoggerFactory.getLogger(MainMemory.class); private static final long INITIAL_REF_ID = 0xffffffffL + 1; private static final short SERVER = 0; private final Comm comm; private final MainMemoryDB store; private final MainMemoryMonitor monitor; private final AtomicLong refCounter = new AtomicLong(); @ConstructorProperties({"name", "cluster", "store", "comm", "monitoringType"}) public MainMemory(String name, Cluster cluster, MainMemoryDB store, Comm comm, MonitoringType monitoringType) { this(name, cluster, store, comm, createMonitor(monitoringType, name)); } MainMemory(String name, Cluster cluster, MainMemoryDB store, Comm comm, MainMemoryMonitor monitor) { super(name, cluster); this.comm = comm; this.store = store; this.monitor = monitor; monitor.setMonitoredObject(this); cluster.addNodeChangeListener(this); comm.setReceiver(this); } @Override protected void start(boolean master) { if (master) { if (LOG.isDebugEnabled()) { LOG.debug("Performing store dump:"); store.dump(System.err); } refCounter.set(Math.max(INITIAL_REF_ID, store.getMaxId() + 1)); } setReady(true); } @Override public void switchToMaster() { super.switchToMaster(); } @Override protected void shutdown() { store.close(); } private static MainMemoryMonitor createMonitor(MonitoringType monitoringType, String name) { if (monitoringType == null) return (MainMemoryMonitor) Proxy.newProxyInstance(MainMemory.class.getClassLoader(), new Class<?>[]{MainMemoryMonitor.class}, DegenerateInvocationHandler.INSTANCE); else switch (monitoringType) { case JMX: return new JMXMainMemoryMonitor(name); case METRICS: return new MetricsMainMemoryMonitor(); } throw new IllegalArgumentException("Unknown MonitoringType " + monitoringType); } @Override public void receive(Message message) { // if (!getCluster().isMaster()) { // LOG.debug("Ignoring message {} 'cause I'm just a slave."); // return; // } LOG.debug("Received: {}", message); switch (message.getType()) { case GET: case GETX: handleMessageGet((Message.GET) message); break; case INV: handleMessageInvalidate((Message.INV) message); break; case DEL: handleMessageDelete((LineMessage) message); break; case MSG: handleMessageMsg((Message.MSG) message); break; case BACKUP_PACKET: handleMessageBackup((BACKUP_PACKET) message); break; case INVOKE: handleMessageGet((LineMessage) message); // Server cant invoke, return putx, chnged_owner or notFound. break; case ALLOC_REF: handleMessageAllocRef((Message.ALLOC_REF) message); } } void send(Message message) { LOG.debug("Sending: {}", message); try { comm.send(message); } catch (NodeNotFoundException e) { } } private boolean handleMessageGet(LineMessage msg) { final long id = msg.getLine(); for (;;) { short owner; if (isReserved(id) && store.casOwner(id, (short) -1, msg.getNode()) == msg.getNode()) { // if nonexistent root - create it if (LOG.isDebugEnabled()) LOG.debug("Owner of reserved line {} is now node {} (CAS)", hex(id), msg.getNode()); monitor.addOwnerWrite(); monitor.addObjectServed(); store.write(id, msg.getNode(), 1, new byte[0], null); send(Message.PUTX(msg, id, new short[0], 0, 1, null)); return true; } else if ((owner = store.casOwner(id, SERVER, msg.getNode())) == msg.getNode()) { // if owner is server, then transfer ownership MainMemoryEntry entry = store.read(id); if (LOG.isDebugEnabled()) LOG.debug("Owner of line {} is now node {} (previously owned by server)", hex(id), msg.getNode()); monitor.addOwnerWrite(); monitor.addObjectServed(); send(Message.PUTX(msg, id, new short[0], 0, entry.version, ByteBuffer.wrap(entry.data))); return true; } if (owner == -1 && !isReserved(id)) owner = store.findAllocation(id); if (owner == -1 && !isReserved(id)) { send(Message.NOT_FOUND(msg)); return false; } else if (owner > SERVER) { // if (owner == msg.getNode()) {// probably a slave turned master // MainMemoryEntry entry = store.read(id); // send(Message.PUTX(msg, id, new short[0], entry.version, ByteBuffer.wrap(entry.data))); // monitor.addObjectServed(); // } else send(Message.CHNGD_OWNR(msg, id, owner, true)); monitor.addOwnerServed(); return false; } LOG.debug("casOwner returned {}", owner); } } private void handleMessageInvalidate(Message.INV msg) { final long id = msg.getLine(); final short owner = msg.getNode(); final short previousOwner = msg.getPreviousOwner(); // if mesages are sent to server instead of broadcast, node B may get a putx from node A, then node A would die (before B INVs the server) // then node C gets the line from the server, and then node B INVs the server, we must INV B (in this case, B will wait for our response. See Cache.transitionToE()) // so, we check to see where A got the line from (previous owner). Since it's B but we already have C as the owner, we INV instead of INVACK. short currentOwner; if ((currentOwner = store.casOwner(id, previousOwner, owner)) == owner) { if (LOG.isDebugEnabled()) LOG.debug("Got INV: Owner of line {} is now node {}", hex(id), msg.getNode()); monitor.addOwnerWrite(); send(Message.INVACK(msg)); } else { if (LOG.isDebugEnabled()) LOG.debug("Got INV of line {} from {}, but different owner ({}) listed so replying INV", new Object[]{hex(id), msg.getNode(), currentOwner}); monitor.addOwnerServed(); send(Message.INV(msg, id, currentOwner)); } } private void handleMessageDelete(LineMessage msg) { final long id = msg.getLine(); final short owner = msg.getNode(); if (LOG.isDebugEnabled()) LOG.debug("Line {} deleted.", hex(id)); final Object txn = store.beginTransaction(); try { store.delete(id, txn); store.commit(txn); send(Message.INVACK(msg)); } catch (Exception e) { LOG.error("Exception during delete. Aborting transaction.", e); store.abort(txn); throw Throwables.propagate(e); } } private void handleMessageMsg(Message.MSG msg) { final long id = msg.getLine(); if (handleMessageGet(msg)) send(Message.MSG(msg.getNode(), id, msg.isMessenger(), msg.getData())); // return to sender, immediately following a PUTX } private void handleMessageBackup(BACKUP_PACKET msg) { final Object txn = store.beginTransaction(); try { monitor.addTransaction(msg.getBackups().size()); for (BACKUP backup : msg.getBackups()) { if (LOG.isDebugEnabled()) LOG.debug("Backing up version {} of line {} data: {}", new Object[]{backup.getVersion(), hex(backup.getLine()), backup.getData() != null ? "(" + backup.getData().remaining() + " bytes)" : "null"}); store.write(backup.getLine(), msg.getNode(), backup.getVersion(), Persistables.toByteArray(backup.getData()), txn); } store.commit(txn); send(Message.BACKUP_PACKETACK(msg)); } catch (Exception e) { LOG.error("Exception during DB operation. Aborting transaction.", e); store.abort(txn); throw Throwables.propagate(e); } } private void handleMessageAllocRef(Message.ALLOC_REF msg) { final int num = msg.getNum(); final long end = refCounter.addAndGet(num); final long start = end - num; store.allocate(msg.getNode(), start, num); send(Message.ALLOCED_REF(msg, start, num)); monitor.addAllocation(num); } @Override public void nodeRemoved(short node) { LOG.info("Node {} removed. Server now owns its lines.", node); store.removeOwner(node); } @Override public void nodeAdded(short id) { } @Override public void nodeSwitched(short id) { } }