@Test public void testMutateManyWithLockUsesConsistentTx() throws BackendException { final ImmutableList<Entry> adds = ImmutableList.of(StaticArrayEntry.of(DATA_COL, DATA_VAL)); final ImmutableList<StaticBuffer> deletions = ImmutableList.of(); Map<String, Map<StaticBuffer, KCVMutation>> mutations = ImmutableMap.of(STORE_NAME, ImmutableMap.of(DATA_KEY, new KCVMutation(adds, deletions))); final KeyColumn kc = new KeyColumn(LOCK_KEY, LOCK_COL); // Acquire a lock backingLocker.writeLock(kc, consistentTx); // 2. Run mutateMany // 2.1. Check locks & expected values before mutating data backingLocker.checkLocks(consistentTx); StaticBuffer nextBuf = BufferUtil.nextBiggerBuffer(kc.getColumn()); KeySliceQuery expectedValueQuery = new KeySliceQuery(kc.getKey(), kc.getColumn(), nextBuf); expect(backingStore.getSlice(expectedValueQuery, consistentTx)) // expected value read must use strong consistency .andReturn(StaticArrayEntryList.of(StaticArrayEntry.of(LOCK_COL, LOCK_VAL))); // 2.2. Run mutateMany on backing manager to modify data backingManager.mutateMany(mutations, consistentTx); // writes by txs with locks must use strong consistency ctrl.replay(); // Lock acquisition expectStore.acquireLock(LOCK_KEY, LOCK_COL, LOCK_VAL, expectTx); // Mutate expectManager.mutateMany(mutations, expectTx); }
if (other.target != null) return false; } else if (!target.equals(other.target)) return false; if (tx == null) {
@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((cutoff == null) ? 0 : cutoff.hashCode()); result = prime * result + ((serializer == null) ? 0 : serializer.hashCode()); result = prime * result + ((store == null) ? 0 : store.hashCode()); result = prime * result + ((target == null) ? 0 : target.hashCode()); result = prime * result + ((tx == null) ? 0 : tx.hashCode()); return result; }
@Override protected void deleteSingleLock(KeyColumn kc, ConsistentKeyLockStatus ls, StoreTransaction tx) { List<StaticBuffer> deletions = ImmutableList.of(serializer.toLockCol(ls.getWriteTimestamp(), rid, times)); for (int i = 0; i < lockRetryCount; i++) { try { StoreTransaction newTx = overrideTimestamp(tx, times.getTime()); store.mutate(serializer.toLockKey(kc.getKey(), kc.getColumn()), ImmutableList.of(), deletions, newTx); return; } catch (TemporaryBackendException e) { log.warn("Temporary storage exception while deleting lock", e); // don't return -- iterate and retry } catch (BackendException e) { log.error("Storage exception while deleting lock", e); return; // give up on this lock } } }
KeySliceQuery ksq = new KeySliceQuery(serializer.toLockKey(kc.getKey(), kc.getColumn()), LOCK_COL_START, LOCK_COL_END); List<Entry> claimEntries = getSliceWithRetries(ksq, tx); throw new ExpiredLockException("Expired lock on " + kc.toString() + ": lock timestamp " + tr.getTimestamp() + " " + times.getUnit() + " is older than " + ConfigElement.getPath(GraphDatabaseConfiguration.LOCK_EXPIRE) + "=" + lockExpire);
/** * {@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 JanusGraph'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)); } }
private void checkSingleExpectedValueUnsafe(final KeyColumn kc, final StaticBuffer ev, final ExpectedValueCheckingStore store) throws BackendException { final StaticBuffer nextBuf = BufferUtil.nextBiggerBuffer(kc.getColumn()); KeySliceQuery ksq = new KeySliceQuery(kc.getKey(), kc.getColumn(), nextBuf); if (!input.getColumn().equals(kc.getColumn())) { log.debug("Dropping entry {} (only accepting column {})", input, kc.getColumn()); return false; final StaticBuffer actualCol = e.getColumnAs(StaticBuffer.STATIC_FACTORY); assert null != actualCol; assert null != kc.getColumn(); assert 0 >= kc.getColumn().compareTo(actualCol); assert 0 > actualCol.compareTo(nextBuf); return e.getValueAs(StaticBuffer.STATIC_FACTORY);
mockLocker.writeLock(eq(new KeyColumn(key, col)), eq(tx.getConsistentTx())); expectLastCall().times(numStores / 3 * 2);
@Test public void testMutateWithLockUsesConsistentTx() throws BackendException { final ImmutableList<Entry> adds = ImmutableList.of(StaticArrayEntry.of(DATA_COL, DATA_VAL)); final ImmutableList<StaticBuffer> deletions = ImmutableList.of(); final KeyColumn kc = new KeyColumn(LOCK_KEY, LOCK_COL); // 1. Acquire a lock backingLocker.writeLock(kc, consistentTx); // 2. Run a mutation // N.B. mutation coordinates do not overlap with the lock, but consistentTx should be used anyway // 2.1. Check locks & expected values before mutating data backingLocker.checkLocks(consistentTx); StaticBuffer nextBuf = BufferUtil.nextBiggerBuffer(kc.getColumn()); KeySliceQuery expectedValueQuery = new KeySliceQuery(kc.getKey(), kc.getColumn(), nextBuf); expect(backingStore.getSlice(expectedValueQuery, consistentTx)) // expected value read must use strong consistency .andReturn(StaticArrayEntryList.of(StaticArrayEntry.of(LOCK_COL, LOCK_VAL))); // 2.2. Mutate data backingStore.mutate(DATA_KEY, adds, deletions, consistentTx); // writes by txs with locks must use strong consistency ctrl.replay(); // 1. Lock acquisition expectStore.acquireLock(LOCK_KEY, LOCK_COL, LOCK_VAL, expectTx); // 2. Mutate expectStore.mutate(DATA_KEY, adds, deletions, expectTx); }
private void runWithExceptions() throws BackendException { StaticBuffer lockKey = serializer.toLockKey(target.getKey(), target.getColumn()); List<Entry> locks = store.getSlice(new KeySliceQuery(lockKey, LOCK_COL_START, LOCK_COL_END), tx); // TODO reduce LOCK_COL_END based on cutoff ImmutableList.Builder<StaticBuffer> b = ImmutableList.builder(); for (Entry lc : locks) { TimestampRid tr = serializer.fromLockColumn(lc.getColumn(), times); if (tr.getTimestamp().isBefore(cutoff)) { log.info("Deleting expired lock on {} by rid {} with timestamp {} (before or at cutoff {})", target, tr.getRid(), tr.getTimestamp(), cutoff); b.add(lc.getColumn()); } else { log.debug("Ignoring lock on {} by rid {} with timestamp {} (timestamp is after cutoff {})", target, tr.getRid(), tr.getTimestamp(), cutoff); } } List<StaticBuffer> deletions = b.build(); if (!deletions.isEmpty()) { store.mutate(lockKey, ImmutableList.of(), deletions, tx); log.info("Deleted {} expired locks (before or at cutoff {})", deletions.size(), cutoff); } }