oakkose
oakkose

Reputation: 405

Golang Goroutines Handle Concurrently Multiple Request

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

Answers (1)

PotatoesFall
PotatoesFall

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

Related Questions