From 98fc855ee0b5035fe4a1749af68232549154db81 Mon Sep 17 00:00:00 2001 From: DanB Date: Thu, 9 Jan 2014 20:03:01 +0100 Subject: [PATCH 1/8] Overwrite parameter added on AddBalance --- apier/v1/apier.go | 11 +++++----- apier/v1/apier_local_test.go | 40 +++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/apier/v1/apier.go b/apier/v1/apier.go index 70673cc57..b337c3410 100644 --- a/apier/v1/apier.go +++ b/apier/v1/apier.go @@ -93,11 +93,11 @@ type AttrAddBalance struct { BalanceId string Direction string Value float64 + Overwrite bool // When true it will reset if the balance is already there } func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error { tag := fmt.Sprintf("%s:%s:%s", attr.Direction, attr.Tenant, attr.Account) - if _, err := self.AccountDb.GetUserBalance(tag); err != nil { // create user balance if not exists ub := &engine.UserBalance{ @@ -108,7 +108,6 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error { return err } } - at := &engine.ActionTiming{ UserBalanceIds: []string{tag}, } @@ -116,9 +115,11 @@ func (self *ApierV1) AddBalance(attr *AttrAddBalance, reply *string) error { if attr.Direction == "" { attr.Direction = engine.OUTBOUND } - - at.SetActions(engine.Actions{&engine.Action{ActionType: engine.TOPUP, BalanceId: attr.BalanceId, Direction: attr.Direction, Balance: &engine.Balance{Value: attr.Value}}}) - + aType := engine.TOPUP + if attr.Overwrite { + aType = engine.TOPUP_RESET + } + at.SetActions(engine.Actions{&engine.Action{ActionType: aType, BalanceId: attr.BalanceId, Direction: attr.Direction, Balance: &engine.Balance{Value: attr.Value}}}) if err := at.Execute(); err != nil { *reply = err.Error() return err diff --git a/apier/v1/apier_local_test.go b/apier/v1/apier_local_test.go index 0c3ff8b51..71e9b2255 100644 --- a/apier/v1/apier_local_test.go +++ b/apier/v1/apier_local_test.go @@ -909,6 +909,32 @@ func TestApierAddBalance(t *testing.T) { } else if reply != "OK" { t.Errorf("Calling ApierV1.AddBalance received: %s", reply) } + attrs = &AttrAddBalance{Tenant: "cgrates.org", Account: "dan3", BalanceId: "*monetary", Direction: "*out", Value: 1.5} + if err := rater.Call("ApierV1.AddBalance", attrs, &reply); err != nil { + t.Error("Got error on ApierV1.AddBalance: ", err.Error()) + } else if reply != "OK" { + t.Errorf("Calling ApierV1.AddBalance received: %s", reply) + } + attrs = &AttrAddBalance{Tenant: "cgrates.org", Account: "dan3", BalanceId: "*monetary", Direction: "*out", Value: 2.1} + if err := rater.Call("ApierV1.AddBalance", attrs, &reply); err != nil { + t.Error("Got error on ApierV1.AddBalance: ", err.Error()) + } else if reply != "OK" { + t.Errorf("Calling ApierV1.AddBalance received: %s", reply) + } + attrs = &AttrAddBalance{Tenant: "cgrates.org", Account: "dan6", BalanceId: "*monetary", Direction: "*out", Value: 2.1} + if err := rater.Call("ApierV1.AddBalance", attrs, &reply); err != nil { + t.Error("Got error on ApierV1.AddBalance: ", err.Error()) + } else if reply != "OK" { + t.Errorf("Calling ApierV1.AddBalance received: %s", reply) + } + attrs = &AttrAddBalance{Tenant: "cgrates.org", Account: "dan6", BalanceId: "*monetary", Direction: "*out", Value: 1, Overwrite: true} + if err := rater.Call("ApierV1.AddBalance", attrs, &reply); err != nil { + t.Error("Got error on ApierV1.AddBalance: ", err.Error()) + } else if reply != "OK" { + t.Errorf("Calling ApierV1.AddBalance received: %s", reply) + } + + } // Test here ExecuteAction @@ -985,7 +1011,7 @@ func TestApierAddTriggeredAction(t *testing.T) { reply2 := "" attrs2 := new(AttrAddActionTrigger) *attrs2 = *attrs - attrs2.Account = "dan3" // Does not exist so it should error when adding triggers on it + attrs2.Account = "dan10" // Does not exist so it should error when adding triggers on it // Add trigger to an account which does n exist if err := rater.Call("ApierV1.AddTriggeredAction", attrs2, &reply2); err == nil || reply2 == "OK" { t.Error("Expecting error on ApierV1.AddTriggeredAction.", err, reply2) @@ -1122,6 +1148,18 @@ func TestApierGetBalance(t *testing.T) { } else if reply != 10 { t.Errorf("Calling ApierV1.GetBalance expected: 10, received: %f", reply) } + attrs = &AttrGetBalance{Tenant: "cgrates.org", Account: "dan3", BalanceId: "*monetary", Direction: "*out"} + if err := rater.Call("ApierV1.GetBalance", attrs, &reply); err != nil { + t.Error("Got error on ApierV1.GetBalance: ", err.Error()) + } else if reply != 3.6 { + t.Errorf("Calling ApierV1.GetBalance expected: 3.6, received: %f", reply) + } + attrs = &AttrGetBalance{Tenant: "cgrates.org", Account: "dan6", BalanceId: "*monetary", Direction: "*out"} + if err := rater.Call("ApierV1.GetBalance", attrs, &reply); err != nil { + t.Error("Got error on ApierV1.GetBalance: ", err.Error()) + } else if reply != 1 { + t.Errorf("Calling ApierV1.GetBalance expected: 1, received: %f", reply) + } } // Test here LoadTariffPlanFromFolder From 7ee915e916d7319be11b62e71d4406bc7fd8836b Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 9 Jan 2014 21:59:41 +0200 Subject: [PATCH 2/8] avoid keeping mutex locked over scheduler wait --- scheduler/scheduler.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scheduler/scheduler.go b/scheduler/scheduler.go index b655a4be6..a25efc7f5 100644 --- a/scheduler/scheduler.go +++ b/scheduler/scheduler.go @@ -52,19 +52,20 @@ func (s *Scheduler) Loop() { s.queue = append(s.queue, a0) s.queue = s.queue[1:] sort.Sort(s.queue) + s.Unlock() } else { + s.Unlock() d := a0.GetNextStartTime().Sub(now) // engine.Logger.Info(fmt.Sprintf("Timer set to wait for %v", d)) s.timer = time.NewTimer(d) select { case <-s.timer.C: // timer has expired - engine.Logger.Info(fmt.Sprintf("Time for action on %v", s.queue[0])) + engine.Logger.Info(fmt.Sprintf("Time for action on %v", a0)) case <-s.restartLoop: // nothing to do, just continue the loop } } - s.Unlock() } } From 0278f08de48266ec10f11c1792868acd6dd41329 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 9 Jan 2014 22:16:49 +0200 Subject: [PATCH 3/8] more lock releasing --- scheduler/scheduler.go | 1 + 1 file changed, 1 insertion(+) diff --git a/scheduler/scheduler.go b/scheduler/scheduler.go index a25efc7f5..77d6ff077 100644 --- a/scheduler/scheduler.go +++ b/scheduler/scheduler.go @@ -42,6 +42,7 @@ func (s *Scheduler) Loop() { for { s.Lock() for len(s.queue) == 0 { //hang here if empty + s.Unlock() <-s.restartLoop } a0 := s.queue[0] From 127e7650faa5250dfcc6dd51bef6957a7939df03 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 9 Jan 2014 22:31:08 +0200 Subject: [PATCH 4/8] moved locking down south --- scheduler/scheduler.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scheduler/scheduler.go b/scheduler/scheduler.go index 77d6ff077..731236c7c 100644 --- a/scheduler/scheduler.go +++ b/scheduler/scheduler.go @@ -40,11 +40,10 @@ func NewScheduler() *Scheduler { func (s *Scheduler) Loop() { for { - s.Lock() for len(s.queue) == 0 { //hang here if empty - s.Unlock() <-s.restartLoop } + s.Lock() a0 := s.queue[0] now := time.Now() if a0.GetNextStartTime().Equal(now) || a0.GetNextStartTime().Before(now) { From 2cd548b81002ab01cc42f3078ba92c983dd1a825 Mon Sep 17 00:00:00 2001 From: DanB Date: Fri, 10 Jan 2014 10:44:09 +0100 Subject: [PATCH 5/8] Adding ActionTimings lock in scheduler --- apier/v1/accounts.go | 10 ++-------- scheduler/scheduler.go | 5 ++++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/apier/v1/accounts.go b/apier/v1/accounts.go index ee2ab2b36..a374c91c9 100644 --- a/apier/v1/accounts.go +++ b/apier/v1/accounts.go @@ -32,12 +32,6 @@ type AttrAcntAction struct { Direction string } -// Returns the balance id as used internally -// eg: *out:cgrates.org:1005 -func BalanceId(tenant, account, direction string) string { - return fmt.Sprintf("%s:%s:%s", direction, tenant, account) -} - type AccountActionTiming struct { Id string // The id to reference this particular ActionTiming ActionTimingsId string // The id of the ActionTimings profile attached to the account @@ -56,7 +50,7 @@ func (self *ApierV1) GetAccountActionTimings(attrs AttrAcntAction, reply *[]*Acc } for _, ats := range allATs { for _, at := range ats { - if utils.IsSliceMember(at.UserBalanceIds, BalanceId(attrs.Tenant, attrs.Account, attrs.Direction)) { + if utils.IsSliceMember(at.UserBalanceIds, utils.BalanceKey(attrs.Tenant, attrs.Account, attrs.Direction)) { accountATs = append(accountATs, &AccountActionTiming{Id: at.Id, ActionTimingsId: at.Tag, ActionsId: at.ActionsId, NextExecTime: at.GetNextStartTime()}) } } @@ -84,7 +78,7 @@ func (self *ApierV1) RemActionTiming(attrs AttrRemActionTiming, reply *string) e return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) } } - _, err := engine.AccLock.Guard(engine.ACTION_TIMING_PREFIX+attrs.ActionTimingId, func() (float64, error) { // ToDo: Expand the scheduler to consider the locks also + _, err := engine.AccLock.Guard(engine.ACTION_TIMING_PREFIX, func() (float64, error) { // ToDo: Expand the scheduler to consider the locks also ats, err := self.AccountDb.GetActionTimings(attrs.ActionTimingsId) if err != nil { return 0, err diff --git a/scheduler/scheduler.go b/scheduler/scheduler.go index 731236c7c..14a1a50eb 100644 --- a/scheduler/scheduler.go +++ b/scheduler/scheduler.go @@ -94,7 +94,10 @@ func (s *Scheduler) LoadActionTimings(storage engine.AccountingStorage) { } } if toBeSaved { - storage.SetActionTimings(key, newAts) + engine.AccLock.Guard(engine.ACTION_TIMING_PREFIX, func() (float64, error) { + storage.SetActionTimings(key, newAts) + return 0, nil + }) } } sort.Sort(s.queue) From f393b600d43bef1f2ac5becc9850c8ed5ee45bf8 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 10 Jan 2014 11:46:18 +0200 Subject: [PATCH 6/8] better locking for account guard --- engine/accountlock.go | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/engine/accountlock.go b/engine/accountlock.go index a781447ed..6ea5c104d 100644 --- a/engine/accountlock.go +++ b/engine/accountlock.go @@ -30,7 +30,7 @@ func init() { type AccountLock struct { queue map[string]chan bool - sync.Mutex + sync.RWMutex } func NewAccountLock() *AccountLock { @@ -38,13 +38,15 @@ func NewAccountLock() *AccountLock { } func (cm *AccountLock) GuardGetCost(name string, handler func() (*CallCost, error)) (reply *CallCost, err error) { - cm.Lock() + cm.RLock() lock, exists := AccLock.queue[name] + cm.RUnlock() if !exists { + cm.Lock() lock = make(chan bool, 1) AccLock.queue[name] = lock + cm.Unlock() } - cm.Unlock() lock <- true reply, err = handler() <-lock @@ -52,13 +54,35 @@ func (cm *AccountLock) GuardGetCost(name string, handler func() (*CallCost, erro } func (cm *AccountLock) Guard(name string, handler func() (float64, error)) (reply float64, err error) { + cm.RLock() lock, exists := AccLock.queue[name] + cm.RUnlock() if !exists { + cm.Lock() lock = make(chan bool, 1) AccLock.queue[name] = lock + cm.Unlock() } lock <- true reply, err = handler() <-lock return } + +func (cm *AccountLock) GuardMany(names []string, handler func() (float64, error)) (reply float64, err error) { + for _, name := range names { + cm.RLock() + lock, exists := AccLock.queue[name] + cm.RUnlock() + if !exists { + cm.Lock() + lock = make(chan bool, 1) + AccLock.queue[name] = lock + cm.Unlock() + } + lock <- true + reply, err = handler() + <-lock + } + return +} From 9f125e638f1db4b945545797a21e835a95e2affe Mon Sep 17 00:00:00 2001 From: DanB Date: Fri, 10 Jan 2014 12:51:37 +0100 Subject: [PATCH 7/8] Apier: AddAccount -> SetAccount --- apier/v1/accounts.go | 72 +++++++++++++++++++++++++++++++++++- apier/v1/apier.go | 43 --------------------- apier/v1/apier_local_test.go | 43 ++++++++++----------- console/add_account.go | 6 +-- docs/apicalls.rst | 8 ++-- 5 files changed, 100 insertions(+), 72 deletions(-) diff --git a/apier/v1/accounts.go b/apier/v1/accounts.go index a374c91c9..40bd8e346 100644 --- a/apier/v1/accounts.go +++ b/apier/v1/accounts.go @@ -78,7 +78,7 @@ func (self *ApierV1) RemActionTiming(attrs AttrRemActionTiming, reply *string) e return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) } } - _, err := engine.AccLock.Guard(engine.ACTION_TIMING_PREFIX, func() (float64, error) { // ToDo: Expand the scheduler to consider the locks also + _, err := engine.AccLock.Guard(engine.ACTION_TIMING_PREFIX, func() (float64, error) { ats, err := self.AccountDb.GetActionTimings(attrs.ActionTimingsId) if err != nil { return 0, err @@ -154,3 +154,73 @@ func (self *ApierV1) RemAccountActionTriggers(attrs AttrRemAcntActionTriggers, r *reply = OK return nil } + + +type AttrSetAccount struct { + Tenant string + Direction string + Account string + Type string // <*prepaid|*postpaid> + ActionTimingsId string +} + +// Ads a new account into dataDb. If already defined, returns success. +func (self *ApierV1) SetAccount(attr AttrSetAccount, reply *string) error { + if missing := utils.MissingStructFields(&attr, []string{"Tenant", "Direction", "Account"}); len(missing) != 0 { + return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) + } + balanceId := utils.BalanceKey(attr.Tenant, attr.Account, attr.Direction) + var ub *engine.UserBalance + var ats engine.ActionTimings + _, err := engine.AccLock.Guard(balanceId, func() (float64, error) { + if bal, _ := self.AccountDb.GetUserBalance(balanceId); bal != nil { + ub = bal + } else { // Not found in db, create it here + if len(attr.Type) == 0 { + attr.Type = engine.UB_TYPE_PREPAID + } else if !utils.IsSliceMember([]string{engine.UB_TYPE_POSTPAID, engine.UB_TYPE_PREPAID}, attr.Type) { + return 0, fmt.Errorf("%s:%s", utils.ERR_MANDATORY_IE_MISSING, "Type") + } + ub = &engine.UserBalance{ + Id: balanceId, + Type: attr.Type, + } + } + + if len(attr.ActionTimingsId) != 0 { + var err error + ats, err = self.AccountDb.GetActionTimings(attr.ActionTimingsId) + if err != nil { + return 0, err + } + for _, at := range ats { + at.UserBalanceIds = append(at.UserBalanceIds, balanceId) + } + } + // All prepared, save account + if err := self.AccountDb.SetUserBalance(ub); err != nil { + return 0, err + } + return 0, nil + }) + if err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } + if len(ats) != 0 { + _, err := engine.AccLock.Guard(engine.ACTION_TIMING_PREFIX, func() (float64, error) { // ToDo: Try locking it above on read somehow + if err := self.AccountDb.SetActionTimings(attr.ActionTimingsId, ats); err != nil { + return 0, err + } + return 0, nil + }) + if err != nil { + return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) + } + if self.Sched != nil { + self.Sched.LoadActionTimings(self.AccountDb) + self.Sched.Restart() + } + } + *reply = OK // This will mark saving of the account, error still can show up in actionTimingsId + return nil +} diff --git a/apier/v1/apier.go b/apier/v1/apier.go index b337c3410..0fb805ece 100644 --- a/apier/v1/apier.go +++ b/apier/v1/apier.go @@ -403,49 +403,6 @@ func (self *ApierV1) AddTriggeredAction(attr AttrAddActionTrigger, reply *string return nil } -type AttrAddAccount struct { - Tenant string - Direction string - Account string - Type string // prepaid-postpaid - ActionTimingsId string -} - -// Ads a new account into dataDb. If already defined, returns success. -func (self *ApierV1) AddAccount(attr AttrAddAccount, reply *string) error { - if missing := utils.MissingStructFields(&attr, []string{"Tenant", "Direction", "Account", "Type", "ActionTimingsId"}); len(missing) != 0 { - return fmt.Errorf("%s:%v", utils.ERR_MANDATORY_IE_MISSING, missing) - } - tag := utils.BalanceKey(attr.Tenant, attr.Account, attr.Direction) - ub := &engine.UserBalance{ - Id: tag, - Type: attr.Type, - } - - if attr.ActionTimingsId != "" { - if ats, err := self.AccountDb.GetActionTimings(attr.ActionTimingsId); err == nil { - for _, at := range ats { - engine.Logger.Debug(fmt.Sprintf("Found action timings: %v", at)) - at.UserBalanceIds = append(at.UserBalanceIds, tag) - } - err = self.AccountDb.SetActionTimings(attr.ActionTimingsId, ats) - if err != nil { - if self.Sched != nil { - self.Sched.LoadActionTimings(self.AccountDb) - self.Sched.Restart() - } - } - if err := self.AccountDb.SetUserBalance(ub); err != nil { - return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) - } - } else { - return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) - } - } - *reply = OK - return nil -} - // Process dependencies and load a specific AccountActions profile from storDb into dataDb. func (self *ApierV1) LoadAccountActions(attrs utils.TPAccountActions, reply *string) error { if missing := utils.MissingStructFields(&attrs, []string{"TPid", "LoadId", "Tenant", "Account", "Direction"}); len(missing) != 0 { diff --git a/apier/v1/apier_local_test.go b/apier/v1/apier_local_test.go index 71e9b2255..bc9a86f51 100644 --- a/apier/v1/apier_local_test.go +++ b/apier/v1/apier_local_test.go @@ -1062,35 +1062,36 @@ func TestApierRemAccountActionTriggers(t *testing.T) { } -// Test here AddAccount -func TestApierAddAccount(t *testing.T) { - if !*testLocal { - return - } - reply := "" - attrs := &AttrAddAccount{Tenant: "cgrates.org", Direction: "*out", Account: "dan4", Type: "prepaid", ActionTimingsId: "ATMS_1"} - if err := rater.Call("ApierV1.AddAccount", attrs, &reply); err != nil { - t.Error("Got error on ApierV1.AddAccount: ", err.Error()) - } else if reply != "OK" { - t.Errorf("Calling ApierV1.AddAccount received: %s", reply) - } - reply2 := "" - attrs2 := new(AttrAddAccount) - *attrs2 = *attrs - attrs2.ActionTimingsId = "DUMMY_DATA" // Does not exist so it should error when adding triggers on it - // Add account with actions timing which does not exist - if err := rater.Call("ApierV1.AddAccount", attrs2, &reply2); err == nil || reply2 == "OK" { - t.Error("Expecting error on ApierV1.AddAccount.", err, reply2) - } +// Test here SetAccount +func TestApierSetAccount(t *testing.T) { + if !*testLocal { + return + } + reply := "" + attrs := &AttrSetAccount{Tenant: "cgrates.org", Direction: "*out", Account: "dan7", Type: "*prepaid", ActionTimingsId: "ATMS_1"} + if err := rater.Call("ApierV1.SetAccount", attrs, &reply); err != nil { + t.Error("Got error on ApierV1.SetAccount: ", err.Error()) + } else if reply != "OK" { + t.Errorf("Calling ApierV1.SetAccount received: %s", reply) + } + reply2 := "" + attrs2 := new(AttrSetAccount) + *attrs2 = *attrs + attrs2.ActionTimingsId = "DUMMY_DATA" // Does not exist so it should error when adding triggers on it + // Add account with actions timing which does not exist + if err := rater.Call("ApierV1.SetAccount", attrs2, &reply2); err == nil || reply2 == "OK" { // OK is not welcomed + t.Error("Expecting error on ApierV1.SetAccount.", err, reply2) + } } + // Test here GetAccountActionTimings func TestApierGetAccountActionTimings(t *testing.T) { if !*testLocal { return } var reply []*AccountActionTiming - req := AttrAcntAction{Tenant: "cgrates.org", Account:"dan4", Direction: "*out"} + req := AttrAcntAction{Tenant: "cgrates.org", Account:"dan7", Direction: "*out"} if err := rater.Call("ApierV1.GetAccountActionTimings", req, &reply); err != nil { t.Error("Got error on ApierV1.GetAccountActionTimings: ", err.Error()) } else if len(reply) != 1 { diff --git a/console/add_account.go b/console/add_account.go index 85ce27d52..ade30f214 100644 --- a/console/add_account.go +++ b/console/add_account.go @@ -30,7 +30,7 @@ func init() { // Commander implementation type CmdAddAccount struct { rpcMethod string - rpcParams *apier.AttrAddAccount + rpcParams *apier.AttrSetAccount rpcResult string } @@ -41,8 +41,8 @@ func (self *CmdAddAccount) Usage(name string) string { // set param defaults func (self *CmdAddAccount) defaults() error { - self.rpcMethod = "ApierV1.AddAccount" - self.rpcParams = &apier.AttrAddAccount{Direction: "*out"} + self.rpcMethod = "ApierV1.SetAccount" + self.rpcParams = &apier.AttrSetAccount{Direction: "*out"} return nil } diff --git a/docs/apicalls.rst b/docs/apicalls.rst index 0b6b94fdd..51e77717e 100644 --- a/docs/apicalls.rst +++ b/docs/apicalls.rst @@ -326,21 +326,21 @@ AddTriggeredAction Example AddTriggeredAction(attr \*AttrAddActionTrigger, reply \*float64) -AddAcount +SetAcount +++++++++ :: - type AttrAddAccount struct { + type AttrSetAccount struct { Tenant string Direction string Account string - Type string // prepaid-postpaid + Type string // <*prepaid|*postpaid> ActionTimingsId string } Example - AddAccount(attr \*AttrAddAccount, reply \*float64) + AddAccount(attr \*AttrAddAccount, reply \*string) From 8aafd0ec773398bea2aa87cde02ab5668c524fe3 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Fri, 10 Jan 2014 19:02:40 +0200 Subject: [PATCH 8/8] better guardmany locking --- engine/accountlock.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/engine/accountlock.go b/engine/accountlock.go index 6ea5c104d..ea786132d 100644 --- a/engine/accountlock.go +++ b/engine/accountlock.go @@ -81,7 +81,10 @@ func (cm *AccountLock) GuardMany(names []string, handler func() (float64, error) cm.Unlock() } lock <- true - reply, err = handler() + } + reply, err = handler() + for _, name := range names { + lock := AccLock.queue[name] <-lock } return