Reputation: 405
User can purchase products with my project. However, product stock count must be equal or greater then requested quantity in purchase request. If It is ok, then a purchase record is saved on the table. After then, stock count is updated for this product in the another table. So there are 3 operations and if I send 20 concurrently requests to purchase 20 of product A which has 100 stock count in the db from another golang app, it works wrongly. It opens 20 purchase records in db and stock count updated as 60. Normally, after 5 requests operated, it must throws error because of stock count of product. I could not tweak method for this purpose. How Can I do?
This is the method, It calls repository methods 3 times but there is no special operations there only gorm save and update operations:
var wg sync.WaitGroup
wg.Add(1)
errs := make(chan error, 1)
go func(ctx context.Context, request *dto.CreateTicketPurchaseRequest) {
defer wg.Done()
productStockCount, err := t.stockRepo.GetTicketStockCount(productId) // first check available stock count
if err != nil {
errs <- err
return
}
if productStockCount.StockCount < payload.Quantity {
err = fmt.Errorf("no enough products")
errs <- err
return
}
u := new(entity.TicketPurchases)
u.Quantity = payload.Quantity
u.UserId = payload.UserId
u.StockId = productId
err = t.purchaseRepo.PurchaseTicket(u) // if stock count is ok, then purchase product
if err != nil {
errs <- err
return
}
productStockCount.StockCount = productStockCount.StockCount - u.Quantity
err = t.stockRepo.RemoveStockCount(productStockCount) // update stock count for product
if err != nil {
errs <- err
return
}
close(errs)
}(ctx, payload)
wg.Wait()
if err = <-errs; err != nil {
return c.JSON(http.StatusBadRequest, nil)
}
Upvotes: 1
Views: 403
Reputation: 355
I am assuming multi-threaded performance is not critical and you are only running one instance of the app on the same database.
You could use a sync.Mutex to Lock the entire stockrepo. Lock the mutex before doing anything, and don't unlock until the transaction is done. It is recommended to use defer
with Unlock
.
example:
var mu sync.Mutex
func doStuff() {
mu.Lock()
defer mu.Unlock()
// get ticket count, check, update, etc.
}
a more efficient but more complex solution is to lock per productID, but that's a bit more complex and probably out of scope for your questions.
Upvotes: 1