org.janusgraph.graphdb.database.idassigner.StandardIDPool.java Source code

Java tutorial

Introduction

Here is the source code for org.janusgraph.graphdb.database.idassigner.StandardIDPool.java

Source

// Copyright 2017 JanusGraph Authors
//
// 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.janusgraph.graphdb.database.idassigner;

import java.time.Duration;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import com.google.common.base.Preconditions;
import com.google.common.base.Stopwatch;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.janusgraph.core.JanusGraphException;
import org.janusgraph.diskstorage.BackendException;
import org.janusgraph.diskstorage.IDBlock;

import org.janusgraph.diskstorage.IDAuthority;

import org.janusgraph.diskstorage.util.time.Temporals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Matthias Broecheler (me@matthiasb.com)
 */

public class StandardIDPool implements IDPool {

    private static final Logger log = LoggerFactory.getLogger(StandardIDPool.class);

    private static final IDBlock ID_POOL_EXHAUSTION = new IDBlock() {
        @Override
        public long numIds() {
            throw new UnsupportedOperationException();
        }

        @Override
        public long getId(long index) {
            throw new UnsupportedOperationException();
        }
    };

    private static final IDBlock UNINITIALIZED_BLOCK = new IDBlock() {
        @Override
        public long numIds() {
            return 0;
        }

        @Override
        public long getId(long index) {
            throw new ArrayIndexOutOfBoundsException(0);
        }
    };

    private static final int RENEW_ID_COUNT = 100;

    private final IDAuthority idAuthority;
    private final long idUpperBound; //exclusive
    private final int partition;
    private final int idNamespace;

    private final Duration renewTimeout;
    private final double renewBufferPercentage;

    private IDBlock currentBlock;
    private long currentIndex;
    private long renewBlockIndex;
    //    private long nextID;
    //    private long currentMaxID;
    //    private long renewBufferID;

    private volatile IDBlock nextBlock;
    private Future<IDBlock> idBlockFuture;
    private IDBlockGetter idBlockGetter;
    private final ThreadPoolExecutor exec;

    private volatile boolean closed;

    private final Queue<Future<?>> closeBlockers;

    public StandardIDPool(IDAuthority idAuthority, int partition, int idNamespace, long idUpperBound,
            Duration renewTimeout, double renewBufferPercentage) {
        Preconditions.checkArgument(idUpperBound > 0);
        this.idAuthority = idAuthority;
        Preconditions.checkArgument(partition >= 0);
        this.partition = partition;
        Preconditions.checkArgument(idNamespace >= 0);
        this.idNamespace = idNamespace;
        this.idUpperBound = idUpperBound;
        Preconditions.checkArgument(!renewTimeout.isZero(), "Renew-timeout must be positive");
        this.renewTimeout = renewTimeout;
        Preconditions.checkArgument(renewBufferPercentage > 0.0 && renewBufferPercentage <= 1.0,
                "Renew-buffer percentage must be in (0.0,1.0]");
        this.renewBufferPercentage = renewBufferPercentage;

        currentBlock = UNINITIALIZED_BLOCK;
        currentIndex = 0;
        renewBlockIndex = 0;

        nextBlock = null;

        // daemon=true would probably be fine too
        exec = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
                new ThreadFactoryBuilder().setDaemon(false)
                        .setNameFormat("JanusGraphID(" + partition + ")(" + idNamespace + ")[%d]").build());
        //exec.allowCoreThreadTimeOut(false);
        //exec.prestartCoreThread();
        idBlockFuture = null;

        closeBlockers = new ArrayDeque<>(4);

        closed = false;
    }

    private synchronized void waitForIDBlockGetter() throws InterruptedException {
        Stopwatch sw = Stopwatch.createStarted();
        if (null != idBlockFuture) {
            try {
                nextBlock = idBlockFuture.get(renewTimeout.toMillis(), TimeUnit.MILLISECONDS);
            } catch (ExecutionException e) {
                String msg = String.format(
                        "ID block allocation on partition(%d)-namespace(%d) failed with an exception in %s",
                        partition, idNamespace, sw.stop());
                throw new JanusGraphException(msg, e);
            } catch (TimeoutException e) {
                String msg = String.format("ID block allocation on partition(%d)-namespace(%d) timed out in %s",
                        partition, idNamespace, sw.stop());
                // Attempt to cancel the renewer
                idBlockGetter.stopRequested();
                if (idAuthority.supportsInterruption()) {
                    idBlockFuture.cancel(true);
                } else {
                    // Attempt to clean one dead element out of closeBlockers every time we append to it
                    if (!closeBlockers.isEmpty()) {
                        Future<?> f = closeBlockers.peek();
                        if (null != f && f.isDone())
                            closeBlockers.remove();
                    }
                    closeBlockers.add(idBlockFuture);
                }
                throw new JanusGraphException(msg, e);
            } catch (CancellationException e) {
                String msg = String.format(
                        "ID block allocation on partition(%d)-namespace(%d) was cancelled after %s", partition,
                        idNamespace, sw.stop());
                throw new JanusGraphException(msg, e);
            } finally {
                idBlockFuture = null;
            }
            // Allow InterruptedException to propagate up the stack
        }
    }

    private synchronized void nextBlock() throws InterruptedException {
        assert currentIndex == currentBlock.numIds();
        Preconditions.checkState(!closed,
                "ID Pool has been closed for partition(%s)-namespace(%s) - cannot apply for new id block",
                partition, idNamespace);

        if (null == nextBlock && null == idBlockFuture) {
            startIDBlockGetter();
        }

        if (null == nextBlock) {
            waitForIDBlockGetter();
        }

        if (nextBlock == ID_POOL_EXHAUSTION)
            throw new IDPoolExhaustedException(
                    "Exhausted ID Pool for partition(" + partition + ")-namespace(" + idNamespace + ")");

        currentBlock = nextBlock;
        currentIndex = 0;

        log.debug("ID partition({})-namespace({}) acquired block: [{}]", partition, idNamespace, currentBlock);

        assert currentBlock.numIds() > 0;

        nextBlock = null;

        assert RENEW_ID_COUNT > 0;
        renewBlockIndex = Math.max(0, currentBlock.numIds()
                - Math.max(RENEW_ID_COUNT, Math.round(currentBlock.numIds() * renewBufferPercentage)));
        assert renewBlockIndex < currentBlock.numIds() && renewBlockIndex >= currentIndex;
    }

    @Override
    public synchronized long nextID() {
        assert currentIndex <= currentBlock.numIds();

        if (currentIndex == currentBlock.numIds()) {
            try {
                nextBlock();
            } catch (InterruptedException e) {
                throw new JanusGraphException("Could not renew id block due to interruption", e);
            }
        }

        if (currentIndex == renewBlockIndex) {
            startIDBlockGetter();
        }

        long returnId = currentBlock.getId(currentIndex);
        currentIndex++;
        if (returnId >= idUpperBound)
            throw new IDPoolExhaustedException("Reached id upper bound of " + idUpperBound);
        log.trace("partition({})-namespace({}) Returned id: {}", partition, idNamespace, returnId);
        return returnId;
    }

    @Override
    public synchronized void close() {
        closed = true;
        try {
            waitForIDBlockGetter();
        } catch (InterruptedException e) {
            throw new JanusGraphException("Interrupted while waiting for id renewer thread to finish", e);
        }

        for (Future<?> closeBlocker : closeBlockers) {
            try {
                closeBlocker.get();
            } catch (InterruptedException e) {
                throw new JanusGraphException(
                        "Interrupted while waiting for runaway ID renewer task " + closeBlocker, e);
            } catch (ExecutionException e) {
                log.debug("Runaway ID renewer task completed with exception", e);
            }
        }
        exec.shutdownNow();
    }

    private synchronized void startIDBlockGetter() {
        Preconditions.checkArgument(idBlockFuture == null, idBlockFuture);
        if (closed)
            return; //Don't renew anymore if closed
        //Renew buffer
        log.debug("Starting id block renewal thread upon {}", currentIndex);
        idBlockGetter = new IDBlockGetter(idAuthority, partition, idNamespace, renewTimeout);
        idBlockFuture = exec.submit(idBlockGetter);
    }

    private static class IDBlockGetter implements Callable<IDBlock> {

        private final Stopwatch alive;
        private final IDAuthority idAuthority;
        private final int partition;
        private final int idNamespace;
        private final Duration renewTimeout;
        private volatile boolean stopRequested;

        public IDBlockGetter(IDAuthority idAuthority, int partition, int idNamespace, Duration renewTimeout) {
            this.idAuthority = idAuthority;
            this.partition = partition;
            this.idNamespace = idNamespace;
            this.renewTimeout = renewTimeout;
            this.alive = Stopwatch.createStarted();
        }

        private void stopRequested() {
            this.stopRequested = true;
        }

        @Override
        public IDBlock call() {
            Stopwatch running = Stopwatch.createStarted();

            try {
                if (stopRequested) {
                    log.debug(
                            "Aborting ID block retrieval on partition({})-namespace({}) after "
                                    + "graceful shutdown was requested, exec time {}, exec+q time {}",
                            partition, idNamespace, running.stop(), alive.stop());
                    throw new JanusGraphException("ID block retrieval aborted by caller");
                }
                IDBlock idBlock = idAuthority.getIDBlock(partition, idNamespace, renewTimeout);
                log.debug(
                        "Retrieved ID block from authority on partition({})-namespace({}), "
                                + "exec time {}, exec+q time {}",
                        partition, idNamespace, running.stop(), alive.stop());
                Preconditions.checkArgument(idBlock != null && idBlock.numIds() > 0);
                return idBlock;
            } catch (BackendException e) {
                throw new JanusGraphException("Could not acquire new ID block from storage", e);
            } catch (IDPoolExhaustedException e) {
                return ID_POOL_EXHAUSTION;
            }
        }
    }
}