Skip to content

Commit

Permalink
fix: dead lock
Browse files Browse the repository at this point in the history
  • Loading branch information
rema424 committed Nov 17, 2019
1 parent ef0f1f5 commit 59c683e
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 100 deletions.
17 changes: 8 additions & 9 deletions cmd/http/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/rema424/sqlxx"
)

var e = createMux()
Expand Down Expand Up @@ -45,10 +44,10 @@ func init() {
if err != nil {
log.Fatalln(err)
}
acsr, err := sqlxx.Open(db)
if err != nil {
log.Fatalln(err)
}
// acsr, err := sqlxx.Open(db)
// if err != nil {
// log.Fatalln(err)
// }

// Service2
gateway2 := service2.NewGateway(db)
Expand All @@ -57,10 +56,10 @@ func init() {
// provider2 := service2.NewProvider(mockGateway2)

// Service3
gateway3 := service3.NewGateway(acsr)
provider3 := service3.NewProvider(gateway3)
// mockGateway3 := service3.NewMockGateway(service3.NewMockDB())
// provider3 := service3.NewProvider(mockGateway3)
// gateway3 := service3.NewGateway(acsr)
// provider3 := service3.NewProvider(gateway3)
mockGateway3 := service3.NewMockGateway(service3.NewMockDB())
provider3 := service3.NewProvider(mockGateway3)

ctrl := &controller.Controller{}
ctrl2 := controller.NewController2(provider2)
Expand Down
68 changes: 29 additions & 39 deletions internal/service3/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package service3

import (
"context"
"fmt"
"log"

"github.com/rema424/sqlxx"
Expand Down Expand Up @@ -34,55 +35,44 @@ func (g *Gateway) OpenAccount(ctx context.Context, initialAmmount int) (Account,
return Account{ID: id, Balance: initialAmmount}, nil
}

// IsBalanceSufficient ...
func (g *Gateway) IsBalanceSufficient(ctx context.Context, accountID int64, ammount int) (bool, error) {
// 行ロックを取得する(FOR UPDATE)
q := `SELECT COALESCE(balance, 0) AS 'tekitode' FROM account WHERE id = ? FOR UPDATE;`

// 残高を取得
var b int
if err := g.db.Get(ctx, &b, q, accountID); err != nil {
return false, err
// GetAccountsForTransfer ...
func (g *Gateway) GetAccountsForTransfer(ctx context.Context, fromID, toID int64) (from, to Account, err error) {
// 送金に関わるアカウントにはロックをかけて取得する
q := `
SELECT
COALESCE(id, 0) AS 'aikawarazu',
COALESCE(balance, 0) AS 'tekitode'
FROM account
WHERE id = ? OR id = ?
FOR UPDATE;
`
var dest []Account
if err := g.db.Select(ctx, &dest, q, fromID, toID); err != nil {
return from, to, err
}

// 残高を確認
return b >= ammount, nil
}
if len(dest) != 2 {
return from, to, fmt.Errorf("gateway: account not found for transfer")
}

// DecreaseBalance ...
func (g *Gateway) DecreaseBalance(ctx context.Context, id int64, ammount int) (Account, error) {
q := `UPDATE account SET balance = balance - ? WHERE id = ?;`
_, err := g.db.Exec(ctx, q, ammount, id)
if err != nil {
return Account{}, err
for _, a := range dest {
if a.ID == fromID {
from = a
} else if a.ID == toID {
to = a
}
}

return g.getAccountByID(ctx, id)
return from, to, nil
}

// IncreaseBalance ...
func (g *Gateway) IncreaseBalance(ctx context.Context, id int64, ammount int) (Account, error) {
q := `UPDATE account SET balance = balance + ? WHERE id = ?;`
_, err := g.db.Exec(ctx, q, ammount, id)
// UpdateBalance ...
func (g *Gateway) UpdateBalance(ctx context.Context, a Account) (Account, error) {
q := `UPDATE account SET balance = :tekitode WHERE id = :aikawarazu;`
_, err := g.db.NamedExec(ctx, q, a)
if err != nil {
return Account{}, err
}

return g.getAccountByID(ctx, id)
}

func (g *Gateway) getAccountByID(ctx context.Context, id int64) (Account, error) {
q := `
SELECT
COALESCE(id, 0) AS 'aikawarazu',
COALESCE(balance, 0) AS 'tekitode'
FROM account
WHERE id = ?;
`
var a Account
if err := g.db.Get(ctx, &a, q, id); err != nil {
return a, err
}
return a, nil
}

Expand Down
53 changes: 12 additions & 41 deletions internal/service3/mock_gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,65 +57,36 @@ func (g *MockGateway) OpenAccount(ctx context.Context, initialAmmount int) (Acco
return a, nil
}

// IsBalanceSufficient ...
func (g *MockGateway) IsBalanceSufficient(ctx context.Context, accountID int64, ammount int) (bool, error) {
// GetAccountsForTransfer ...
func (g *MockGateway) GetAccountsForTransfer(ctx context.Context, fromID, toID int64) (from, to Account, err error) {
if !isInTx(ctx) {
g.db.mu.Lock()
defer g.db.mu.Unlock()
}

account, ok := g.db.data[accountID]
var ok bool
from, ok = g.db.data[fromID]
if !ok {
return false, fmt.Errorf("gateway: account not found - id: %d", accountID)
return Account{}, Account{}, fmt.Errorf("gateway: account not found - accoutID: %d", fromID)
}

return account.Balance >= ammount, nil
}

func (g *MockGateway) getAccountByID(ctx context.Context, accountID int64) (Account, error) {
if !isInTx(ctx) {
g.db.mu.Lock()
defer g.db.mu.Unlock()
}

account, ok := g.db.data[accountID]
to, ok = g.db.data[toID]
if !ok {
return Account{}, fmt.Errorf("gateway: account not found - id: %d", accountID)
return Account{}, Account{}, fmt.Errorf("gateway: account not found - accoutID: %d", toID)
}

return account, nil
return from, to, nil
}

// DecreaseBalance ...
func (g *MockGateway) DecreaseBalance(ctx context.Context, id int64, ammount int) (Account, error) {
// UpdateBalance ...
func (g *MockGateway) UpdateBalance(ctx context.Context, a Account) (Account, error) {
if !isInTx(ctx) {
g.db.mu.Lock()
defer g.db.mu.Unlock()
}

account, err := g.getAccountByID(ctx, id)
if err != nil {
return Account{}, err
}
account.Balance -= ammount
g.db.data[id] = account
return account, nil
}

// IncreaseBalance ...
func (g *MockGateway) IncreaseBalance(ctx context.Context, id int64, ammount int) (Account, error) {
if !isInTx(ctx) {
g.db.mu.Lock()
defer g.db.mu.Unlock()
}

account, err := g.getAccountByID(ctx, id)
if err != nil {
return Account{}, err
}
account.Balance += ammount
g.db.data[id] = account
return account, nil
g.db.data[a.ID] = a
return a, nil
}

// RunInTransaction ...
Expand Down
11 changes: 11 additions & 0 deletions internal/service3/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,14 @@ type Account struct {
ID int64 `db:"aikawarazu"`
Balance int `db:"tekitode"`
}

// IsSufficient ...
func (a *Account) IsSufficient(ammount int) bool {
return a.Balance >= ammount
}

// Transfer ...
func (a *Account) Transfer(ammount int, to *Account) {
a.Balance -= ammount
to.Balance += ammount
}
33 changes: 25 additions & 8 deletions internal/service3/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,43 @@ func (p *Provider) Transfer(ctx context.Context, ammount int, fromID, toID int64
}

txFn := func(ctx context.Context) (interface{}, error) {
// 送金元の残高を確認
b, err := p.r.IsBalanceSufficient(ctx, fromID, ammount)
// 送金元、送金先の口座を取得する
from, to, err := p.r.GetAccountsForTransfer(ctx, fromID, toID)
if err != nil {
return Accounts{}, err
} else if !b {
return Accounts{}, fmt.Errorf("provider: balance is not sufficient - accountID: %d", fromID)
}

// 送金元の残高を減らす
from, err := p.r.DecreaseBalance(ctx, fromID, ammount)
// 送金元の残高を確認
if !from.IsSufficient(ammount) {
return Accounts{}, fmt.Errorf("provider: balance is not sufficient - accountID: %d", from.ID)
}

// 送金する
from.Transfer(ammount, &to)

// 送金元の残高を更新する
from, err = p.r.UpdateBalance(ctx, from)
if err != nil {
return Accounts{}, err
}

// 送金先の残高を増やす
to, err := p.r.IncreaseBalance(ctx, toID, ammount)
// 送金先の残高を更新する
to, err = p.r.UpdateBalance(ctx, to)
if err != nil {
return Accounts{}, err
}

// from, err = p.r.DecreaseBalance(ctx, fromID, ammount)
// if err != nil {
// return Accounts{}, err
// }

// // 送金先の残高を増やす
// to, err := p.r.IncreaseBalance(ctx, toID, ammount)
// if err != nil {
// return Accounts{}, err
// }

return Accounts{from: from, to: to}, nil
}

Expand Down
5 changes: 2 additions & 3 deletions internal/service3/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import "context"
type Repository interface {
RunInTransaction(context.Context, func(context.Context) (interface{}, error)) (interface{}, error)
OpenAccount(ctx context.Context, initialAmmount int) (Account, error)
IsBalanceSufficient(ctx context.Context, accountID int64, ammount int) (bool, error)
IncreaseBalance(ctx context.Context, accountID int64, ammount int) (Account, error)
DecreaseBalance(ctx context.Context, accountID int64, ammount int) (Account, error)
GetAccountsForTransfer(ctx context.Context, fromID, toID int64) (from, to Account, err error)
UpdateBalance(ctx context.Context, a Account) (Account, error)
}

0 comments on commit 59c683e

Please sign in to comment.