From ccb07769c83f8882b388bbb138ce8655448212bc Mon Sep 17 00:00:00 2001 From: fishmeister09 Date: Sat, 27 Sep 2025 13:41:49 +0530 Subject: [PATCH] changes made by sutra-cli for fixing the race condition --- data-service/package-lock.json | 16 +++++++ data-service/package.json | 9 ++-- data-service/src/server.ts | 84 ++++++++++++++++++--------------- order-service/package-lock.json | 16 +++++++ order-service/package.json | 1 + order-service/src/api.ts | 6 ++- 6 files changed, 89 insertions(+), 43 deletions(-) diff --git a/data-service/package-lock.json b/data-service/package-lock.json index b91a604..23cd457 100644 --- a/data-service/package-lock.json +++ b/data-service/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "async-mutex": "^0.5.0", "cors": "^2.8.5", "express": "^4.18.2", "uuid": "^9.0.1" @@ -268,6 +269,15 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -1116,6 +1126,12 @@ } } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/data-service/package.json b/data-service/package.json index bbe4e0b..9616d3f 100644 --- a/data-service/package.json +++ b/data-service/package.json @@ -18,16 +18,17 @@ "author": "Demo Project", "license": "MIT", "dependencies": { - "express": "^4.18.2", + "async-mutex": "^0.5.0", "cors": "^2.8.5", + "express": "^4.18.2", "uuid": "^9.0.1" }, "devDependencies": { - "@types/express": "^4.17.21", "@types/cors": "^2.8.19", + "@types/express": "^4.17.21", "@types/node": "^20.8.0", "@types/uuid": "^9.0.7", - "typescript": "^5.2.2", - "ts-node": "^10.9.1" + "ts-node": "^10.9.1", + "typescript": "^5.2.2" } } diff --git a/data-service/src/server.ts b/data-service/src/server.ts index 274561e..038d96d 100644 --- a/data-service/src/server.ts +++ b/data-service/src/server.ts @@ -1,6 +1,7 @@ import express, { Request, Response, NextFunction } from 'express'; import cors from 'cors'; import fs from 'fs/promises'; +import { Mutex } from 'async-mutex'; import path from 'path'; interface InventoryItem { @@ -48,6 +49,7 @@ class DataService { private inventoryPath: string; private cachedOrders: OrderDatabase | null = null; private cachedInventory: InventoryDatabase | null = null; + private inventoryMutex = new Mutex(); constructor() { this.ordersPath = path.join(__dirname, '../data/orders.json'); @@ -251,63 +253,69 @@ class DataService { } async reserveStock(productName: string, quantity: number): Promise { - const db = await this.loadInventory(); - const item = db.inventory.find( - (i) => i.productName.toLowerCase() === productName.toLowerCase() - ); + return await this.inventoryMutex.runExclusive(async () => { + const db = await this.loadInventory(); + const item = db.inventory.find( + (i) => i.productName.toLowerCase() === productName.toLowerCase() + ); - if (!item) { - return false; - } + if (!item) { + return false; + } - const available = item.quantity - item.reservedQuantity; - if (available < quantity) { - return false; - } + const available = item.quantity - item.reservedQuantity; + if (available < quantity) { + return false; + } - item.reservedQuantity += quantity; - item.lastUpdated = new Date().toISOString(); + item.reservedQuantity += quantity; + item.lastUpdated = new Date().toISOString(); - await this.saveInventory(db); - return true; + await this.saveInventory(db); + return true; + }); } async fulfillOrder(productName: string, quantity: number): Promise { - const db = await this.loadInventory(); - const item = db.inventory.find( - (i) => i.productName.toLowerCase() === productName.toLowerCase() - ); + return await this.inventoryMutex.runExclusive(async () => { + const db = await this.loadInventory(); + const item = db.inventory.find( + (i) => i.productName.toLowerCase() === productName.toLowerCase() + ); - if (!item || item.reservedQuantity < quantity) { - return false; - } + if (!item || item.reservedQuantity < quantity) { + return false; + } - item.quantity -= quantity; - item.reservedQuantity -= quantity; - item.lastUpdated = new Date().toISOString(); + item.quantity -= quantity; + item.reservedQuantity -= quantity; + item.lastUpdated = new Date().toISOString(); - await this.saveInventory(db); - return true; + await this.saveInventory(db); + return true; + }); } async cancelReservation( productName: string, quantity: number ): Promise { - const db = await this.loadInventory(); - const item = db.inventory.find( - (i) => i.productName.toLowerCase() === productName.toLowerCase() - ); + return await this.inventoryMutex.runExclusive(async () => { + const db = await this.loadInventory(); + const item = db.inventory.find( + (i) => i.productName.toLowerCase() === productName.toLowerCase() + ); - if (!item) { - return false; - } + if (!item) { + return false; + } - item.reservedQuantity = Math.max(0, item.reservedQuantity - quantity); - item.lastUpdated = new Date().toISOString(); + item.reservedQuantity = Math.max(0, item.reservedQuantity - quantity); + item.lastUpdated = new Date().toISOString(); - await this.saveInventory(db); - return true; + await this.saveInventory(db); + return true; + }); } } diff --git a/order-service/package-lock.json b/order-service/package-lock.json index e7feebf..94659fd 100644 --- a/order-service/package-lock.json +++ b/order-service/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "amqplib": "^0.10.3", + "async-mutex": "^0.5.0", "axios": "^1.6.0", "commander": "^11.1.0", "cors": "^2.8.5", @@ -295,6 +296,15 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1280,6 +1290,12 @@ } } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/order-service/package.json b/order-service/package.json index 096bf93..5634ab5 100644 --- a/order-service/package.json +++ b/order-service/package.json @@ -20,6 +20,7 @@ "license": "MIT", "dependencies": { "amqplib": "^0.10.3", + "async-mutex": "^0.5.0", "axios": "^1.6.0", "commander": "^11.1.0", "cors": "^2.8.5", diff --git a/order-service/src/api.ts b/order-service/src/api.ts index ae36061..cd4034b 100644 --- a/order-service/src/api.ts +++ b/order-service/src/api.ts @@ -1,6 +1,7 @@ import express, { Request, Response, NextFunction } from 'express'; import cors from 'cors'; import { v4 as uuidv4 } from 'uuid'; +import { Mutex } from 'async-mutex'; import axios, { AxiosError } from 'axios'; import { Order, @@ -58,6 +59,7 @@ app.use((err: Error, req: Request, res: Response, next: NextFunction) => { class OrderService { private dataServiceUrl: string; private products: Product[] = []; + private orderCreationMutex = new Mutex(); constructor(dataServiceUrl: string) { this.dataServiceUrl = dataServiceUrl; @@ -99,7 +101,8 @@ class OrderService { } async createOrder(request: OrderCreateRequest): Promise { - const db = await this.loadDatabase(); + return await this.orderCreationMutex.runExclusive(async () => { + const db = await this.loadDatabase(); // Find product in catalog const product = this.products.find( @@ -168,6 +171,7 @@ class OrderService { ); } return order; + }); } async getOrder(orderId: string): Promise {