fix(medusa): ensure transaction is reused when service calls itself (#6089)
**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:
```javascript
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.
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
1 parent
12aa1737d5
commit
1b99c5dc22
1 file changed
+13
-10
@@ -1,12 +1,13 @@
|
|||||||
|
import { RemoteQueryFunction } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
buildRelations,
|
buildRelations,
|
||||||
buildSelects,
|
buildSelects,
|
||||||
FlagRouter,
|
FlagRouter,
|
||||||
MedusaV2Flag,
|
MedusaV2Flag,
|
||||||
objectToStringPath,
|
objectToStringPath,
|
||||||
promiseAll, selectorConstraintsToString,
|
promiseAll,
|
||||||
|
selectorConstraintsToString,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { RemoteQueryFunction } from "@medusajs/types"
|
|
||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
import { isDefined, MedusaError } from "medusa-core-utils"
|
||||||
import { EntityManager, In } from "typeorm"
|
import { EntityManager, In } from "typeorm"
|
||||||
|
|
||||||
@@ -42,9 +43,9 @@ import {
|
|||||||
ProductSelector,
|
ProductSelector,
|
||||||
UpdateProductInput,
|
UpdateProductInput,
|
||||||
} from "../types/product"
|
} from "../types/product"
|
||||||
|
import { CreateProductVariantInput } from "../types/product-variant"
|
||||||
import { buildQuery, isString, setMetadata } from "../utils"
|
import { buildQuery, isString, setMetadata } from "../utils"
|
||||||
import EventBusService from "./event-bus"
|
import EventBusService from "./event-bus"
|
||||||
import { CreateProductVariantInput } from "../types/product-variant"
|
|
||||||
import SalesChannelService from "./sales-channel"
|
import SalesChannelService from "./sales-channel"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
@@ -594,7 +595,7 @@ class ProductService extends TransactionBaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.retrieve(product.id, {
|
const result = await this.withTransaction(manager).retrieve(product.id, {
|
||||||
relations: ["options"],
|
relations: ["options"],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -645,7 +646,7 @@ class ProductService extends TransactionBaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const product = await this.retrieve(productId, {
|
const product = await this.withTransaction(manager).retrieve(productId, {
|
||||||
relations,
|
relations,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -805,7 +806,7 @@ class ProductService extends TransactionBaseService {
|
|||||||
this.productOptionRepository_
|
this.productOptionRepository_
|
||||||
)
|
)
|
||||||
|
|
||||||
const product = await this.retrieve(productId, {
|
const product = await this.withTransaction(manager).retrieve(productId, {
|
||||||
relations: ["options", "variants"],
|
relations: ["options", "variants"],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -833,7 +834,7 @@ class ProductService extends TransactionBaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.retrieve(productId)
|
const result = await this.withTransaction(manager).retrieve(productId)
|
||||||
|
|
||||||
await this.eventBus_
|
await this.eventBus_
|
||||||
.withTransaction(manager)
|
.withTransaction(manager)
|
||||||
@@ -849,7 +850,7 @@ class ProductService extends TransactionBaseService {
|
|||||||
return await this.atomicPhase_(async (manager) => {
|
return await this.atomicPhase_(async (manager) => {
|
||||||
const productRepo = manager.withRepository(this.productRepository_)
|
const productRepo = manager.withRepository(this.productRepository_)
|
||||||
|
|
||||||
const product = await this.retrieve(productId, {
|
const product = await this.withTransaction(manager).retrieve(productId, {
|
||||||
relations: ["variants"],
|
relations: ["variants"],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -898,7 +899,9 @@ class ProductService extends TransactionBaseService {
|
|||||||
this.productOptionRepository_
|
this.productOptionRepository_
|
||||||
)
|
)
|
||||||
|
|
||||||
const product = await this.retrieve(productId, { relations: ["options"] })
|
const product = await this.withTransaction(manager).retrieve(productId, {
|
||||||
|
relations: ["options"],
|
||||||
|
})
|
||||||
|
|
||||||
const { title, values } = data
|
const { title, values } = data
|
||||||
|
|
||||||
@@ -973,7 +976,7 @@ class ProductService extends TransactionBaseService {
|
|||||||
this.productOptionRepository_
|
this.productOptionRepository_
|
||||||
)
|
)
|
||||||
|
|
||||||
const product = await this.retrieve(productId, {
|
const product = await this.withTransaction(manager).retrieve(productId, {
|
||||||
relations: ["variants", "variants.options"],
|
relations: ["variants", "variants.options"],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user