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

fix(medusa): ensure transaction is reused when service calls itself #6089

Merged
merged 7 commits into from
Jan 19, 2024

Conversation

srindom
Copy link
Collaborator

@srindom srindom commented Jan 15, 2024

What

  • ProductService.create calls ProductService.retrieve before returning. This fix ensures that the manager created in the atomicPhase is used when ProductService.retrieve is called.

Why

  • Without the explicit use of the same manager in the retrieve call, race conditions easily occur if you have concurrent ProductService.create calls. The code below can reproduce the scenario:
  const productData = [
    {
      barcode: `1234233423-${Math.random() * 100}`,
      external_id: `1234233423-${Math.random() * 100}`,
      description: "super cool product",
      discountable: true,
      hs_code: "1234213",
      origin_country: "DK",
      title: `Super cool product ${Math.random() * 100}`,
      type: { value: "Eyewear" },
      sales_channels: [{ id: sc.id }],
    },
    {
      barcode: `1234233423-${Math.random() * 100}`,
      external_id: `random-external-id-${Math.random() * 100}`,
      description: "super cool product",
      discountable: true,
      hs_code: "1234213",
      origin_country: "DK",
      title: `Super cool product ${Math.random() * 100}`,
      type: { value: "Eyewear" },
      sales_channels: [{ id: sc.id }],
    },
  ];

  const result = await Promise.all(
    productData.map(async (product) => productService.create(product))
  );

Explaination

What happens is the following:

  • Request 1 calls ProductService.create. This in turn calls atomicPhase which sets the ProductService.transactionManager_ = txForReqOne
  • Request 1 creates the product in the DB with txForReqOne.
  • Request 2 calls ProductService.create. This in turn calls atomicPhase which sets the ProductService.transactionManager_ = txForReqTwo
  • Request 1 reaches the end of ProductService.create where this.retrieve is called. Because the ProductService is a singleton the retrieve call will attempt to use txForReqTwo to fetch the product.
  • Error: since txForReqTwo can't read the data of txForReqOne, the product is not found.

@srindom srindom requested a review from a team as a code owner January 15, 2024 10:40
Copy link

changeset-bot bot commented Jan 15, 2024

⚠️ No Changeset found

Latest commit: 1c569a4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

vercel bot commented Jan 15, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
medusa-dashboard ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 19, 2024 10:42am
3 Ignored Deployments
Name Status Preview Comments Updated (UTC)
api-reference ⬜️ Ignored (Inspect) Visit Preview Jan 19, 2024 10:42am
docs-ui ⬜️ Ignored (Inspect) Visit Preview Jan 19, 2024 10:42am
medusa-docs ⬜️ Ignored (Inspect) Visit Preview Jan 19, 2024 10:42am

@olivermrbl olivermrbl changed the title fix: ensure transaction is reused when service calls itself fix(medusa): ensure transaction is reused when service calls itself Jan 15, 2024
@adrien2p
Copy link
Member

Nice catch 💪 you found that probably because there is somewhere where the service is not called within a transaction right?

@olivermrbl
Copy link
Contributor

Should we fix this across the entire service? Think we use this pattern in a couple of other places

@adrien2p
Copy link
Member

Should we fix this across the entire service? Think we use this pattern in a couple of other places

Is it also the only service or should we check them all?

@olivermrbl
Copy link
Contributor

@adrien2p, I will migrate the rest of the ProductService before we merge, but will keep the rest (if any) for a separate PR

@adrien2p
Copy link
Member

Sounds good to me, if the pr is not done just after we might need to create a ticket?

@srindom
Copy link
Collaborator Author

srindom commented Jan 18, 2024

@olivermrbl, @adrien2p - can we apply automerge or is there still more here?

@adrien2p
Copy link
Member

I ll do a final review, haven't seen the last commit. Butnif you want we can auto merge and if i have any comments we can handle them separately to not block it

Copy link
Member

@adrien2p adrien2p left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🔥 couple of comments

@@ -594,7 +595,7 @@ class ProductService extends TransactionBaseService {
)
}

const result = await this.retrieve(product.id, {
const result = await this.withTransaction(manager).retrieve(product.id, {
Copy link
Member

@adrien2p adrien2p Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: line 559 is missing. maybe we can do something like productServiceTx = this.withTransaction(manager) and then use it in both places?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@srindom I ve seen that you have merged but there is still a missing transaction here

@@ -833,7 +834,7 @@ class ProductService extends TransactionBaseService {
)
}

const result = await this.retrieve(productId)
const result = await this.withTransaction(manager).retrieve(productId)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: same as the previous one to not recreate the instance multiple times everytime

@kodiakhq kodiakhq bot merged commit 1b99c5d into develop Jan 19, 2024
16 checks passed
@kodiakhq kodiakhq bot deleted the fix/create-product-transaction-management branch January 19, 2024 11:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants