@Override public void mutateMany(Map<String, Map<StaticBuffer, KCVMutation>> mutations, StoreTransaction txh) throws BackendException { ExpectedValueCheckingTransaction etx = (ExpectedValueCheckingTransaction)txh; boolean hasAtLeastOneLock = etx.prepareForMutations(); if (hasAtLeastOneLock) { // Force all mutations on this transaction to use strong consistency log.debug("Transaction {} holds one or more locks: writing using consistent transaction {} due to held locks", etx, etx.getConsistentTx()); manager.mutateMany(mutations, etx.getConsistentTx()); } else { log.debug("Transaction {} holds no locks: writing mutations using store transaction {}", etx, etx.getInconsistentTx()); manager.mutateMany(mutations, etx.getInconsistentTx()); } }
/** * If {@code !}{@link #isMutationStarted()}, check all locks and expected * values, then mark the transaction as started. * <p> * If {@link #isMutationStarted()}, this does nothing. * * @throws com.thinkaurelius.titan.diskstorage.BackendException * * @return true if this transaction holds at least one lock, false if the * transaction holds no locks */ boolean prepareForMutations() throws BackendException { if (!isMutationStarted()) { checkAllLocks(); checkAllExpectedValues(); mutationStarted(); } return !expectedValuesByStore.isEmpty(); }
/** * Check that all expected values saved from earlier * {@link KeyColumnValueStore#acquireLock(StaticBuffer, StaticBuffer, StaticBuffer, StoreTransaction)} * calls using this transaction. * * @throws com.thinkaurelius.titan.diskstorage.BackendException */ void checkAllExpectedValues() throws BackendException { for (final ExpectedValueCheckingStore store : expectedValuesByStore.keySet()) { final Map<KeyColumn, StaticBuffer> m = expectedValuesByStore.get(store); for (final KeyColumn kc : m.keySet()) { checkSingleExpectedValue(kc, m.get(kc), store); } } }
/** * {@inheritDoc} * <p/> * This implementation supports locking when {@code lockStore} is non-null. * <p/> * Consider the following scenario. This method is called twice with * identical key, column, and txh arguments, but with different * expectedValue arguments in each call. In testing, it seems titan's * graphdb requires that implementations discard the second expectedValue * and, when checking expectedValues vs actual values just prior to mutate, * only the initial expectedValue argument should be considered. */ @Override public void acquireLock(StaticBuffer key, StaticBuffer column, StaticBuffer expectedValue, StoreTransaction txh) throws BackendException { if (locker != null) { ExpectedValueCheckingTransaction tx = (ExpectedValueCheckingTransaction) txh; if (tx.isMutationStarted()) throw new PermanentLockingException("Attempted to obtain a lock after mutations had been persisted"); KeyColumn lockID = new KeyColumn(key, column); log.debug("Attempting to acquireLock on {} ev={}", lockID, expectedValue); locker.writeLock(lockID, tx.getConsistentTx()); tx.storeExpectedValue(this, lockID, expectedValue); } else { store.acquireLock(key, column, expectedValue, unwrapTx(txh)); } }
@Override public ExpectedValueCheckingTransaction beginTransaction(BaseTransactionConfig configuration) throws BackendException { // Get a transaction without any guarantees about strong consistency StoreTransaction inconsistentTx = manager.beginTransaction(configuration); // Get a transaction that provides global strong consistency Configuration customOptions = new MergedConfiguration(storeFeatures.getKeyConsistentTxConfig(), configuration.getCustomOptions()); BaseTransactionConfig consistentTxCfg = new StandardBaseTransactionConfig.Builder(configuration) .customOptions(customOptions).build(); StoreTransaction strongConsistentTx = manager.beginTransaction(consistentTxCfg); // Return a wrapper around both the inconsistent and consistent store transactions ExpectedValueCheckingTransaction wrappedTx = new ExpectedValueCheckingTransaction(inconsistentTx, strongConsistentTx, maxReadTime); return wrappedTx; }
private static StoreTransaction getConsistentTx(StoreTransaction t) { assert null != t; assert t instanceof ExpectedValueCheckingTransaction; return ((ExpectedValueCheckingTransaction) t).getConsistentTx(); } }
@Override public void commit() throws BackendException { inconsistentTx.commit(); deleteAllLocks(); strongConsistentTx.commit(); }
protected StoreTransaction unwrapTx(StoreTransaction t) { assert null != t; assert t instanceof ExpectedValueCheckingTransaction; return ((ExpectedValueCheckingTransaction) t).getInconsistentTx(); }
void storeExpectedValue(ExpectedValueCheckingStore store, KeyColumn lockID, StaticBuffer value) { Preconditions.checkNotNull(store); Preconditions.checkNotNull(lockID); lockedOn(store); Map<KeyColumn, StaticBuffer> m = expectedValuesByStore.get(store); assert null != m; if (m.containsKey(lockID)) { log.debug("Multiple expected values for {}: keeping initial value {} and discarding later value {}", new Object[]{lockID, m.get(lockID), value}); } else { m.put(lockID, value); log.debug("Store expected value for {}: {}", lockID, value); } }
public StoreTransaction newTransaction(KeyColumnValueStoreManager manager) throws BackendException { StoreTransaction transaction = manager.beginTransaction(getTxConfig()); if (!manager.getFeatures().hasLocking() && manager.getFeatures().isKeyConsistent()) { transaction = new ExpectedValueCheckingTransaction(transaction, manager.beginTransaction(getConsistentTxConfig(manager)), GraphDatabaseConfiguration.STORAGE_READ_WAITTIME.getDefaultValue()); } return transaction; }
/** * {@inheritDoc} * <p/> * This implementation supports locking when {@code lockStore} is non-null. * <p/> * Consider the following scenario. This method is called twice with * identical key, column, and txh arguments, but with different * expectedValue arguments in each call. In testing, it seems titan's * graphdb requires that implementations discard the second expectedValue * and, when checking expectedValues vs actual values just prior to mutate, * only the initial expectedValue argument should be considered. */ @Override public void acquireLock(StaticBuffer key, StaticBuffer column, StaticBuffer expectedValue, StoreTransaction txh) throws BackendException { if (locker != null) { ExpectedValueCheckingTransaction tx = (ExpectedValueCheckingTransaction) txh; if (tx.isMutationStarted()) throw new PermanentLockingException("Attempted to obtain a lock after mutations had been persisted"); KeyColumn lockID = new KeyColumn(key, column); log.debug("Attempting to acquireLock on {} ev={}", lockID, expectedValue); locker.writeLock(lockID, tx.getConsistentTx()); tx.storeExpectedValue(this, lockID, expectedValue); } else { store.acquireLock(key, column, expectedValue, unwrapTx(txh)); } }
void deleteLocks(ExpectedValueCheckingTransaction tx) throws BackendException { locker.deleteLocks(tx.getConsistentTx()); }
@Override public void rollback() throws BackendException { deleteAllLocks(); inconsistentTx.rollback(); strongConsistentTx.rollback(); }
protected StoreTransaction unwrapTx(StoreTransaction t) { assert null != t; assert t instanceof ExpectedValueCheckingTransaction; return ((ExpectedValueCheckingTransaction) t).getInconsistentTx(); }
void storeExpectedValue(ExpectedValueCheckingStore store, KeyColumn lockID, StaticBuffer value) { Preconditions.checkNotNull(store); Preconditions.checkNotNull(lockID); lockedOn(store); Map<KeyColumn, StaticBuffer> m = expectedValuesByStore.get(store); assert null != m; if (m.containsKey(lockID)) { log.debug("Multiple expected values for {}: keeping initial value {} and discarding later value {}", new Object[]{lockID, m.get(lockID), value}); } else { m.put(lockID, value); log.debug("Store expected value for {}: {}", lockID, value); } }
public void open() throws BackendException { manager = new KeyColumnValueStoreManager[CONCURRENCY]; tx = new StoreTransaction[CONCURRENCY][NUM_TX]; store = new KeyColumnValueStore[CONCURRENCY]; for (int i = 0; i < CONCURRENCY; i++) { manager[i] = openStorageManager(i); StoreFeatures storeFeatures = manager[i].getFeatures(); store[i] = manager[i].openDatabase(DB_NAME); for (int j = 0; j < NUM_TX; j++) { tx[i][j] = manager[i].beginTransaction(getTxConfig()); log.debug("Began transaction of class {}", tx[i][j].getClass().getCanonicalName()); } ModifiableConfiguration sc = GraphDatabaseConfiguration.buildGraphConfiguration(); sc.set(GraphDatabaseConfiguration.LOCK_LOCAL_MEDIATOR_GROUP,concreteClassName + i); sc.set(GraphDatabaseConfiguration.UNIQUE_INSTANCE_ID,"inst"+i); sc.set(GraphDatabaseConfiguration.LOCK_RETRY,10); sc.set(GraphDatabaseConfiguration.LOCK_EXPIRE, Duration.ofMillis(EXPIRE_MS)); if (!storeFeatures.hasLocking()) { Preconditions.checkArgument(storeFeatures.isKeyConsistent(),"Store needs to support some form of locking"); KeyColumnValueStore lockerStore = manager[i].openDatabase(DB_NAME + "_lock_"); ConsistentKeyLocker c = new ConsistentKeyLocker.Builder(lockerStore, manager[i]).fromConfig(sc).mediatorName(concreteClassName + i).build(); store[i] = new ExpectedValueCheckingStore(store[i], c); for (int j = 0; j < NUM_TX; j++) tx[i][j] = new ExpectedValueCheckingTransaction(tx[i][j], manager[i].beginTransaction(getConsistentTxConfig(manager[i])), GraphDatabaseConfiguration.STORAGE_READ_WAITTIME.getDefaultValue()); } } }
@Override public void mutateMany(Map<String, Map<StaticBuffer, KCVMutation>> mutations, StoreTransaction txh) throws BackendException { ExpectedValueCheckingTransaction etx = (ExpectedValueCheckingTransaction)txh; boolean hasAtLeastOneLock = etx.prepareForMutations(); if (hasAtLeastOneLock) { // Force all mutations on this transaction to use strong consistency log.debug("Transaction {} holds one or more locks: writing using consistent transaction {} due to held locks", etx, etx.getConsistentTx()); manager.mutateMany(mutations, etx.getConsistentTx()); } else { log.debug("Transaction {} holds no locks: writing mutations using store transaction {}", etx, etx.getInconsistentTx()); manager.mutateMany(mutations, etx.getInconsistentTx()); } }
/** * If {@code !}{@link #isMutationStarted()}, check all locks and expected * values, then mark the transaction as started. * <p> * If {@link #isMutationStarted()}, this does nothing. * * @throws com.thinkaurelius.titan.diskstorage.BackendException * * @return true if this transaction holds at least one lock, false if the * transaction holds no locks */ boolean prepareForMutations() throws BackendException { if (!isMutationStarted()) { checkAllLocks(); checkAllExpectedValues(); mutationStarted(); } return !expectedValuesByStore.isEmpty(); }
/** * {@inheritDoc} * <p/> * This implementation supports locking when {@code lockStore} is non-null. * <p/> * Consider the following scenario. This method is called twice with * identical key, column, and txh arguments, but with different * expectedValue arguments in each call. In testing, it seems titan's * graphdb requires that implementations discard the second expectedValue * and, when checking expectedValues vs actual values just prior to mutate, * only the initial expectedValue argument should be considered. */ @Override public void acquireLock(StaticBuffer key, StaticBuffer column, StaticBuffer expectedValue, StoreTransaction txh) throws BackendException { if (locker != null) { ExpectedValueCheckingTransaction tx = (ExpectedValueCheckingTransaction) txh; if (tx.isMutationStarted()) throw new PermanentLockingException("Attempted to obtain a lock after mutations had been persisted"); KeyColumn lockID = new KeyColumn(key, column); log.debug("Attempting to acquireLock on {} ev={}", lockID, expectedValue); locker.writeLock(lockID, tx.getConsistentTx()); tx.storeExpectedValue(this, lockID, expectedValue); } else { store.acquireLock(key, column, expectedValue, unwrapTx(txh)); } }
/** * Check all locks attempted by earlier * {@link KeyColumnValueStore#acquireLock(StaticBuffer, StaticBuffer, StaticBuffer, StoreTransaction)} * calls using this transaction. * * @throws com.thinkaurelius.titan.diskstorage.BackendException */ void checkAllLocks() throws BackendException { StoreTransaction lt = getConsistentTx(); for (ExpectedValueCheckingStore store : expectedValuesByStore.keySet()) { Locker locker = store.getLocker(); // Ignore locks on stores without a locker if (null == locker) continue; locker.checkLocks(lt); } }