public CompletedTransactionStatus getTransactionStatus(GlobalTransaction gtx) { CompletedTransactionInfo completedTx = completedTransactions.get(gtx); if (completedTx != null) { return completedTx.successful ? CompletedTransactionStatus.COMMITTED : CompletedTransactionStatus.ABORTED; } // Transaction ids are allocated in sequence, so any transaction with a smaller id must have // been started // before a transaction that was already removed from the completed transactions map because // it was too old. // We assume that the transaction was either committed, or it was rolled back (e.g. because // the prepare // RPC timed out. // Note: We must check the id *after* verifying that the tx doesn't exist in the map. if (gtx.getId() > globalMaxPrunedTxId) return CompletedTransactionStatus.NOT_COMPLETED; Long nodeMaxPrunedTxId = nodeMaxPrunedTxIds.get(gtx.getAddress()); if (nodeMaxPrunedTxId == null) { // We haven't removed any transaction for this node return CompletedTransactionStatus.NOT_COMPLETED; } else if (gtx.getId() > nodeMaxPrunedTxId) { // We haven't removed this particular transaction yet return CompletedTransactionStatus.NOT_COMPLETED; } else { // We already removed the status of this transaction from the completed transactions map return CompletedTransactionStatus.EXPIRED; } }
/** @see #markTransactionCompleted(GlobalTransaction, boolean) */ public boolean isTransactionCompleted(GlobalTransaction gtx) { if (completedTransactions.containsKey(gtx)) return true; // Transaction ids are allocated in sequence, so any transaction with a smaller id must have // been started // before a transaction that was already removed from the completed transactions map because // it was too old. // We assume that the transaction was either committed, or it was rolled back (e.g. because // the prepare // RPC timed out. // Note: We must check the id *after* verifying that the tx doesn't exist in the map. if (gtx.getId() > globalMaxPrunedTxId) return false; Long nodeMaxPrunedTxId = nodeMaxPrunedTxIds.get(gtx.getAddress()); return nodeMaxPrunedTxId != null && gtx.getId() <= nodeMaxPrunedTxId; }
/** * It stops tracking keys committed. * * @param track Flag to stop tracking keys for local site state transfer or for remote site state * transfer. */ public final void stopTrack(Flag track) { setTrack(track, false); if (!trackStateTransfer && !trackXSiteStateTransfer) { if (trace) { log.tracef("Tracking is disabled. Clear tracker: %s", tracker); } tracker.clear(); } else { for (Iterator<Map.Entry<Object, DiscardPolicy>> iterator = tracker.entrySet().iterator(); iterator.hasNext(); ) { if (iterator.next().getValue().update(trackStateTransfer, trackXSiteStateTransfer)) { iterator.remove(); } } } }
/** * It tries to commit the cache entry. The entry is not committed if it is originated from state * transfer and other operation already has updated it. * * @param entry the entry to commit * @param metadata the entry's metadata * @param operation if {@code null}, it identifies this commit as originated from a normal * operation. Otherwise, it is originated from a state transfer (local or remote site) */ public final void commit( final CacheEntry entry, final Metadata metadata, final Flag operation, boolean l1Invalidation) { if (trace) { log.tracef( "Trying to commit. Key=%s. Operation Flag=%s, L1 invalidation=%s", toStr(entry.getKey()), operation, l1Invalidation); } if (l1Invalidation || (operation == null && !trackStateTransfer && !trackXSiteStateTransfer)) { // track == null means that it is a normal put and the tracking is not enabled! // if it is a L1 invalidation, commit without track it. if (trace) { log.tracef( "Committing key=%s. It is a L1 invalidation or a normal put and no tracking is enabled!", toStr(entry.getKey())); } entry.commit(dataContainer, metadata); return; } if (isTrackDisabled(operation)) { // this a put for state transfer but we are not tracking it. This means that the state // transfer has ended // or canceled due to a clear command. if (trace) { log.tracef( "Not committing key=%s. It is a state transfer key but no track is enabled!", toStr(entry.getKey())); } return; } tracker.compute( entry.getKey(), (o, discardPolicy) -> { if (discardPolicy != null && discardPolicy.ignore(operation)) { if (trace) { log.tracef( "Not committing key=%s. It was already overwritten! Discard policy=%s", toStr(entry.getKey()), discardPolicy); } return discardPolicy; } entry.commit(dataContainer, metadata); DiscardPolicy newDiscardPolicy = calculateDiscardPolicy(); if (trace) { log.tracef( "Committed key=%s. Old discard policy=%s. New discard policy=%s", toStr(entry.getKey()), discardPolicy, newDiscardPolicy); } return newDiscardPolicy; }); }
@Override public String toString() { return "CommitManager{" + "tracker=" + tracker.size() + " key(s)" + ", trackStateTransfer=" + trackStateTransfer + ", trackXSiteStateTransfer=" + trackXSiteStateTransfer + '}'; }
private void updateLastPrunedTxId(final long txId, Address address) { if (txId > globalMaxPrunedTxId) { globalMaxPrunedTxId = txId; } nodeMaxPrunedTxIds.compute( address, (a, nodeMaxPrunedTxId) -> { if (nodeMaxPrunedTxId != null && txId <= nodeMaxPrunedTxId) { return nodeMaxPrunedTxId; } return txId; }); }
/** @return {@code true} if no keys are tracked, {@code false} otherwise. */ public final boolean isEmpty() { return tracker.isEmpty(); }
public void cleanupCompletedTransactions() { if (completedTransactions.isEmpty()) return; try { if (trace) log.tracef( "About to cleanup completed transaction. Initial size is %d", completedTransactions.size()); long beginning = timeService.time(); long minCompleteTimestamp = timeService.time() - TimeUnit.MILLISECONDS.toNanos(configuration.transaction().completedTxTimeout()); int removedEntries = 0; // Collect the leavers. They will be removed at the end. Set<Address> leavers = new HashSet<>(); for (Map.Entry<Address, Long> e : nodeMaxPrunedTxIds.entrySet()) { if (!rpcManager.getMembers().contains(e.getKey())) { leavers.add(e.getKey()); } } // Remove stale completed transactions. Iterator<Map.Entry<GlobalTransaction, CompletedTransactionInfo>> txIterator = completedTransactions.entrySet().iterator(); while (txIterator.hasNext()) { Map.Entry<GlobalTransaction, CompletedTransactionInfo> e = txIterator.next(); CompletedTransactionInfo completedTx = e.getValue(); if (minCompleteTimestamp - completedTx.timestamp > 0) { // Need to update lastPrunedTxId *before* removing the tx from the map // Don't need atomic operations, there can't be more than one thread updating // lastPrunedTxId. final long txId = e.getKey().getId(); final Address address = e.getKey().getAddress(); updateLastPrunedTxId(txId, address); txIterator.remove(); removedEntries++; } else { // Nodes with "active" completed transactions are not removed.. leavers.remove(e.getKey().getAddress()); } } // Finally, remove nodes that are no longer members and don't have any "active" completed // transactions. for (Address e : leavers) { nodeMaxPrunedTxIds.remove(e); } long duration = timeService.timeDuration(beginning, TimeUnit.MILLISECONDS); if (trace) log.tracef( "Finished cleaning up completed transactions in %d millis, %d transactions were removed, " + "current number of completed transactions is %d", removedEntries, duration, completedTransactions.size()); if (trace) log.tracef( "Last pruned transaction ids were updated: %d, %s", globalMaxPrunedTxId, nodeMaxPrunedTxIds); } catch (Exception e) { log.errorf(e, "Failed to cleanup completed transactions: %s", e.getMessage()); } }
/** * With the current state transfer implementation it is possible for a transaction to be * prepared several times on a remote node. This might cause leaks, e.g. if the transaction is * prepared, committed and prepared again. Once marked as completed (because of commit or * rollback) any further prepare received on that transaction are discarded. */ public void markTransactionCompleted(GlobalTransaction globalTx, boolean successful) { if (trace) log.tracef("Marking transaction %s as completed", globalTx); completedTransactions.put( globalTx, new CompletedTransactionInfo(timeService.time(), successful)); }