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

org.jsimpledb.kv.simple.SimpleKVTransaction Maven / Gradle / Ivy

There is a newer version: 3.6.1
Show newest version

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

package org.jsimpledb.kv.simple;

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

import java.util.Arrays;
import java.util.SortedSet;
import java.util.TreeSet;

import org.jsimpledb.kv.AbstractKVStore;
import org.jsimpledb.kv.CloseableKVStore;
import org.jsimpledb.kv.KVPair;
import org.jsimpledb.kv.KVPairIterator;
import org.jsimpledb.kv.KVTransaction;
import org.jsimpledb.kv.KeyRange;
import org.jsimpledb.kv.mvcc.AtomicKVStore;
import org.jsimpledb.kv.mvcc.LockOwner;
import org.jsimpledb.kv.mvcc.MutableView;
import org.jsimpledb.kv.util.CloseableForwardingKVStore;
import org.jsimpledb.kv.util.NavigableMapKVStore;
import org.jsimpledb.util.ByteUtil;
import org.jsimpledb.util.CloseableIterator;
import org.slf4j.LoggerFactory;

/**
 * {@link KVTransaction} implementation for {@link SimpleKVDatabase}.
 *
 * 

* Locking note: all fields in this class are protected by the Java monitor of the associated {@link SimpleKVDatabase}, * not the Java monitor of this instance. */ public class SimpleKVTransaction extends AbstractKVStore implements KVTransaction { final SimpleKVDatabase kvdb; final TreeSet mutations = new TreeSet<>(KeyRange.SORT_BY_MIN); final LockOwner lockOwner = new LockOwner(); boolean stale; long waitTimeout; private volatile boolean readOnly; /** * Constructor. * * @param kvdb associated database * @param waitTimeout wait timeout for this transaction * @throws IllegalArgumentException if {@code kvdb} is null * @throws IllegalArgumentException if {@code waitTimeout} is negative */ protected SimpleKVTransaction(SimpleKVDatabase kvdb, long waitTimeout) { Preconditions.checkArgument(kvdb != null, "null kvdb"); this.kvdb = kvdb; this.setTimeout(waitTimeout); } @Override public SimpleKVDatabase getKVDatabase() { return this.kvdb; } @Override public void setTimeout(long timeout) { Preconditions.checkArgument(timeout >= 0, "timeout < 0"); this.waitTimeout = timeout; } @Override public ListenableFuture watchKey(byte[] key) { return this.kvdb.watchKey(key); } @Override public byte[] get(byte[] key) { return this.kvdb.get(this, key); } @Override public KVPair getAtLeast(byte[] min, byte[] max) { return this.kvdb.getAtLeast(this, min, max); } @Override public KVPair getAtMost(byte[] max, byte[] min) { return this.kvdb.getAtMost(this, max, min); } @Override public CloseableIterator getRange(byte[] minKey, byte[] maxKey, boolean reverse) { if (minKey == null) minKey = ByteUtil.EMPTY; return new KVPairIterator(this, new KeyRange(minKey, maxKey), null, reverse); } @Override public void put(byte[] key, byte[] value) { this.kvdb.put(this, key, value); } @Override public void remove(byte[] key) { this.kvdb.remove(this, key); } @Override public void removeRange(byte[] minKey, byte[] maxKey) { this.kvdb.removeRange(this, minKey, maxKey); } @Override public boolean isReadOnly() { return this.readOnly; } @Override public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } @Override public void commit() { this.kvdb.commit(this, this.readOnly); } @Override public void rollback() { this.kvdb.rollback(this); } @Override public CloseableKVStore mutableSnapshot() { // Build copy CloseableKVStore kvstore; if (this.kvdb.kv instanceof NavigableMapKVStore) { final NavigableMapKVStore kv; synchronized (this.kvdb) { kv = ((NavigableMapKVStore)this.kvdb.kv).clone(); } kvstore = new CloseableForwardingKVStore(kv.clone()); } else if (this.kvdb.kv instanceof AtomicKVStore) { final AtomicKVStore kv = (AtomicKVStore)this.kvdb.kv; final CloseableKVStore snapshot = kv.snapshot(); final MutableView view = new MutableView(snapshot); view.disableReadTracking(); kvstore = new CloseableForwardingKVStore(view, snapshot); } else { throw new UnsupportedOperationException("underlying KVStore " + this.kvdb.kv.getClass().getSimpleName() + " is not an AtomicKVStore"); } // Apply mutations synchronized (this.kvdb) { for (Mutation mutation : this.mutations) mutation.apply(kvstore); } // Done return kvstore; } // Find the mutation that overlaps with the given key, if any. // This method assumes we are already synchronized on the associated database. Mutation findMutation(byte[] key) { // Sanity check during unit testing assert Thread.holdsLock(this.kvdb); assert !this.hasOverlaps() && !this.hasEmpties(); // Get all mutations starting at or prior to `key' and look for overlap final SortedSet left = this.mutations.headSet(Mutation.key(ByteUtil.getNextKey(key))); if (!left.isEmpty()) { final Mutation last = left.last(); if (last.contains(key)) return last; } return null; } /** * Ensure transaction is eventually rolled back if leaked due to an application bug. */ @Override protected void finalize() throws Throwable { try { if (!this.stale) LoggerFactory.getLogger(this.getClass()).warn(this + " leaked without commit() or rollback()"); this.rollback(); } finally { super.finalize(); } } private boolean hasEmpties() { for (Mutation mutation : this.mutations) { final byte[] minKey = mutation.getMin(); final byte[] maxKey = mutation.getMax(); if (minKey != null && maxKey != null && Arrays.equals(minKey, maxKey)) return true; } return false; } private boolean hasOverlaps() { Mutation previous = null; for (Mutation mutation : this.mutations) { if (previous != null && mutation.overlaps(previous)) return true; previous = mutation; } return false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy