org.corfudb.runtime.BaseRuntime.java Source code

Java tutorial

Introduction

Here is the source code for org.corfudb.runtime.BaseRuntime.java

Source

/**
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.corfudb.runtime;

import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.*;

import java.io.*;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

abstract class BaseRuntime implements AbstractRuntime, SMRLearner, RPCServerHandler {
    //underlying SMREngines
    Map<Long, SMREngine> enginemap;

    //map from object IDs to object instances; used for multiplexing
    Map<Long, CorfuDBObject> objectmap;

    //unique node id
    long uniquenodeid;

    final long reservedStreamIDStart = Long.MAX_VALUE - 100;
    final long reservedStreamIDStop = Long.MAX_VALUE;
    final long reservedRemoteReadMapID = Long.MAX_VALUE;

    StreamFactory streamfactory;

    RPCClient rpcc;
    RPCServer rpcs;
    RemoteReadMap rrmap;
    String rpchostname;
    int rpcportnum;

    public BaseRuntime(StreamFactory fact, long tuniquenodeid, String trpchostname, int trpcportnum) {
        streamfactory = fact;
        objectmap = new HashMap();
        enginemap = new HashMap();
        uniquenodeid = tuniquenodeid;

        //rpc
        rpchostname = trpchostname;
        rpcportnum = trpcportnum;
        rpcc = new ThriftRPCClient();
        rpcs = new ThriftRPCServer();
        rrmap = new RemoteReadMapImpl(fact.newStream(reservedRemoteReadMapID), uniquenodeid);
        rpcs.registerHandler(rpcportnum, this);
    }

    CorfuDBObject getObject(long objectid) {
        synchronized (objectmap) {
            //            if (!objectmap.containsKey(objectid)) throw new RuntimeException("object not registered!");
            //returns null if the object does not exist
            return objectmap.get(objectid);
        }
    }

    //returns any engine
    SMREngine getEngine() {
        synchronized (enginemap) {
            if (enginemap.size() == 0)
                return null;
            return enginemap.values().iterator().next();
        }
    }

    /**
     * Returns the SMR engine corresponding to the passed in stream/object ID.
     *
     * @param objectid object/stream id
     * @return SMR Engine playing the stream with the passed in stream id
     */
    SMREngine getEngine(long objectid) {
        synchronized (enginemap) {
            if (!enginemap.containsKey(objectid))
                return null;
            return enginemap.get(objectid);
        }
    }

    public void registerObject(CorfuDBObject obj) {
        registerObject(obj, false);
    }

    /**
     * Registers an object with the runtime
     *
     * @param  obj  the object to register
     */
    public void registerObject(CorfuDBObject obj, boolean remote) {
        if (!remote) {
            synchronized (objectmap) {
                synchronized (enginemap) {
                    if (objectmap.containsKey(obj.getID())) {
                        System.out.println("object ID already registered!");
                        throw new RuntimeException();
                    }
                    System.out.println("registering object ID " + obj.getID());
                    objectmap.put(obj.getID(), obj);
                    SMREngine smre = new SMREngine(streamfactory.newStream(obj.getID()), uniquenodeid);
                    smre.registerLearner(this);
                    enginemap.put(obj.getID(), smre);
                    rrmap.putMyRuntime(obj.getID(), this.rpchostname, this.rpcportnum);
                }
            }
        } else {
            System.out.println("ignoring remote object registration for " + obj.getID());
        }
    }

    public void rpcRemoteRuntime(CorfuDBObject cob, CorfuDBObjectCommand command) {
        Pair<String, Integer> remoteruntime = rrmap.getRemoteRuntime(cob.getID());
        if (remoteruntime == null)
            throw new RuntimeException("unable to find object in system");
        CorfuDBObjectCommand retobj = (CorfuDBObjectCommand) rpcc.send(new Pair<Long, Object>(cob.getID(), command),
                remoteruntime.first, remoteruntime.second);
        if (retobj == null)
            throw new RuntimeException("remote read returned null...");
        command.setReturnValue(retobj.getReturnValue());
    }

    //receives incoming RPCs
    @Override
    public Object deliverRPC(Object cmd) {
        Pair<Long, Object> P = (Pair<Long, Object>) cmd;
        //only queries are supported --- should we check this here, or just enforce it at the send point?
        SMREngine smre = getEngine(P.first);
        if (smre == null) //we aren't playing this stream; the client was misinformed
        {
            System.out.println("received RPC for object that we aren't playing");
            return null; //todo: should we return a cleaner error code instead?
        }
        smre.sync(SMREngine.TIMESTAMP_INVALID, P.second);
        return P.second;
    }

    //we don't lock the object; the sub-classing runtime is responsible for locking, if required,
    //before calling this method
    public void applyCommandToObject(long curstream, Object command, long timestamp) {
        CorfuDBObject cob = getObject(curstream);
        if (cob == null)
            throw new RuntimeException("entry for stream " + curstream + " with no registered object");
        cob.applyToObject(command);
        //todo: verify that it's okay for this to not be atomic with the apply
        //in the worst case, the object thinks it has an older version than it really does
        //but all that should cause is spurious aborts
        //the alternative is to have the apply in the object always call a superclass version of apply
        //that sets the timestamp
        //only the apply thread sets the timestamp, so we only have to worry about concurrent reads
        if (timestamp != SMREngine.TIMESTAMP_INVALID)
            cob.setTimestamp(timestamp);
    }

}

/**
 * This runtime implementation provides linearizable semantics for CorfuDB objects. It's unaware of transactions.
 * It does a simple, pass-through translation between the runtime API and SMR invocations, with the addition of
 * object multiplexing so that a single SMR instance can be shared by multiple objects.
 *
 */
public class SimpleRuntime extends BaseRuntime {

    /**
     * Creates a SimpleRuntime
     *
     * @param  fact  a factory for creating new Stream objects
     * @param  tuniquenodeid    an identifier unique to this client process
     */
    public SimpleRuntime(StreamFactory fact, long tuniquenodeid, String trpchostname, int trpcportnum) {
        super(fact, tuniquenodeid, trpchostname, trpcportnum);
    }

    public void BeginTX() {
        throw new RuntimeException("SimpleRuntime does not support transactions.");
    }

    public boolean EndTX() {
        throw new RuntimeException("SimpleRuntime does not support transactions.");
    }

    public void query_then_update_helper(CorfuDBObject cob, CorfuDBObjectCommand query,
            CorfuDBObjectCommand update) {
        query_then_update_helper(cob, query, update, null);
    }

    public void query_then_update_helper(CorfuDBObject cob, CorfuDBObjectCommand query, CorfuDBObjectCommand update,
            Serializable key) {
        Set<Long> streams = new HashSet<Long>();
        streams.add(cob.getID());
        SMREngine smre = getEngine(cob.getID());
        if (smre == null) // we are not playing this stream
        {
            Pair<String, Integer> remotenode = rrmap.getRemoteRuntime(cob.getID());
            if (remotenode == null) // we can't locate a remote runtime to read from either
                throw new RuntimeException("Cant find object in system");
            throw new RuntimeException("remote objects do not support query_then_update_helper");
        }

        getEngine(cob.getID()).propose(update, streams, query);
    }

    public void update_helper(CorfuDBObject cob, CorfuDBObjectCommand update) {
        update_helper(cob, update, null);
    }

    public void update_helper(CorfuDBObject cob, CorfuDBObjectCommand update, Serializable key) {
        query_then_update_helper(cob, null, update);
    }

    public void query_helper(CorfuDBObject cob) {
        query_helper(cob, null);
    }

    public void query_helper(CorfuDBObject cob, Serializable key) {
        query_helper(cob, key, null);
    }

    public void query_helper(CorfuDBObject cob, Serializable key, CorfuDBObjectCommand command) {
        SMREngine smre = getEngine(cob.getID());
        if (smre == null) //not playing stream
        {
            rpcRemoteRuntime(cob, command);
        } else
            smre.sync(SMREngine.TIMESTAMP_INVALID, command);
    }

    public void deliver(Object command, long curstream, long timestamp) {
        //we don't have to lock the object --- there's one thread per SMREngine,
        //and exactly one SMREngine per stream/object
        applyCommandToObject(curstream, command, timestamp);
    }

}

interface RPCServer {
    public void registerHandler(int portnum, RPCServerHandler h);
}

interface RPCServerHandler {
    public Object deliverRPC(Object cmd);
}

interface RPCClient {
    public Object send(Serializable command, String hostname, int portnum);
}

class ThriftRPCClient implements RPCClient {
    public void ThriftRPCClient() {

    }

    public Object send(Serializable command, String hostname, int portnum) {
        try {
            //todo: make this less brain-dead
            TTransport transport = new TSocket(hostname, portnum);
            transport.open();
            TProtocol protocol = new TBinaryProtocol(transport);
            RemoteReadService.Client client = new RemoteReadService.Client(protocol);

            Object ret = Utils.deserialize(client.remote_read(Utils.serialize(command)));
            transport.close();
            return ret;
        } catch (TTransportException e) {
            throw new RuntimeException(e);
        } catch (TException e) {
            throw new RuntimeException(e);
        }
    }
}

class ThriftRPCServer implements RPCServer {

    public ThriftRPCServer() {

    }

    public void registerHandler(int portnum, RPCServerHandler h) {
        final RPCServerHandler handler = h;
        final TServer server;
        TServerSocket serverTransport;
        RemoteReadService.Processor<RemoteReadService.Iface> processor;
        try {
            serverTransport = new TServerSocket(portnum);
            processor = new RemoteReadService.Processor(new RemoteReadService.Iface() {
                @Override
                public ByteBuffer remote_read(ByteBuffer arg) throws TException {
                    return Utils.serialize(handler.deliverRPC(Utils.deserialize(arg)));
                }
            });
            server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor(processor));
            new Thread(new Runnable() {
                @Override
                public void run() {
                    server.serve(); //this seems to be a blocking call, putting it in its own thread
                }
            }).start();
            System.out.println("listening on port " + portnum);
        } catch (TTransportException e) {
            throw new RuntimeException(e);
        }
    }

}

class Utils {
    public static ByteBuffer serialize(Object obj) {
        try {
            //todo: make serialization less clunky!
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(obj);
            byte b[] = baos.toByteArray();
            oos.close();
            return ByteBuffer.wrap(b);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static Object deserialize(ByteBuffer b) {
        try {
            //todo: make serialization less clunky!
            ByteArrayInputStream bais = new ByteArrayInputStream(b.array());
            ObjectInputStream ois = new ObjectInputStream(bais);
            Object obj = ois.readObject();
            return obj;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException ce) {
            throw new RuntimeException(ce);
        }
    }
}

interface RemoteReadMap {
    public Pair<String, Integer> getRemoteRuntime(long objectid);

    public void putMyRuntime(long objectid, String hostname, int port);
}

class RemoteReadMapImpl implements RemoteReadMap, SMRLearner {
    //this can't be implemented over CorfuDBObjects to avoid
    //circularity issues.

    //for now, we maintain just one node per object
    Map<Long, Pair<String, Integer>> objecttoruntimemap;
    Lock biglock;

    SMREngine smre;

    public RemoteReadMapImpl(Stream s, long uniquenodeid) {
        objecttoruntimemap = new HashMap<Long, Pair<String, Integer>>();
        //        System.out.println("creating remotereadmap...");
        smre = new SMREngine(s, uniquenodeid);
        smre.registerLearner(this);
        //        System.out.println(smre + " has learner " + smre.smrlearner + " of class " + smre.smrlearner.getClass());
        biglock = new ReentrantLock();
    }

    @Override
    public Pair<String, Integer> getRemoteRuntime(long objectid) {
        smre.sync();
        biglock.lock();
        Pair<String, Integer> P = objecttoruntimemap.get(objectid);
        if (P == null) {
            System.out.println("object " + objectid + " not found");
            System.out.println(objecttoruntimemap);
        }
        biglock.unlock();
        return P;
    }

    @Override
    public void putMyRuntime(long objectid, String hostname, int port) {
        Triple T = new Triple(objectid, hostname, port);
        //        System.out.println("RRM proposing command " + T + " on " + smre.curstream.getStreamID());
        smre.propose(T);
    }

    @Override
    public void deliver(Object command, long curstream, long timestamp) {
        //        System.out.println("RRM got message on " + curstream);
        Triple<Long, String, Integer> T = (Triple<Long, String, Integer>) command;
        biglock.lock();
        objecttoruntimemap.put(T.first, new Pair(T.second, T.third));
        biglock.unlock();
    }
}