org.corfudb.client.CorfuDBClient.java Source code

Java tutorial

Introduction

Here is the source code for org.corfudb.client.CorfuDBClient.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.client;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.http.impl.client.HttpClients;
import org.apache.http.client.HttpClient;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;

import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.Json;

import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * Note, the following imports require Java 8
 */
import java.util.concurrent.locks.StampedLock;

/**
 * This class is used by clients to access the CorfuDB infrastructure.
 * It is responsible for constructing client views, and returning
 * interfaces which clients use to access the CorfuDB infrastructure.
 *
 * @author Michael Wei <mwei@cs.ucsd.edu>
 */
public class CorfuDBClient implements java.io.Serializable {

    private String configurationString;
    private StampedLock viewLock;
    private Thread viewManagerThread;
    private CorfuDBView currentView;
    private BooleanLock viewUpdatePending;

    private Logger log = LoggerFactory.getLogger(CorfuDBClient.class);

    private class BooleanLock {
        public boolean lock;

        public BooleanLock() {
            lock = false;
        }
    }

    /**
     * Suppressed default constructor.
     */
    private CorfuDBClient() {
    }

    /**
     * Constructor. Generates an instance of a CorfuDB client, which
     * manages views of the CorfuDB infrastructure and provides interfaces
     * for clients to access.
     *
     * @param configurationString   A configuration string which describes how to reach the \
     *                              CorfuDB instance. This is usually a http address for a \
     *                              configuration master.
     */
    public CorfuDBClient(String configurationString) {
        this.configurationString = configurationString;

        viewLock = new StampedLock();
        viewUpdatePending = new BooleanLock();
        viewUpdatePending.lock = true;
        viewManagerThread = getViewManagerThread();
    }

    /**
     * Starts the view manager thread. The view manager retrieves the view
     * and manages view changes. This thread will be automatically started
     * when any requests are made, but this method allows the view manager
     * to load the inital view during application load.
     */
    public void startViewManager() {
        if (!viewManagerThread.isAlive()) {
            log.debug("Starting view manager thread.");
            viewManagerThread.start();
        }
    }

    /**
     * Retrieves the CorfuDBView from a configuration string. The view manager
     * uses this method to fetch the most recent view.
     */
    private CorfuDBView retrieveView() throws IOException {
        HttpClient httpClient = HttpClients.createDefault();
        HttpResponse response = httpClient.execute(new HttpGet(configurationString));
        if (response.getStatusLine().getStatusCode() != 200) {
            log.warn("Failed to get view from configuration string", response.getStatusLine());
            throw new IOException("Couldn't get view from configuration string");
        }
        try (JsonReader jr = Json
                .createReader(new BufferedReader(new InputStreamReader(response.getEntity().getContent())))) {
            return new CorfuDBView(jr.readObject());
        }
    }

    /**
     * Invalidate the current view and wait for a new view.
     */
    public void invalidateViewAndWait() {
        log.warn("Client requested invalidation of current view");
        if (currentView != null) {
            currentView.invalidate();
        }
        synchronized (viewUpdatePending) {
            viewUpdatePending.lock = true;
            viewUpdatePending.notify();
            while (viewUpdatePending.lock) {
                try {
                    viewUpdatePending.wait();
                } catch (InterruptedException ie) {
                }
            }
        }
    }

    /**
     * Synchronously block until a valid view is installed.
     */
    public void waitForViewReady() {
        synchronized (viewUpdatePending) {
            while (viewUpdatePending.lock) {
                try {
                    viewUpdatePending.wait();
                } catch (InterruptedException ie) {

                }
            }
        }
    }

    /**
     * Get the current view. This method optimisically acquires the
     * current view.
     */
    public CorfuDBView getView() {
        if (viewManagerThread == null || currentView == null || !currentView.isValid()) {
            if (viewManagerThread == null) {
                startViewManager();
            }
            synchronized (viewUpdatePending) {
                while (viewUpdatePending.lock) {
                    try {
                        viewUpdatePending.wait();
                    } catch (InterruptedException ie) {
                    }
                }
            }
        }
        long stamp = viewLock.tryOptimisticRead();
        CorfuDBView view = currentView;
        if (!viewLock.validate(stamp)) {
            //We should only get here if the view is being updated.
            stamp = viewLock.readLock();
            view = currentView;
            viewLock.unlock(stamp);
        }
        return currentView;
    }

    /**
     * Retrieves a runnable that provides a view manager thread. The view
     * manager retrieves the view and manages view changes.
     */
    private Thread getViewManagerThread() {
        return new Thread(new Runnable() {
            @Override
            public void run() {
                log.debug("View manager thread started.");
                while (true) {
                    synchronized (viewUpdatePending) {
                        if (viewUpdatePending.lock) {
                            log.debug("View manager retrieving view...");
                            //lock, preventing old view from being read.
                            long stamp = viewLock.writeLock();
                            try {
                                CorfuDBView newView = retrieveView();
                                if (currentView == null || newView.getEpoch() > currentView.getEpoch())
                                    ;
                                {
                                    String oldEpoch = (currentView == null) ? "null"
                                            : Long.toString(currentView.getEpoch());
                                    log.info("New view epoch " + newView.getEpoch()
                                            + " greater than old view epoch " + oldEpoch + ", changing views");
                                    currentView = newView;
                                    viewUpdatePending.lock = false;
                                    viewUpdatePending.notifyAll();
                                }
                            } catch (IOException ie) {
                                log.warn("Error retrieving view: " + ie.getMessage());
                                currentView.invalidate();
                            } finally {
                                viewLock.unlock(stamp);
                            }
                        } else {
                            while (!viewUpdatePending.lock) {
                                try {
                                    viewUpdatePending.wait();
                                } catch (InterruptedException ie) {
                                }
                            }
                        }
                    }
                }
            }
        });
    }
}