/** * @param delay time to delay for first heartbeat */ @VisibleForTesting void acquireLocksWithHeartbeatDelay(QueryPlan plan, Context ctx, String username, long delay) throws LockException { LockState ls = acquireLocks(plan, ctx, username, true); if (ls != null && !isTxnOpen()) { // If there's no lock, we don't need to do heartbeat // Start heartbeat for read-only queries which don't open transactions but requires locks. // For those that require transactions, the heartbeat has already been started in openTxn. ctx.setHeartbeater(startHeartbeat(delay)); } }
@VisibleForTesting long openTxn(Context ctx, String user, long delay) throws LockException { /*Q: why don't we lock the snapshot here??? Instead of having client make an explicit call whenever it chooses A: If we want to rely on locks for transaction scheduling we must get the snapshot after lock acquisition. Relying on locks is a pessimistic strategy which works better under high contention.*/ init(); getLockManager(); if(isTxnOpen()) { throw new LockException("Transaction already opened. " + JavaUtils.txnIdToString(txnId)); } try { txnId = getMS().openTxn(user); stmtId = 0; numStatements = 0; tableWriteIds.clear(); isExplicitTransaction = false; startTransactionCount = 0; LOG.info("Opened " + JavaUtils.txnIdToString(txnId)); ctx.setHeartbeater(startHeartbeat(delay)); return txnId; } catch (TException e) { throw new LockException(e, ErrorMsg.METASTORE_COMMUNICATION_FAILED); } }
@Override protected void destruct() { try { stopHeartbeat(); if (shutdownRunner != null) { ShutdownHookManager.removeShutdownHook(shutdownRunner); } if (isTxnOpen()) rollbackTxn(); if (lockMgr != null) lockMgr.close(); } catch (Exception e) { LOG.error("Caught exception " + e.getClass().getName() + " with message <" + e.getMessage() + ">, swallowing as there is nothing we can do with it."); // Not much we can do about it here. } }
@Override public long getTableWriteId(String dbName, String tableName) throws LockException { assert isTxnOpen(); return getTableWriteId(dbName, tableName, true); }
"when matched then delete " + "when not matched then insert values(s.a,s.b)", true)); long txnid2 = txnMgr2.getCurrentTxnId(); txnMgr2.acquireLocks(driver.getPlan(), ctx, "T2", false); locks = getLocks(); ((DbLockManager)txnMgr2.getLockManager()).checkLock(extLockId); locks = getLocks(); Assert.assertEquals("Unexpected lock count", 2, locks.size()); txnMgr2.commitTxn();
/** * check that locks in Waiting state show what they are waiting on * This test is somewhat abusive in that it make DbLockManager retain locks for 2 * different queries (which are not part of the same transaction) which can never * happen in real use cases... but it makes testing convenient. * @throws Exception */ @Test public void testLockBlockedBy() throws Exception { dropTable(new String[] {"TAB_BLOCKED"}); CommandProcessorResponse cpr = driver.run("create table TAB_BLOCKED (a int, b int) clustered by (a) into 2 buckets stored as orc TBLPROPERTIES ('transactional'='true')"); checkCmdOnDriver(cpr); cpr = driver.compileAndRespond("select * from TAB_BLOCKED", true); checkCmdOnDriver(cpr); txnMgr.acquireLocks(driver.getPlan(), ctx, "I AM SAM"); List<ShowLocksResponseElement> locks = getLocks(); Assert.assertEquals("Unexpected lock count", 1, locks.size()); checkLock(LockType.SHARED_READ, LockState.ACQUIRED, "default", "TAB_BLOCKED", null, locks); HiveTxnManager txnMgr2 = TxnManagerFactory.getTxnManagerFactory().getTxnManager(conf); swapTxnManager(txnMgr2); cpr = driver.compileAndRespond("drop table TAB_BLOCKED", true); checkCmdOnDriver(cpr); ((DbTxnManager)txnMgr2).acquireLocks(driver.getPlan(), ctx, "SAM I AM", false);//make non-blocking locks = getLocks(); Assert.assertEquals("Unexpected lock count", 2, locks.size()); checkLock(LockType.SHARED_READ, LockState.ACQUIRED, "default", "TAB_BLOCKED", null, locks); checkLock(LockType.EXCLUSIVE, LockState.WAITING, "default", "TAB_BLOCKED", null, locks); Assert.assertEquals("BlockedByExtId doesn't match", locks.get(0).getLockid(), locks.get(1).getBlockedByExtId()); Assert.assertEquals("BlockedByIntId doesn't match", locks.get(0).getLockIdInternal(), locks.get(1).getBlockedByIntId()); }
txnMgr2.openTxn(ctx, "Fidler"); swapTxnManager(txnMgr2); checkCmdOnDriver(driver.compileAndRespond("show tables", true)); txnMgr2.acquireLocks(driver.getPlan(), ctx, "Fidler"); locks = getLocks(); Assert.assertEquals("Unexpected lock count", 3, locks.size()); checkLock(LockType.SHARED_READ, LockState.ACQUIRED, "default", null, null, locks); txnMgr.commitTxn(); txnMgr2.rollbackTxn(); Assert.assertEquals("Lock remained", 0, getLocks().size()); Assert.assertEquals("Lock remained", 0, getLocks(txnMgr2).size()); txnMgr2.openTxn(ctx, "Fidler"); swapTxnManager(txnMgr2); checkCmdOnDriver(driver.compileAndRespond("show tables", true)); txnMgr2.acquireLocks(driver.getPlan(), ctx, "Fidler", false); locks = getLocks(); Assert.assertEquals("Unexpected lock count", 3, locks.size()); checkLock(LockType.SHARED_READ, LockState.ACQUIRED, "default", null, null, locks); txnMgr.commitTxn(); txnMgr2.commitTxn(); Assert.assertEquals("Lock remained", 0, getLocks().size()); Assert.assertEquals("Lock remained", 0, getLocks(txnMgr2).size());
@VisibleForTesting long openTxn(Context ctx, String user, long delay) throws LockException { //todo: why don't we lock the snapshot here??? Instead of having client make an explicit call //whenever it chooses init(); if(isTxnOpen()) { throw new LockException("Transaction already opened. " + JavaUtils.txnIdToString(txnId)); } try { txnId = getMS().openTxn(user); statementId = 0; LOG.debug("Opened " + JavaUtils.txnIdToString(txnId)); ctx.setHeartbeater(startHeartbeat(delay)); return txnId; } catch (TException e) { throw new LockException(e, ErrorMsg.METASTORE_COMMUNICATION_FAILED); } }
@Override public void rollbackTxn() throws LockException { if (!isTxnOpen()) { throw new RuntimeException("Attempt to rollback before opening a transaction"); } try { lockMgr.clearLocalLockRecords(); stopHeartbeat(); LOG.debug("Rolling back " + JavaUtils.txnIdToString(txnId)); getMS().rollbackTxn(txnId); } catch (NoSuchTxnException e) { LOG.error("Metastore could not find " + JavaUtils.txnIdToString(txnId)); throw new LockException(e, ErrorMsg.TXN_NO_SUCH_TRANSACTION, JavaUtils.txnIdToString(txnId)); } catch(TxnAbortedException e) { throw new LockException(e, ErrorMsg.TXN_ABORTED, JavaUtils.txnIdToString(txnId)); } catch (TException e) { throw new LockException(ErrorMsg.METASTORE_COMMUNICATION_FAILED.getMsg(), e); } finally { txnId = 0; stmtId = -1; numStatements = 0; tableWriteIds.clear(); } }
init(); getLockManager(); verifyState(plan); queryId = plan.getQueryId(); switch (plan.getOperation()) {
@Override public LockResponse acquireMaterializationRebuildLock(String dbName, String tableName, long txnId) throws LockException { // Acquire lock LockResponse lockResponse; try { lockResponse = getMS().lockMaterializationRebuild(dbName, tableName, txnId); } catch (TException e) { throw new LockException(ErrorMsg.METASTORE_COMMUNICATION_FAILED.getMsg(), e); } if (lockResponse.getState() == LockState.ACQUIRED) { // If lock response is ACQUIRED, we can create the heartbeater long initialDelay = 0L; long heartbeatInterval = getHeartbeatInterval(conf); assert heartbeatInterval > 0; MaterializationRebuildLockHeartbeater heartbeater = new MaterializationRebuildLockHeartbeater( this, dbName, tableName, queryId, txnId); ScheduledFuture<?> task = startHeartbeat(initialDelay, heartbeatInterval, heartbeater); heartbeater.task.set(task); LOG.debug("Started heartbeat for materialization rebuild lock for {} with delay/interval = {}/{} {} for query: {}", AcidUtils.getFullTableName(dbName, tableName), initialDelay, heartbeatInterval, TimeUnit.MILLISECONDS, queryId); } return lockResponse; }
@Override public void commitTxn() throws LockException { if (!isTxnOpen()) { throw new RuntimeException("Attempt to commit before opening a transaction"); } try { // do all new clear in clearLocksAndHB method to make sure that same code is there for replCommitTxn flow. clearLocksAndHB(); LOG.debug("Committing txn " + JavaUtils.txnIdToString(txnId)); getMS().commitTxn(txnId); } catch (NoSuchTxnException e) { LOG.error("Metastore could not find " + JavaUtils.txnIdToString(txnId)); throw new LockException(e, ErrorMsg.TXN_NO_SUCH_TRANSACTION, JavaUtils.txnIdToString(txnId)); } catch (TxnAbortedException e) { LockException le = new LockException(e, ErrorMsg.TXN_ABORTED, JavaUtils.txnIdToString(txnId), e.getMessage()); LOG.error(le.getMessage()); throw le; } catch (TException e) { throw new LockException(ErrorMsg.METASTORE_COMMUNICATION_FAILED.getMsg(), e); } finally { // do all new reset in resetTxnInfo method to make sure that same code is there for replCommitTxn flow. resetTxnInfo(); } } @Override
if(!isTxnOpen()) { throw new LockException("No transaction context for operation: " + queryPlan.getOperationName() + " for " + getQueryIdWaterMark(queryPlan)); throw new IllegalStateException("Unkown HiverOperation for " + getQueryIdWaterMark(queryPlan)); markExplicitTransaction(queryPlan); break; case COMMIT: case ROLLBACK: if(!isTxnOpen()) { throw new LockException(null, ErrorMsg.OP_NOT_ALLOWED_WITHOUT_TXN, queryPlan.getOperationName()); default: if(!queryPlan.getOperation().isAllowedInTransaction() && isExplicitTransaction) { if(allowOperationInATransaction(queryPlan)) { break; JavaUtils.txnIdToString(getCurrentTxnId()), queryPlan.getQueryId());
init(); getLockManager(); t = getTable(output); if(AcidUtils.isAcidTable(t)) { compBuilder.setShared(); compBuilder.setSemiShared(); compBuilder.setOperationType(DataOperationType.UPDATE); t = getTable(output); break; case DELETE: compBuilder.setSemiShared(); compBuilder.setOperationType(DataOperationType.DELETE); t = getTable(output); break;
@Override public List<Long> replOpenTxn(String replPolicy, List<Long> srcTxnIds, String user) throws LockException { try { return getMS().replOpenTxn(replPolicy, srcTxnIds, user); } catch (TException e) { throw new LockException(e, ErrorMsg.METASTORE_COMMUNICATION_FAILED); } }
@Ignore("This seems useless now that we have a txn for everything") @Test public void testLockTimeout() throws Exception { addPartitionInput(newTable(true)); QueryPlan qp = new MockQueryPlan(this, HiveOperation.QUERY); //make sure it works with nothing to expire testLockExpiration(txnMgr, 0, true); //create a few read locks, all on the same resource for(int i = 0; i < 5; i++) { ((DbTxnManager)txnMgr).acquireLocks(qp, ctx, "PeterI" + i, true); // No heartbeat } testLockExpiration(txnMgr, 5, true); //create a lot of locks for(int i = 0; i < TEST_TIMED_OUT_TXN_ABORT_BATCH_SIZE + 17; i++) { ((DbTxnManager)txnMgr).acquireLocks(qp, ctx, "PeterI" + i, true); // No heartbeat } testLockExpiration(txnMgr, TEST_TIMED_OUT_TXN_ABORT_BATCH_SIZE + 17, true); // Create a lock, but send the heartbeat with a long delay. The lock will get expired. ((DbTxnManager)txnMgr).acquireLocksWithHeartbeatDelay(qp, ctx, "bob", HiveConf.getTimeVar(conf, HiveConf.ConfVars.HIVE_TXN_TIMEOUT, TimeUnit.MILLISECONDS) * 10); testLockExpiration(txnMgr, 1, true); // Create a lock and trigger a heartbeat. With heartbeat, the lock won't expire. txnMgr.acquireLocks(qp, ctx, "peter"); testLockExpiration(txnMgr, 1, false); }
@Override public ValidTxnWriteIdList getValidWriteIds(List<String> tableList, String validTxnList) throws LockException { assert isTxnOpen(); assert validTxnList != null && !validTxnList.isEmpty(); try { return TxnCommonUtils.createValidTxnWriteIdList( txnId, getMS().getValidWriteIds(tableList, validTxnList)); } catch (TException e) { throw new LockException(ErrorMsg.METASTORE_COMMUNICATION_FAILED.getMsg(), e); } }
/** * we don't expect multiple threads to call this method concurrently but {@link #lockMgr} will * be read by a different threads than one writing it, thus it's {@code volatile} */ @Override public HiveLockManager getLockManager() throws LockException { init(); if (lockMgr == null) { lockMgr = new DbLockManager(conf, this); } return lockMgr; }