All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.permazen.kv.raft.fallback.FallbackKVTransaction Maven / Gradle / Ivy


/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.kv.raft.fallback;

import com.google.common.util.concurrent.ListenableFuture;

import io.permazen.kv.CloseableKVStore;
import io.permazen.kv.KVStore;
import io.permazen.kv.KVTransaction;
import io.permazen.kv.RetryKVTransactionException;
import io.permazen.kv.StaleKVTransactionException;
import io.permazen.kv.util.ForwardingKVStore;

import java.util.ArrayList;

/**
 * A {@link KVTransaction} associated with a {@link FallbackKVDatabase}.
 *
 * @see FallbackKVDatabase
 */
public class FallbackKVTransaction extends ForwardingKVStore implements KVTransaction {

    private final FallbackKVDatabase db;
    private final KVTransaction kvt;
    private final int migrationCount;

    private ArrayList futureList;

    private boolean stale;              // protected by this.db's monitor

    FallbackKVTransaction(FallbackKVDatabase db, KVTransaction kvt, int migrationCount) {
        this.db = db;
        this.kvt = kvt;
        this.migrationCount = migrationCount;
    }

// ForwardingKVStore

    /**
     * Get the underlying transaction, which will be associated with either one of the fallback
     * databases, or the standalone mode database.
     *
     * @return underlying transaction
     */
    public KVTransaction getKVTransaction() {
        return this.kvt;
    }

    @Override
    protected KVStore delegate() {
        return this.kvt;
    }

// KVTransaction

    @Override
    public FallbackKVDatabase getKVDatabase() {
        return this.db;
    }

    @Override
    public void setTimeout(long timeout) {
        this.kvt.setTimeout(timeout);
    }

    @Override
    public ListenableFuture watchKey(byte[] key) {

        // Check freshness
        synchronized (this.db) {
            if (this.stale)
                throw new StaleKVTransactionException(this);
        }

        // Get target's future - it must be a ListenableFuture or we can't do this
        final ListenableFuture innerFuture;
        try {
            innerFuture = (ListenableFuture)this.kvt.watchKey(key);
        } catch (ClassCastException e) {
            throw new UnsupportedOperationException("nested transaction does not support ListenableFuture's", e);
        }

        // Create outer future
        final FallbackKVDatabase.FallbackFuture outerFuture = this.db.new FallbackFuture(innerFuture);

        // Add outer future to our pending list
        synchronized (this) {
            if (this.futureList == null)
                this.futureList = new ArrayList<>();
            this.futureList.add(outerFuture);
        }

        // Done
        return outerFuture;
    }

    @Override
    public CloseableKVStore readOnlySnapshot() {
        return this.kvt.readOnlySnapshot();
    }

    @Override
    public boolean isReadOnly() {
        return this.kvt.isReadOnly();
    }

    @Override
    public void setReadOnly(boolean readOnly) {
        this.kvt.setReadOnly(readOnly);
    }

    @Override
    public void commit() {

        // Check freshness
        synchronized (this.db) {
            if (this.stale)
                throw new StaleKVTransactionException(this);
            this.stale = true;
        }

        // Check to see if migration occurred
        this.retryIfMigrating();

        // Commit nested transaction
        this.kvt.commit();

        // Snapshot our pending futures (if any)
        final ArrayList fallbackFutures;
        synchronized (this) {
            fallbackFutures = this.futureList;
            this.futureList = null;
        }

        // Check again to see if migration occurred - this is required to close
        // a race where the database could appear to revert to an earlier version
        this.retryIfMigrating();

        // Register futures, but if they are already out-of-date, trigger spurious notifications instead
        if (fallbackFutures != null && !this.db.registerFallbackFutures(fallbackFutures, this.migrationCount)) {
            for (FallbackKVDatabase.FallbackFuture future : fallbackFutures)
                future.set(null);
        }
    }

    @Override
    public void rollback() {
        synchronized (this.db) {
            this.stale = true;
            this.kvt.rollback();
        }
    }

    private void retryIfMigrating() {
        if (!this.db.checkNoMigration(this.migrationCount)) {
            this.rollback();
            throw new RetryKVTransactionException(this, "fallback migration in progress");
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy