Skip to content

Commit 8cccd64

Browse files
authored
Fix: Improvement to Concurrency Tolerance (#363)
* fix: config corruption on concurrent program runs * fix: config corruptions during concurrent execution * conventional err syntax
1 parent 3644aaf commit 8cccd64

File tree

6 files changed

+57
-13
lines changed

6 files changed

+57
-13
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
vendor/*
2-
!vendor/vendor.json
2+
!vendor/vendor.json
3+
.vscode

bluemix/configuration/core_config/bx_config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ func (c *bxConfig) init() {
119119
}
120120

121121
func (c *bxConfig) read(cb func()) {
122-
c.lock.RLock()
123-
defer c.lock.RUnlock()
122+
/* concurrency note: init() calls the persistor's Load(), which has a flock,
123+
via lockedRead() and lockedWrite(), surrounding the critical sections */
124124

125125
c.init()
126126

bluemix/configuration/core_config/cf_config.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ type cfConfig struct {
8686
data *CFConfigData
8787
persistor configuration.Persistor
8888
initOnce *sync.Once
89-
lock sync.RWMutex
89+
lock sync.Mutex
9090
onError func(error)
9191
}
9292

@@ -119,9 +119,8 @@ func (c *cfConfig) init() {
119119
}
120120

121121
func (c *cfConfig) read(cb func()) {
122-
c.lock.RLock()
123-
defer c.lock.RUnlock()
124-
122+
/* concurrency note: init() calls the persistor's Load(), which has a flock,
123+
via lockedRead() and lockedWrite(), surrounding the critical sections */
125124
c.init()
126125

127126
cb()

bluemix/configuration/persistor.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package configuration
22

33
import (
4+
"context"
45
"io/ioutil"
56
"os"
67
"path/filepath"
8+
"time"
79

810
"github.com/IBM-Cloud/ibm-cloud-cli-sdk/common/file_helpers"
11+
"github.com/gofrs/flock"
912
)
1013

1114
const (
@@ -25,33 +28,71 @@ type Persistor interface {
2528
}
2629

2730
type DiskPersistor struct {
28-
filePath string
31+
filePath string
32+
fileLock *flock.Flock
33+
parentContext context.Context
2934
}
3035

3136
func NewDiskPersistor(path string) DiskPersistor {
3237
return DiskPersistor{
33-
filePath: path,
38+
filePath: path,
39+
fileLock: flock.New(path),
40+
parentContext: context.Background(),
3441
}
3542
}
3643

3744
func (dp DiskPersistor) Exists() bool {
3845
return file_helpers.FileExists(dp.filePath)
3946
}
4047

48+
func (dp *DiskPersistor) lockedRead(data DataInterface) error {
49+
lockCtx, cancelLockCtx := context.WithTimeout(dp.parentContext, 30*time.Second) /* allotting a 30-second timeout means there can be a maximum of 298 failed retrials (each up to 500 ms, as
50+
specified after the deferred call to cancelLockCtx). 30 appears to be a conventional value for a parent context passed to TryLockContext, as per docs */
51+
defer cancelLockCtx()
52+
_, lockErr := dp.fileLock.TryLockContext(lockCtx, 100*time.Millisecond) /* provide a file lock just while dp.read is called, because it calls an unmarshaling function
53+
The boolean (first return value) can be wild-carded because lockErr must be non-nil when the lock-acquiring fails (whereby the boolean will be false) */
54+
defer dp.fileLock.Unlock()
55+
if lockErr != nil {
56+
return lockErr
57+
}
58+
readErr := dp.read(data)
59+
if readErr != nil {
60+
return readErr
61+
}
62+
return nil
63+
}
64+
4165
func (dp DiskPersistor) Load(data DataInterface) error {
42-
err := dp.read(data)
66+
err := dp.lockedRead(data)
4367
if os.IsPermission(err) {
4468
return err
4569
}
4670

47-
if err != nil {
48-
err = dp.write(data)
71+
if err != nil { /* would happen if there was nothing to read (EOF) */
72+
err = dp.lockedWrite(data)
4973
}
5074
return err
5175
}
5276

77+
func (dp DiskPersistor) lockedWrite(data DataInterface) error {
78+
lockCtx, cancelLockCtx := context.WithTimeout(dp.parentContext, 30*time.Second) /* allotting a 30-second timeout means there can be a maximum of 298 failed retrials (each up to 500 ms, as
79+
specified after the deferred call to cancelLockCtx). 30 appears to be a conventional value for a parent context passed to TryLockContext, as per docs */
80+
defer cancelLockCtx()
81+
_, lockErr := dp.fileLock.TryLockContext(lockCtx, 100*time.Millisecond) /* provide a file lock just while dp.read is called, because it calls an unmarshaling function
82+
The boolean (first return value) can be wild-carded because lockErr must be non-nil when the lock-acquiring fails (whereby the boolean will be false) */
83+
defer dp.fileLock.Unlock()
84+
if lockErr != nil {
85+
return lockErr
86+
}
87+
writeErr := dp.write(data)
88+
if writeErr != nil {
89+
return writeErr
90+
}
91+
return nil
92+
}
93+
5394
func (dp DiskPersistor) Save(data DataInterface) error {
54-
return dp.write(data)
95+
return dp.lockedWrite(data)
5596
}
5697

5798
func (dp DiskPersistor) read(data DataInterface) error {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
require (
2323
github.com/BurntSushi/toml v1.1.0 // indirect
2424
github.com/davecgh/go-spew v1.1.1 // indirect
25+
github.com/gofrs/flock v0.8.1 // indirect
2526
github.com/golang/protobuf v1.5.2 // indirect
2627
github.com/hpcloud/tail v1.0.0 // indirect
2728
github.com/inconshreveable/mousetrap v1.0.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
3232
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
3333
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
3434
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
35+
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
36+
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
3537
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
3638
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
3739
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=

0 commit comments

Comments
 (0)