Cannot call abortTransaction after calling commitTransaction: error in nodejs application

Viewed 21

I am having an issue with mongodb driver that I am not able to understand.

Some context. I am creating an NFT collection ranker endpoint. To that end I am attempting to store processed data in two separate collections rankings and sortedRankings. rankings contains collection level data and sortedRankings contains the sorted collection.

I found out about mongodb transactions and decided to give it a shot. Here is the associated code in question.

export async function rankingInsertOrUpdate(
  storedAccuracy: WithId < NFTCollectionRanking > | null,
  rankingDocument: NFTCollectionRanking,
  sortedRankingDocument: NFTSortedRanking
) {
  const session = client.startSession();

  try {
    if (storedAccuracy === null) {
      runTransationWithRetry(insertRankings, session);
    } else if (rankingDocument.accuracy > storedAccuracy.accuracy) {
      runTransationWithRetry(updateRankings, session);
    }
  } catch (error: any) {
    throw new Error(error);
  } finally {
    session.endSession();
  }

  async function insertRankings() {
    session.startTransaction({
      readConcern: {
        level: 'snapshot'
      },
      writeConcern: {
        w: 'majority'
      },
    });

    try {
      await rankings.insertOne(rankingDocument);
      await sortedRankings.insertOne(sortedRankingDocument);
    } catch (error) {
      console.log('Caught exception during insert transaction, aborting.');
      session.abortTransaction();
      throw error;
    }

    commitWithRetry(session);
  }

  async function updateRankings() {
    session.startTransaction({
      readConcern: {
        level: 'snapshot'
      },
      writeConcern: {
        w: 'majority'
      },
    });

    try {
      await rankings.updateOne(rankingDocument);
      await sortedRankings.updateOne(sortedRankingDocument);
    } catch (error) {
      console.log('Caught exception during update transaction, aborting.');
      session.abortTransaction();
      throw error;
    }

    commitWithRetry(session);
  }
}

function runTransationWithRetry(txnFunc: any, session: any) {
  while (true) {
    try {
      txnFunc(session); // performs transaction
      break;
    } catch (error: any) {
      // If transient errorm retry the whole function
      if (
        error.hasOwnProperty('errorLabels') &&
        error.errorLabels.includes('TransientTransactionError')
      ) {
        console.log('TransientTransactionError, retrying transaction ...');
        continue;
      } else {
        throw error;
      }
    }
  }
}

function commitWithRetry(session: ClientSession) {
  while (true) {
    try {
      session.commitTransaction(); // Uses write concern set at transaction start
      console.log('Transaction committed');
      break;
    } catch (error: any) {
      // Can retry commit
      if (
        error.hasOwnProperty('errorLabels') &&
        error.errorLabels.includes('UnknownTransactionCommitResult')
      ) {
        console.log(
          'UnknownTransactionCommitResult, retrying commit operation ...'
        );
        continue;
      } else {
        console.log('Error during commit ...');
        throw error;
      }
    }
  }
}

Whats happening specifically is that node is crashing at the very last second. Here is a pic of the console logs.

enter image description here

I run the POST, let it process everything, then it crashes. The processed data appears in my DB. So it is successful in sending it to the DB, but I'm not able to find out why abortTransaction is being called. If an error was thrown during the insertRankings or updateRankings function, then it would've console logged Caught exception during insert transaction, aborting, but this is not appearing in my terminal. The only statement printed is Transaction committed. The session is also ended with session.endSession() after runTransactionWithRetry is finished. Not sure how it's successful but crashes at the end.

I used the retry flow from the mongo docs found here https://www.mongodb.com/docs/manual/reference/method/Session.startTransaction/ . I tried following it a T. I have checked other posts like mongodb do I need a abortTransaction after commitTransaction that fails? and MongoError: Cannot call abortTransaction twice; MongoError: Cannot call abortTransaction after calling commitTransaction but they do not help answer my question.

I'm kinda stumped and any help would be amazing.

1 Answers

I have realized my error and I was able to fix the issue. Somehow, abortTransaction was being executed after the commit because I was not awaiting some of the asynchronous functions in this flow.

After checking the functions abortTransaction and commitTransaction, I realized they returned promises. That is why weird things were happening. I did not await these promises.

On top of that I also did not await the function passed into runTransactionWithRetry even though I knew it returned promises. I should've seen this, but neglected it due to copy pasta from the example on mongodb.

As to how abortTransaction was being executed in all this, I am unsure. But after these minor fixes, I was able to get my endpoint to POST without it crashing.

Related