Skip to content

Commit 9433734

Browse files
committed
[FAB-4202] Fix race condition in orderer json ledger.
If the block file is read before a writing is finished, it would yield 'unexpected EOF' error. Therefore, a file lock is added to prevent concurrent write/read to the same block file. Change-Id: I22601dcf6066e02a00c57c405abd4e8dfa74dece Signed-off-by: Jay Guo <[email protected]>
1 parent fa63fb9 commit 9433734

File tree

2 files changed

+45
-5
lines changed

2 files changed

+45
-5
lines changed

orderer/ledger/json/impl.go

+19-5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222
"os"
2323
"path/filepath"
24+
"sync"
2425

2526
ledger "github.com/hyperledger/fabric/orderer/ledger"
2627
cb "github.com/hyperledger/fabric/protos/common"
@@ -32,6 +33,7 @@ import (
3233

3334
var logger = logging.MustGetLogger("orderer/jsonledger")
3435
var closedChan chan struct{}
36+
var fileLock sync.Mutex
3537

3638
func init() {
3739
closedChan = make(chan struct{})
@@ -58,7 +60,14 @@ type jsonLedger struct {
5860

5961
// readBlock returns the block or nil, and whether the block was found or not, (nil,true) generally indicates an irrecoverable problem
6062
func (jl *jsonLedger) readBlock(number uint64) (*cb.Block, bool) {
61-
file, err := os.Open(jl.blockFilename(number))
63+
name := jl.blockFilename(number)
64+
65+
// In case of ongoing write, reading the block file may result in `unexpected EOF` error.
66+
// Therefore, we use file mutex here to prevent this race condition.
67+
fileLock.Lock()
68+
defer fileLock.Unlock()
69+
70+
file, err := os.Open(name)
6271
if err == nil {
6372
defer file.Close()
6473
block := &cb.Block{}
@@ -113,10 +122,9 @@ func (jl *jsonLedger) Iterator(startPosition *ab.SeekPosition) (ledger.Iterator,
113122
return &ledger.NotFoundErrorIterator{}, 0
114123
}
115124
return &cursor{jl: jl, blockNumber: start.Specified.Number}, start.Specified.Number
125+
default:
126+
return &ledger.NotFoundErrorIterator{}, 0
116127
}
117-
118-
// This line should be unreachable, but the compiler requires it
119-
return &ledger.NotFoundErrorIterator{}, 0
120128
}
121129

122130
// Height returns the number of blocks on the ledger
@@ -144,11 +152,17 @@ func (jl *jsonLedger) Append(block *cb.Block) error {
144152

145153
// writeBlock commits a block to disk
146154
func (jl *jsonLedger) writeBlock(block *cb.Block) {
147-
file, err := os.Create(jl.blockFilename(block.Header.Number))
155+
name := jl.blockFilename(block.Header.Number)
156+
157+
fileLock.Lock()
158+
defer fileLock.Unlock()
159+
160+
file, err := os.Create(name)
148161
if err != nil {
149162
panic(err)
150163
}
151164
defer file.Close()
165+
152166
err = jl.marshaler.Marshal(file, block)
153167
logger.Debugf("Wrote block %d", block.Header.Number)
154168
if err != nil {

orderer/ledger/json/impl_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,32 @@ func TestRetrieval(t *testing.T) {
181181
}
182182
}
183183

184+
// Without file lock in the implementation, this test is flaky due to
185+
// a race condition of concurrent write and read of block file. With
186+
// the fix, running this test repeatedly should not yield failure.
187+
func TestRaceCondition(t *testing.T) {
188+
tev, fl := initialize(t)
189+
defer tev.tearDown()
190+
191+
it, _ := fl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_Specified{Specified: &ab.SeekSpecified{Number: 1}}})
192+
193+
var block *cb.Block
194+
var status cb.Status
195+
196+
complete := make(chan struct{})
197+
go func() {
198+
block, status = it.Next()
199+
close(complete)
200+
}()
201+
202+
fl.Append(ledger.CreateNextBlock(fl, []*cb.Envelope{{Payload: []byte("My Data")}}))
203+
<-complete
204+
205+
if status != cb.Status_SUCCESS {
206+
t.Fatalf("Expected to successfully read the block")
207+
}
208+
}
209+
184210
func TestBlockedRetrieval(t *testing.T) {
185211
tev, fl := initialize(t)
186212
defer tev.tearDown()

0 commit comments

Comments
 (0)