Search code examples
postgresqlgotransactionsgo-gorm

Gorm Transaction Error error = transaction has already been committed or rolled back


I'm aiming to do transaction management in the code below. If there is an error in one of the strategies, I am trying to rollback. While testing the code, I noticed that if the rollback or commit command works once, it gives error = transaction has already been committed or rolled back the second time. How can I resolve this error?

func (d *DistributeService) Distribute(vehicleNumberPlate string, request model.DistributeRequest) (*model.DistributeResponse, error) {
    var response model.DistributeResponse
    response.Vehicle = vehicleNumberPlate
    var routeList []model.RouteResponse
    tx := d.repo.BeginTransaction()
    for _, routes := range request.RouteRequest {
        var routeResponse model.RouteResponse
        strategy, isStrategyExists := d.strategies[routes.DeliveryPoint]
        if isStrategyExists {
            resp, err := strategy.Distribute(routes.Deliveries, vehicleNumberPlate, tx)
            if err != nil {
                tx.Rollback()
                logrus.Errorf("Error while distributing: %v", err)
                return nil, err
            }
            routeResponse.DeliveryPoint = routes.DeliveryPoint
            routeResponse.Deliveries = *resp
            routeList = append(routeList, routeResponse)
        } else {
            logrus.Errorf("Invalid delivery point: %v", routes.DeliveryPoint)
            return nil, errors.New("invalid delivery point")
        }
    }
    response.RouteResponse = routeList
    err := d.checkSackPackagesAreUnloaded()
    tx.Commit()
    if err != nil {
        return nil, err
    }
    return &response, nil
}

Solution

  • You're probably using the same transaction object in each call. If it was closed once - for whatever reason - you need to create a new transaction object.

    Why do I say you are probably using the same transaction object you ask? Because you're basically passing a pointer to d *DistributeService. And then use tx := d.repo.BeginTransaction(). We can't tell what that code does, but I'm pretty sure you're returning the same transaction object here for subsequent runs.

    The solution is to create a new transaction object every time this method is called, f.e. with tx := db.Begin() as described in the docs.