protected static synchronized ActiveTransactionsRecord makeSentinelRecord() { if (sentinelRecordCreated) { throw new Error("ActiveTransactionsRecord::makeSentinelRecord() invoked more than once!"); } sentinelRecordCreated = true; return new ActiveTransactionsRecord(); }
@Override protected void finish() { super.finish(); activeTxRecord.decrementRunning(); }
private static ActiveTransactionsRecord findActiveRecordForNumber(ActiveTransactionsRecord rec, int number) { while (rec.transactionNumber < number) { rec = rec.getNext(); } return rec; }
public static ActiveTransactionsRecord getRecordForNewTransaction() { ActiveTransactionsRecord rec = Transaction.mostRecentCommittedRecord; TxContext ctx = threadTxContext.get(); ctx.oldestRequiredVersion = rec; // volatile write while (true) { while ((rec.getNext() != null) && (rec.getNext().isCommitted())) { rec = rec.getNext(); } if (rec != ctx.oldestRequiredVersion) { // a more recent record exists, so backoff and try again with the new one ctx.oldestRequiredVersion = rec; // volatile write } else { return rec; } } }
protected void finishCommit(ActiveTransactionsRecord recordToCommit) { // we only advance the most recent committed record if we don't see this // transaction already committed if (!recordToCommit.isCommitted()) { recordToCommit.setCommitted(); Transaction.setMostRecentCommittedRecord(recordToCommit); } } }
public ClusteredPersistentTransaction(ActiveTransactionsRecord record) { super(record); ActiveTransactionsRecord newRecord = tryToApplyRemoteCommits(record); if (newRecord != this.activeTxRecord) { // if a new record is returned, that means that this transaction // will belong // to that new record, so we must take it off from its current // record and set // it properly newRecord.incrementRunning(); this.activeTxRecord.decrementRunning(); this.activeTxRecord = newRecord; setNumber(newRecord.transactionNumber); } }
@Override protected void doCommit() { // the commit is already done, so create a new ActiveTransactionsRecord for (Map.Entry<PerTxBox, Object> entry : this.perTxValues.entrySet()) { entry.getKey().commit(entry.getValue()); } ActiveTransactionsRecord newRecord = new ActiveTransactionsRecord(getNumber(), WriteSet.empty()); newRecord.setCommitted(); setMostRecentCommittedRecord(newRecord); if (!this.activeTxRecord.trySetNext(newRecord)) { throw new Error("Unacceptable: UnsafeSingleThreadedTransaction in a concurrent environment"); } // we must update the activeRecords accordingly context().oldestRequiredVersion = newRecord; this.activeTxRecord = newRecord; }
public static void initializeTxNumber(int maxTx) { logger.info("Setting the last committed TX number to {}", maxTx); ActiveTransactionsRecord initialRecord = new ActiveTransactionsRecord(maxTx, WriteSet.empty()); boolean success = Transaction.mostRecentCommittedRecord.trySetNext(initialRecord); if (!success) { throw new AssertionError("Impossible condition: Failed to initializeTxNumber."); } Transaction.setMostRecentCommittedRecord(initialRecord); }
protected boolean isSnapshotValidationWorthIt(ActiveTransactionsRecord lastRecord) { if (this.bodiesRead.isEmpty()) { return false; } int numberOfReadsToCheck = this.bodiesRead.first().length - (next + 1); // if there are more arrays the rest are full, for sure for (VBox[] array : bodiesRead.rest()) { numberOfReadsToCheck += array.length; } int numberOfWritesToCheck = 0; for (ActiveTransactionsRecord rec = this.activeTxRecord.getNext(); rec != null; rec = rec.getNext()) { numberOfWritesToCheck += rec.getWriteSet().size(); } return ((float) numberOfWritesToCheck) / numberOfReadsToCheck > WR_THRESHOLD; }
/** * Help to commit a transaction as much as possible. * * @param recordToCommit * the record to help commit */ protected void helpCommit(ActiveTransactionsRecord recordToCommit) { if (!recordToCommit.isCommitted()) { // We must check whether recordToCommit.getWriteSet() could, in the // meanwhile, have // become null. This occurs when this recordToCommit was already // committed and even // cleaned while this thread was waiting to be scheduled WriteSet writeSet = recordToCommit.getWriteSet(); if (writeSet != null) { writeSet.helpWriteBack(recordToCommit.transactionNumber); // the thread that commits the last body will handle the rest of // the commit finishCommit(recordToCommit); } } }
@Override protected ActiveTransactionsRecord getSameRecordForNewTransaction() { this.activeTxRecord.incrementRunning(); return this.activeTxRecord; }
@Override protected void enqueueValidCommit(ActiveTransactionsRecord lastCheck, WriteSet writeSet) { ActiveTransactionsRecord commitRecord = this.getCommitTxRecord(); /* Here we know that our commit is valid. However, we may have concluded such result via some helper AND even have seen already our record enqueued and committed. So we need to check for that to skip enqueuing. */ if (lastCheck.transactionNumber >= commitRecord.transactionNumber) { logger.debug("Transaction {} for commit request {} was already enqueued AND even committed by another helper.", commitRecord.transactionNumber, this.commitRequest.getId()); } else { if (lastCheck.trySetNext(commitRecord)) { logger.debug("Enqueued record for valid transaction {} of commit request {}", commitRecord.transactionNumber, this.commitRequest.getId()); } else { logger.debug("Transaction {} of commit request {} was already enqueued by another helper.", commitRecord.transactionNumber, this.commitRequest.getId()); } } // EVERYONE MUST TRY THIS, to ensure visibility when looking it up ahead. txVersionToCommitIdMap.putIfAbsent(commitRecord.transactionNumber, this.commitRequest.getId()); }
@Override protected void helpCommit(ActiveTransactionsRecord recordToCommit) { if (!recordToCommit.isCommitted()) { logger.debug("Helping to commit version {}", recordToCommit.transactionNumber); int txVersion = recordToCommit.transactionNumber; UUID commitId = CommitOnlyTransaction.txVersionToCommitIdMap.get(txVersion); if (commitId != null) { // may be null if it was already persisted JvstmLockFreeBackEnd.getInstance().getRepository().mapTxVersionToCommitId(txVersion, commitId); CommitOnlyTransaction.txVersionToCommitIdMap.remove(txVersion); } super.helpCommit(recordToCommit); } else { logger.debug("Version {} was already fully committed", recordToCommit.transactionNumber); } }
@Override protected void validateCommitAndEnqueue(ActiveTransactionsRecord lastCheck) { enqueueValidCommit(lastCheck, this.getCommitTxRecord().getWriteSet()); updateOrecVersion(); }
@Override protected boolean validateCommit() { ActiveTransactionsRecord mostRecentRecord = Transaction.mostRecentRecord; boolean result = super.validateCommit(); if (result) { // upgradeTx(); setNumber(mostRecentRecord.transactionNumber); // the correct order is to increment first the // new, and only then decrement the old mostRecentRecord.incrementRunning(); this.activeTxRecord.decrementRunning(); this.activeTxRecord = mostRecentRecord; } else { TransactionSupport.STATISTICS.incConflicts(); } return result; }
private ActiveTransactionsRecord findOldestRecordInUse() { // We use this in case there are no thread running, to know until where to clean. If we // only read this after doing the search we might clean more than we should, because a new // transaction can begin and commit a new record at any time. By reading first, we ensure // that if all threads have gone, we can clean at least until here. ActiveTransactionsRecord mostRecentCommittedAtBegin = Transaction.mostRecentCommittedRecord; for (ActiveTransactionsRecord next = mostRecentCommittedAtBegin.getNext(); (next != null) && next.isCommitted(); next = next.getNext()) { mostRecentCommittedAtBegin = next; } // we could use this opportunity to advance Transaction.mostRecentCommittedRecord // First pass. Here we check all contexts to identify the oldest record in use. ActiveTransactionsRecord minRequiredRecord1 = findOldestRecordUpTo(null, Integer.MAX_VALUE); // If there was no record identified as a minimum we can safely clean up to the record that // was committed at the beginning, because all other threads will see it and use it (or use // another more recent record which is ok) if (minRequiredRecord1 == null) { return mostRecentCommittedAtBegin; } // Otherwise we do a second pass. In the second pass we re-check all the records that were // checked before the identified oldest context, as they may have changed concurrently to a // lower minimum. ActiveTransactionsRecord minRequiredRecord2 = findOldestRecordUpTo(this.oldestContext, minRequiredRecord1.transactionNumber); // If we find another record in the second pass then that is the minimum. If not then the // first found is it. return (minRequiredRecord2 != null) ? minRequiredRecord2 : minRequiredRecord1; }
/** * Enqueue a valid commit (just after the record lastCheck). If enqueue fails then, revalidate, upgrade the transaction and retry to enqueue. * * @param lastCheck The last record up to where validation succeeded. * @param writeSet The writeSet of this commit. * @return */ /* This code was extracted from validateCommitAndEnqueue, to enable overriding it in subclasses that wish to reuse the remainder of the algorithm coded in validateCommitAndEnqueue. */ protected void enqueueValidCommit(ActiveTransactionsRecord lastCheck, WriteSet writeSet) { ProcessPerTxBoxesTransaction commitTx; while (!lastCheck.trySetNext(getCommitTxRecord())) { // Failed enqueue, at least some other transaction succeeded in the meantime lastCheck = helpCommitAll(); snapshotValidation(lastCheck.transactionNumber); // Re-execute the perTxBoxes speculatively. They are supposed to be a point of contention, thus // any validation to check if previous speculative reads are still up-to-date should most of the time // lead to the conclusion that they are not. This way we avoid registering those reads and skip the validation. commitTx = speculatePerTxBoxes(lastCheck.transactionNumber); writeSet.addPerTxBoxesWrites(commitTx.specWriteSet); assignCommitRecord(lastCheck.transactionNumber + 1, writeSet); } }
@Override protected void helpCommit(ActiveTransactionsRecord recordToCommit) { if (!recordToCommit.isCommitted()) { logger.debug("Helping to commit version {}", recordToCommit.transactionNumber); int txVersion = recordToCommit.transactionNumber; UUID commitId = CommitOnlyTransaction.txVersionToCommitIdMap.get(txVersion); if (commitId != null) { // may be null if it was already persisted JvstmLockFreeBackEnd.getInstance().getRepository().mapTxVersionToCommitId(txVersion, commitId); CommitOnlyTransaction.txVersionToCommitIdMap.remove(txVersion); } super.helpCommit(recordToCommit); } else { logger.debug("Version {} was already fully committed", recordToCommit.transactionNumber); } }
protected void assignCommitRecord(int txNumber, WriteSet writeSet) { setCommitTxRecord(new ActiveTransactionsRecord(txNumber, writeSet)); }