@Override public LockImmutableTimestampResponse lockImmutableTimestamp(IdentifiedTimeLockRequest request) { long timestamp = timestampService.getFreshTimestamp(); // this will always return synchronously LockToken token = lockService.lockImmutableTimestamp(request.getRequestId(), timestamp).get(); long immutableTs = lockService.getImmutableTimestamp().orElse(timestamp); return LockImmutableTimestampResponse.of(immutableTs, token); }
@Override public LockImmutableTimestampResponse lockImmutableTimestamp(IdentifiedTimeLockRequest request) { LockImmutableTimestampResponse response = executeOnTimeLock(() -> delegate.lockImmutableTimestamp(request)); lockRefresher.registerLock(response.getLock()); return response; }
@Override public TransactionAndImmutableTsLock setupRunTaskWithConditionThrowOnConflict(PreCommitCondition condition) { StartIdentifiedAtlasDbTransactionResponse transactionResponse = timelockService.startIdentifiedAtlasDbTransaction( StartIdentifiedAtlasDbTransactionRequest.createForRequestor(clientId)); try { LockToken immutableTsLock = transactionResponse.immutableTimestamp().getLock(); long immutableTs = transactionResponse.immutableTimestamp().getImmutableTimestamp(); recordImmutableTimestamp(immutableTs); cleaner.punch(transactionResponse.startTimestampAndPartition().timestamp()); Supplier<Long> startTimestampSupplier = Suppliers.ofInstance( transactionResponse.startTimestampAndPartition().timestamp()); SnapshotTransaction transaction = createTransaction(immutableTs, startTimestampSupplier, immutableTsLock, condition); return TransactionAndImmutableTsLock.of(transaction, immutableTsLock); } catch (Throwable e) { timelockService.tryUnlock(ImmutableSet.of(transactionResponse.immutableTimestamp().getLock())); throw Throwables.rewrapAndThrowUncheckedException(e); } }
@Override public StartIdentifiedAtlasDbTransactionResponse startIdentifiedAtlasDbTransaction( StartIdentifiedAtlasDbTransactionRequest request) { StartIdentifiedAtlasDbTransactionResponse response = executeOnTimeLock( () -> delegate.startIdentifiedAtlasDbTransaction(request)); lockRefresher.registerLock(response.immutableTimestamp().getLock()); return response; }
@Test public void registersImmutableTimestampLock() { when(delegate.lockImmutableTimestamp(any())).thenReturn(LockImmutableTimestampResponse.of(123L, TOKEN_1)); timelock.lockImmutableTimestamp(IdentifiedTimeLockRequest.create()); verify(refresher).registerLock(TOKEN_1); }
ImmutableMap.of(TABLE, ConflictHandler.RETRY_ON_WRITE_WRITE)), SweepStrategyManagers.createDefault(keyValueService), lockImmutableTimestampResponse.getImmutableTimestamp(), Optional.of(lockImmutableTimestampResponse.getLock()), preCommitCondition, AtlasDbConstraintCheckingMode.NO_CONSTRAINT_CHECKING,
@Override public StartAtlasDbTransactionResponse startAtlasDbTransaction(IdentifiedTimeLockRequest request) { StartAtlasDbTransactionResponse response = executeOnTimeLock(() -> delegate.startAtlasDbTransaction(request)); lockRefresher.registerLock(response.immutableTimestamp().getLock()); return response; }
@Override public TransactionAndImmutableTsLock setupRunTaskWithConditionThrowOnConflict(PreCommitCondition condition) { StartIdentifiedAtlasDbTransactionResponse transactionResponse = timelockService.startIdentifiedAtlasDbTransaction( StartIdentifiedAtlasDbTransactionRequest.createForRequestor(clientId)); try { LockToken immutableTsLock = transactionResponse.immutableTimestamp().getLock(); long immutableTs = transactionResponse.immutableTimestamp().getImmutableTimestamp(); recordImmutableTimestamp(immutableTs); cleaner.punch(transactionResponse.startTimestampAndPartition().timestamp()); Supplier<Long> startTimestampSupplier = Suppliers.ofInstance( transactionResponse.startTimestampAndPartition().timestamp()); SnapshotTransaction transaction = createTransaction(immutableTs, startTimestampSupplier, immutableTsLock, condition); return TransactionAndImmutableTsLock.of(transaction, immutableTsLock); } catch (Throwable e) { timelockService.tryUnlock(ImmutableSet.of(transactionResponse.immutableTimestamp().getLock())); throw Throwables.rewrapAndThrowUncheckedException(e); } }
@Test public void checkImmutableTsLockOnceIfThoroughlySwept_WithValidationOnReads() { TimelockService timelockService = spy(new LegacyTimelockService(timestampService, lockService, lockClient)); long transactionTs = timelockService.getFreshTimestamp(); LockImmutableTimestampResponse res = timelockService.lockImmutableTimestamp(IdentifiedTimeLockRequest.create()); SnapshotTransaction transaction = getSnapshotTransactionWith( timelockService, () -> transactionTs, res, PreCommitConditions.NO_OP, true); transaction.get(TABLE_SWEPT_THOROUGH, ImmutableSet.of(TEST_CELL)); transaction.commit(); timelockService.unlock(ImmutableSet.of(res.getLock())); verify(timelockService).refreshLockLeases(ImmutableSet.of(res.getLock())); }
@Test public void lockImmutableTimestampLocksFreshTimestamp() throws InterruptedException { long immutableTs = 3L; LockRefreshToken expectedToken = mockImmutableTsLockResponse(); mockMinLockedInVersionIdResponse(immutableTs); LockImmutableTimestampResponse expectedResponse = LockImmutableTimestampResponse.of(immutableTs, toTokenV2(expectedToken)); assertEquals(expectedResponse, timelock.lockImmutableTimestamp(IdentifiedTimeLockRequest.create())); }
@Test public void checkImmutableTsLockOnceIfThoroughlySwept_WithoutValidationOnReads() { TimelockService timelockService = spy(new LegacyTimelockService(timestampService, lockService, lockClient)); long transactionTs = timelockService.getFreshTimestamp(); LockImmutableTimestampResponse res = timelockService.lockImmutableTimestamp(IdentifiedTimeLockRequest.create()); SnapshotTransaction transaction = getSnapshotTransactionWith( timelockService, () -> transactionTs, res, PreCommitConditions.NO_OP, false); transaction.get(TABLE_SWEPT_THOROUGH, ImmutableSet.of(TEST_CELL)); transaction.commit(); timelockService.unlock(ImmutableSet.of(res.getLock())); verify(timelockService).refreshLockLeases(ImmutableSet.of(res.getLock())); }
@Override public LockImmutableTimestampResponse lockImmutableTimestamp(IdentifiedTimeLockRequest request) { long immutableLockTs = timestampService.getFreshTimestamp(); LockDescriptor lockDesc = AtlasTimestampLockDescriptor.of(immutableLockTs); com.palantir.lock.LockRequest lockRequest = com.palantir.lock.LockRequest.builder( ImmutableSortedMap.of(lockDesc, LockMode.READ)) .withLockedInVersionId(immutableLockTs).build(); LockRefreshToken lock; try { lock = lockService.lock(immutableTsLockClient.getClientId(), lockRequest); } catch (InterruptedException e) { throw Throwables.throwUncheckedException(e); } try { return LockImmutableTimestampResponse.of( getImmutableTimestampInternal(immutableLockTs), LockTokenConverter.toTokenV2(lock)); } catch (Throwable e) { if (lock != null) { lockService.unlock(lock); } throw Throwables.rewrapAndThrowUncheckedException(e); } }
@Test public void validateLocksOnReadsIfThoroughlySwept() { TimelockService timelockService = new LegacyTimelockService(timestampService, lockService, lockClient); long transactionTs = timelockService.getFreshTimestamp(); LockImmutableTimestampResponse res = timelockService.lockImmutableTimestamp(IdentifiedTimeLockRequest.create()); SnapshotTransaction transaction = getSnapshotTransactionWith( timelockService, () -> transactionTs, res, PreCommitConditions.NO_OP, true); timelockService.unlock(ImmutableSet.of(res.getLock())); assertThatExceptionOfType(TransactionLockTimeoutException.class).isThrownBy(() -> transaction.get(TABLE_SWEPT_THOROUGH, ImmutableSet.of(TEST_CELL))); }
@Override public LockImmutableTimestampResponse lockImmutableTimestamp(IdentifiedTimeLockRequest request) { long timestamp = timestampService.getFreshTimestamp(); // this will always return synchronously LockToken token = lockService.lockImmutableTimestamp(request.getRequestId(), timestamp).get(); long immutableTs = lockService.getImmutableTimestamp().orElse(timestamp); return LockImmutableTimestampResponse.of(immutableTs, token); }
@Test public void commitDoesNotThrowIfAlreadySuccessfullyCommitted() { final Cell cell = Cell.create(PtBytes.toBytes("row1"), PtBytes.toBytes("column1")); TimestampService timestampServiceSpy = spy(timestampService); TimelockService timelockService = new LegacyTimelockService(timestampServiceSpy, lockService, lockClient); long transactionTs = timelockService.getFreshTimestamp(); LockImmutableTimestampResponse res = timelockService.lockImmutableTimestamp(IdentifiedTimeLockRequest.create()); SnapshotTransaction snapshot = getSnapshotTransactionWith( timelockService, () -> transactionTs, res, PreCommitConditions.NO_OP); when(timestampServiceSpy.getFreshTimestamp()).thenReturn(10000000L); //forcing to try to commit a transaction that is already committed transactionService.putUnlessExists(transactionTs, timelockService.getFreshTimestamp()); snapshot.put(TABLE, ImmutableMap.of(cell, PtBytes.toBytes("value"))); snapshot.commit(); timelockService.unlock(Collections.singleton(res.getLock())); }
@Test public void validateLocksOnlyOnCommitIfValidationFlagIsFalse() { TimelockService timelockService = new LegacyTimelockService(timestampService, lockService, lockClient); long transactionTs = timelockService.getFreshTimestamp(); LockImmutableTimestampResponse res = timelockService.lockImmutableTimestamp(IdentifiedTimeLockRequest.create()); SnapshotTransaction transaction = getSnapshotTransactionWith( timelockService, () -> transactionTs, res, PreCommitConditions.NO_OP, false); timelockService.unlock(ImmutableSet.of(res.getLock())); transaction.get(TABLE_SWEPT_THOROUGH, ImmutableSet.of(TEST_CELL)); assertThatExceptionOfType(TransactionLockTimeoutException.class).isThrownBy(() -> transaction.commit()); }
@Test public void commitThrowsIfRolledBackAtCommitTime_expiredLocks() { final Cell cell = Cell.create(PtBytes.toBytes("row1"), PtBytes.toBytes("column1")); TimelockService timelockService = spy(new LegacyTimelockService(timestampService, lockService, lockClient)); // expire the locks when the pre-commit check happens - this is guaranteed to be after we've written the data PreCommitCondition condition = unused -> doReturn(ImmutableSet.of()).when(timelockService).refreshLockLeases(any()); LockImmutableTimestampResponse res = timelockService.lockImmutableTimestamp(IdentifiedTimeLockRequest.create()); long transactionTs = timelockService.getFreshTimestamp(); SnapshotTransaction snapshot = getSnapshotTransactionWith( timelockService, () -> transactionTs, res, condition); //simulate roll back at commit time transactionService.putUnlessExists(snapshot.getTimestamp(), TransactionConstants.FAILED_COMMIT_TS); snapshot.put(TABLE, ImmutableMap.of(cell, PtBytes.toBytes("value"))); assertThatExceptionOfType(TransactionLockTimeoutException.class).isThrownBy(snapshot::commit); timelockService.unlock(ImmutableSet.of(res.getLock())); TransactionOutcomeMetricsAssert.assertThat(transactionOutcomeMetrics) .hasFailedCommits(1) .hasLocksExpired(1); }
@Test public void commitThrowsIfRolledBackAtCommitTime_alreadyAborted() { final Cell cell = Cell.create(PtBytes.toBytes("row1"), PtBytes.toBytes("column1")); TimelockService timelockService = new LegacyTimelockService(timestampService, lockService, lockClient); LockImmutableTimestampResponse res = timelockService.lockImmutableTimestamp(IdentifiedTimeLockRequest.create()); long transactionTs = timelockService.getFreshTimestamp(); SnapshotTransaction snapshot = getSnapshotTransactionWith( timelockService, () -> transactionTs, res, PreCommitConditions.NO_OP); //forcing to try to commit a transaction that is already committed transactionService.putUnlessExists(transactionTs, TransactionConstants.FAILED_COMMIT_TS); snapshot.put(TABLE, ImmutableMap.of(cell, PtBytes.toBytes("value"))); assertThatExceptionOfType(TransactionCommitFailedException.class).isThrownBy(snapshot::commit); timelockService.unlock(Collections.singleton(res.getLock())); }
@Override public LockImmutableTimestampResponse lockImmutableTimestamp(IdentifiedTimeLockRequest request) { LockImmutableTimestampResponse response = executeOnTimeLock(() -> delegate.lockImmutableTimestamp(request)); lockRefresher.registerLock(response.getLock()); return response; }