Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transaction is not rolled back when operations are inside a Promise.all #56

Closed
iamtiagodev opened this issue Dec 9, 2024 · 3 comments
Closed

Comments

@iamtiagodev
Copy link

Given two services as in the example bellow:

@Injectable()
export class BService {
  
  async create(entity: B): Promise<B> {
    return this.bRepository.save(entity);
  }

  async createWithError(entity: B): Promise<B> {
    const someReason = true;
    if (someReason) {
      throw new Error('Unexpected error');
    }
    return this.bRepository.save(entity);
  }
}


@Injectable()
export class AService {

 ...
  @Transactional()
  async create(): Promise<B[]> {
    const result = await Promise.all([
      this.bService.create(fixtures[0]),
      this.bService.create(fixtures[1]),
      this.bService.create(fixtures[2]),
      this.bService.createWithError(fixtures[3]),
    ]);
    return result;
  }
}

When on one of the calls inside Promise.all fails (i.e. an exception is thrown ) the rollback doesn't always behave as it should. Sometimes it does rollback, but others it only rollsback the selects for the save (upsert) operations.

When logging: true in nestJS configs, I get the following SQL operations

query: START TRANSACTION
query: SELECT "B"."id" AS "B_id", "B"."aAttribute" AS "B_aAttribute", "B"."bAttribute" AS "B_bAttribute" FROM "b" "B" WHERE "B"."id" IN ($1) -- PARAMETERS: [94]
query: SELECT "B"."id" AS "B_id", "B"."aAttribute" AS "B_aAttribute", "B"."bAttribute" AS "B_bAttribute" FROM "b" "B" WHERE "B"."id" IN ($1) -- PARAMETERS: [95]
query: SELECT "B"."id" AS "B_id", "B"."aAttribute" AS "B_aAttribute", "B"."bAttribute" AS "B_bAttribute" FROM "b" "B" WHERE "B"."id" IN ($1) -- PARAMETERS: [96]
query: ROLLBACK
query: INSERT INTO "b"("aAttribute", "bAttribute") VALUES ($1, $2) RETURNING "id" -- PARAMETERS: ["Value 2","Value 2"]
query: INSERT INTO "b"("aAttribute", "bAttribute") VALUES ($1, $2) RETURNING "id" -- PARAMETERS: ["Value 3","Value 3"]
query: INSERT INTO "b"("aAttribute", "bAttribute") VALUES ($1, $2) RETURNING "id" -- PARAMETERS: ["Value 4","Value 4"]

Does this mean this lib doesn't support Promise.all and we have to wait for each of the calls? 🤔

@iamtiagodev
Copy link
Author

iamtiagodev commented Dec 9, 2024

On other hand, when one uses the native typeorm transaction and wraps the transaction like

await datasource.transaction(async (entityManager) => {
    const result = await Promise.all([
      this.bService.create(entityManager, fixtures[0]),
      this.bService.create(entityManager, fixtures[1]),
      this.bService.create(entityManager, fixtures[2]),
      this.bService.createWithError(entityManager, fixtures[3]),
    ]);
    return result;
})

It works as expected.

@szwalkowski
Copy link

@iamtiagodev I think you should handle this on your own here.
Promise.all throws an error the moment there's rejection. So - don't know the specifics - but I'm guessing , rollback works on catching an error - as I was testing it.
So the error is thrown but processing in other promises does not stop. So if whatever inside wasn't yet associated with transaction, it will proceed.

You should change Promise.all to allSettled and throw the error once all is processed and any is rejected.

@iamtiagodev
Copy link
Author

@szwalkowski apologies for not following up and... yup, you are totally correct 👍
The solution is to use Promise.allSettled and manually throw any rejection.

I did a small PoC to study transactional behaviour. I'll leave it here in case someone else bumps into this thread

https://github.com/iamtiagodev/typeorm-transaction-experiment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants