Skip to content

Commit 63486ad

Browse files
Use channel instead of sync cond for RetrieveLoop and blockStoreRetri… (#1152)
<!-- Please read and fill out this form before submitting your PR. Please make sure you have reviewed our contributors guide before submitting your first PR. --> ## Overview Resolves: #1132, resolves: #1153 <!-- Please provide an explanation of the PR, including the appropriate context, background, goal, and rationale. If there is an issue with this information, please provide a tl;dr and link the issue. --> ## Checklist <!-- Please complete the checklist to ensure that the PR is ready to be reviewed. IMPORTANT: PRs should be left in Draft until the below checklist is completed. --> - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords
1 parent 344d382 commit 63486ad

3 files changed

Lines changed: 77 additions & 68 deletions

File tree

block/manager.go

Lines changed: 33 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,11 @@ type Manager struct {
7979

8080
blockCache *BlockCache
8181

82-
// blockStoreMtx is used by blockStoreCond
83-
blockStoreMtx *sync.Mutex
84-
// blockStoreCond is used to notify sync goroutine (SyncLoop) that it needs to retrieve blocks from blockStore
85-
blockStoreCond *sync.Cond
82+
// blockStoreCh is used to notify sync goroutine (SyncLoop) that it needs to retrieve blocks from blockStore
83+
blockStoreCh chan struct{}
8684

87-
// retrieveMtx is used by retrieveCond
88-
retrieveMtx *sync.Mutex
8985
// retrieveCond is used to notify sync goroutine (SyncLoop) that it needs to retrieve data
90-
retrieveCond *sync.Cond
86+
retrieveCh chan struct{}
9187

9288
logger log.Logger
9389

@@ -96,9 +92,7 @@ type Manager struct {
9692
txsAvailable <-chan struct{}
9793
doneBuildingBlock chan struct{}
9894

99-
// Maintains blocks that need to be published to DA layer
100-
pendingBlocks []*types.Block
101-
pendingBlocksMtx *sync.RWMutex
95+
pendingBlocks *PendingBlocks
10296
}
10397

10498
// getInitialState tries to load lastState from Store, and if it's not available it reads GenesisDoc.
@@ -181,21 +175,17 @@ func NewManager(
181175
HeaderCh: make(chan *types.SignedHeader, channelLength),
182176
BlockCh: make(chan *types.Block, channelLength),
183177
blockInCh: make(chan newBlockEvent, blockInChLength),
184-
blockStoreMtx: new(sync.Mutex),
178+
blockStoreCh: make(chan struct{}, 1),
185179
blockStore: blockStore,
186-
retrieveMtx: new(sync.Mutex),
187180
lastStateMtx: new(sync.RWMutex),
188181
blockCache: NewBlockCache(),
182+
retrieveCh: make(chan struct{}, 1),
189183
logger: logger,
190184
txsAvailable: txsAvailableCh,
191185
doneBuildingBlock: doneBuildingCh,
192186
buildingBlock: false,
193-
pendingBlocks: make([]*types.Block, 0),
194-
pendingBlocksMtx: new(sync.RWMutex),
187+
pendingBlocks: NewPendingBlocks(),
195188
}
196-
agg.retrieveCond = sync.NewCond(agg.retrieveMtx)
197-
agg.blockStoreCond = sync.NewCond(agg.blockStoreMtx)
198-
199189
return agg, nil
200190
}
201191

@@ -291,6 +281,9 @@ func (m *Manager) BlockSubmissionLoop(ctx context.Context) {
291281
return
292282
case <-timer.C:
293283
}
284+
if m.pendingBlocks.isEmpty() {
285+
continue
286+
}
294287
err := m.submitBlocksToDA(ctx)
295288
if err != nil {
296289
m.logger.Error("error while submitting block to DA", "error", err)
@@ -308,9 +301,9 @@ func (m *Manager) SyncLoop(ctx context.Context, cancel context.CancelFunc) {
308301
for {
309302
select {
310303
case <-daTicker.C:
311-
m.retrieveCond.Signal()
304+
m.sendNonBlockingSignalToRetrieveCh()
312305
case <-blockTicker.C:
313-
m.blockStoreCond.Signal()
306+
m.sendNonBlockingSignalToBlockStoreCh()
314307
case blockEvent := <-m.blockInCh:
315308
block := blockEvent.block
316309
daHeight := blockEvent.daHeight
@@ -327,8 +320,8 @@ func (m *Manager) SyncLoop(ctx context.Context, cancel context.CancelFunc) {
327320
}
328321
m.blockCache.setBlock(blockHeight, block)
329322

330-
m.blockStoreCond.Signal()
331-
m.retrieveCond.Signal()
323+
m.sendNonBlockingSignalToBlockStoreCh()
324+
m.sendNonBlockingSignalToRetrieveCh()
332325

333326
err := m.trySyncNextBlock(ctx, daHeight)
334327
if err != nil {
@@ -342,6 +335,20 @@ func (m *Manager) SyncLoop(ctx context.Context, cancel context.CancelFunc) {
342335
}
343336
}
344337

338+
func (m *Manager) sendNonBlockingSignalToBlockStoreCh() {
339+
select {
340+
case m.blockStoreCh <- struct{}{}:
341+
default:
342+
}
343+
}
344+
345+
func (m *Manager) sendNonBlockingSignalToRetrieveCh() {
346+
select {
347+
case m.retrieveCh <- struct{}{}:
348+
default:
349+
}
350+
}
351+
345352
// trySyncNextBlock tries to progress one step (one block) in sync process.
346353
//
347354
// To be able to apply block and height h, we need to have its Commit. It is contained in block at height h+1.
@@ -405,30 +412,12 @@ func (m *Manager) trySyncNextBlock(ctx context.Context, daHeight uint64) error {
405412

406413
// BlockStoreRetrieveLoop is responsible for retrieving blocks from the Block Store.
407414
func (m *Manager) BlockStoreRetrieveLoop(ctx context.Context) {
408-
// waitCh is used to signal the block store retrieve loop, that it should check block store for new blocks
409-
// blockStoreCond can be signalled in completely async manner, and goroutine below
410-
// works as some kind of "buffer" for those signals
411-
waitCh := make(chan interface{})
412415
lastBlockStoreHeight := uint64(0)
413-
go func() {
414-
for {
415-
// This infinite loop is expected to be stopped once the context is
416-
// cancelled or throws an error and cleaned up by the GC. This is OK
417-
// because it waits using a conditional which is only signaled periodically.
418-
m.blockStoreMtx.Lock()
419-
m.blockStoreCond.Wait()
420-
waitCh <- nil
421-
m.blockStoreMtx.Unlock()
422-
if ctx.Err() != nil {
423-
return
424-
}
425-
}
426-
}()
427416
for {
428417
select {
429418
case <-ctx.Done():
430419
return
431-
case <-waitCh:
420+
case <-m.blockStoreCh:
432421
}
433422
blockStoreHeight := m.blockStore.Height()
434423
if blockStoreHeight > lastBlockStoreHeight {
@@ -467,30 +456,11 @@ func (m *Manager) getBlocksFromBlockStore(ctx context.Context, startHeight, endH
467456

468457
// RetrieveLoop is responsible for interacting with DA layer.
469458
func (m *Manager) RetrieveLoop(ctx context.Context) {
470-
// waitCh is used to signal the retrieve loop, that it should process next blocks
471-
// retrieveCond can be signalled in completely async manner, and goroutine below
472-
// works as some kind of "buffer" for those signals
473-
waitCh := make(chan interface{})
474-
go func() {
475-
for {
476-
// This infinite loop is expected to be stopped once the context is
477-
// cancelled or throws an error and cleaned up by the GC. This is OK
478-
// because it waits using a conditional which is only signaled periodically.
479-
m.retrieveMtx.Lock()
480-
m.retrieveCond.Wait()
481-
waitCh <- nil
482-
m.retrieveMtx.Unlock()
483-
if ctx.Err() != nil {
484-
return
485-
}
486-
}
487-
}()
488-
489459
for {
490460
select {
491461
case <-ctx.Done():
492462
return
493-
case <-waitCh:
463+
case <-m.retrieveCh:
494464
}
495465
daHeight := atomic.LoadUint64(&m.daHeight)
496466
err := m.processNextDABlock(ctx)
@@ -677,9 +647,7 @@ func (m *Manager) publishBlock(ctx context.Context) error {
677647
}
678648

679649
// Submit block to be published to the DA layer
680-
m.pendingBlocksMtx.Lock()
681-
m.pendingBlocks = append(m.pendingBlocks, block)
682-
m.pendingBlocksMtx.Unlock()
650+
m.pendingBlocks.addPendingBlock(block)
683651

684652
// Commit the new state and block which writes to disk on the proxy app
685653
_, _, err = m.executor.Commit(ctx, newState, block, responses)
@@ -719,12 +687,10 @@ func (m *Manager) publishBlock(ctx context.Context) error {
719687
}
720688

721689
func (m *Manager) submitBlocksToDA(ctx context.Context) error {
722-
m.pendingBlocksMtx.Lock()
723-
defer m.pendingBlocksMtx.Unlock()
724690
submitted := false
725691
backoff := initialBackoff
726692
for attempt := 1; ctx.Err() == nil && !submitted && attempt <= maxSubmitAttempts; attempt++ {
727-
res := m.dalc.SubmitBlocks(ctx, m.pendingBlocks)
693+
res := m.dalc.SubmitBlocks(ctx, m.pendingBlocks.getPendingBlocks())
728694
if res.Code == da.StatusSuccess {
729695
m.logger.Info("successfully submitted Rollkit block to DA layer", "daHeight", res.DAHeight)
730696
submitted = true
@@ -738,7 +704,7 @@ func (m *Manager) submitBlocksToDA(ctx context.Context) error {
738704
if !submitted {
739705
return fmt.Errorf("failed to submit block to DA layer after %d attempts", maxSubmitAttempts)
740706
}
741-
m.pendingBlocks = make([]*types.Block, 0)
707+
m.pendingBlocks.resetPendingBlocks()
742708
return nil
743709
}
744710

block/pending_blocks.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package block
2+
3+
import (
4+
"sync"
5+
6+
"github.com/rollkit/rollkit/types"
7+
)
8+
9+
// Maintains blocks that need to be published to DA layer
10+
type PendingBlocks struct {
11+
pendingBlocks []*types.Block
12+
mtx *sync.RWMutex
13+
}
14+
15+
func NewPendingBlocks() *PendingBlocks {
16+
return &PendingBlocks{
17+
pendingBlocks: make([]*types.Block, 0),
18+
mtx: new(sync.RWMutex),
19+
}
20+
}
21+
22+
func (pb *PendingBlocks) getPendingBlocks() []*types.Block {
23+
pb.mtx.RLock()
24+
defer pb.mtx.RUnlock()
25+
return pb.pendingBlocks
26+
}
27+
28+
func (pb *PendingBlocks) isEmpty() bool {
29+
pendingBlocks := pb.getPendingBlocks()
30+
return len(pendingBlocks) == 0
31+
}
32+
33+
func (pb *PendingBlocks) addPendingBlock(block *types.Block) {
34+
pb.mtx.Lock()
35+
defer pb.mtx.Unlock()
36+
pb.pendingBlocks = append(pb.pendingBlocks, block)
37+
}
38+
39+
func (pb *PendingBlocks) resetPendingBlocks() {
40+
pb.mtx.Lock()
41+
defer pb.mtx.Unlock()
42+
pb.pendingBlocks = make([]*types.Block, 0)
43+
}

node/full_client_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -970,7 +970,7 @@ func TestMempool2Nodes(t *testing.T) {
970970
require.NoError(node2.Stop())
971971
}()
972972

973-
time.Sleep(3 * time.Second)
973+
time.Sleep(4 * time.Second)
974974
timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 3*time.Second)
975975
defer timeoutCancel()
976976

0 commit comments

Comments
 (0)