Skip to content

Commit 9b17117

Browse files
authored
Improve node and services closing (#1554)
<!-- 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 #1552 Resolves #1553 <!-- 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 - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a safe closure mechanism for data storage to enhance system reliability. - **Bug Fixes** - Improved error reporting in block submission processes for clearer understanding of operations. - Refined the sequence of service shutdowns for more predictable application behavior. - **Tests** - Enhanced full node integration tests with simplified context handling and ensured clean-up post-execution. - **Refactor** - Adjusted the order of operations in service shutdown methods for better error handling and control flow. - Updated test cases to better manage the lifecycle of data storage, ensuring accurate state after restarts. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 8a337c4 commit 9b17117

8 files changed

Lines changed: 61 additions & 25 deletions

File tree

block/block_sync.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,17 +220,18 @@ func (bSyncService *BlockSyncService) Start() error {
220220
}
221221

222222
// Stop is a part of Service interface.
223+
//
224+
// `blockStore` is closed last because it's used by other services.
223225
func (bSyncService *BlockSyncService) Stop() error {
224-
err := bSyncService.blockStore.Stop(bSyncService.ctx)
225-
err = errors.Join(
226-
err,
226+
err := errors.Join(
227227
bSyncService.p2pServer.Stop(bSyncService.ctx),
228228
bSyncService.ex.Stop(bSyncService.ctx),
229229
bSyncService.sub.Stop(bSyncService.ctx),
230230
)
231231
if bSyncService.syncerStatus.isStarted() {
232232
err = errors.Join(err, bSyncService.syncer.Stop(bSyncService.ctx))
233233
}
234+
err = errors.Join(err, bSyncService.blockStore.Stop(bSyncService.ctx))
234235
return err
235236
}
236237

block/header_sync.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,17 +217,18 @@ func (hSyncService *HeaderSyncService) Start() error {
217217
}
218218

219219
// Stop is a part of Service interface.
220+
//
221+
// `headerStore` is closed last because it's used by other services.
220222
func (hSyncService *HeaderSyncService) Stop() error {
221-
err := hSyncService.headerStore.Stop(hSyncService.ctx)
222-
err = errors.Join(
223-
err,
223+
err := errors.Join(
224224
hSyncService.p2pServer.Stop(hSyncService.ctx),
225225
hSyncService.ex.Stop(hSyncService.ctx),
226226
hSyncService.sub.Stop(hSyncService.ctx),
227227
)
228228
if hSyncService.syncerStatus.isStarted() {
229229
err = errors.Join(err, hSyncService.syncer.Stop(hSyncService.ctx))
230230
}
231+
err = errors.Join(err, hSyncService.headerStore.Stop(hSyncService.ctx))
231232
return err
232233
}
233234

block/manager.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,6 @@ func (m *Manager) submitBlocksToDA(ctx context.Context) error {
833833
submittedAllBlocks := false
834834
backoff := initialBackoff
835835
blocksToSubmit := m.pendingBlocks.getPendingBlocks()
836-
numTotalBlocks := len(blocksToSubmit)
837836
numSubmittedBlocks := 0
838837
attempt := 0
839838
for ctx.Err() == nil && !submittedAllBlocks && attempt < maxSubmitAttempts {
@@ -861,10 +860,10 @@ func (m *Manager) submitBlocksToDA(ctx context.Context) error {
861860

862861
if !submittedAllBlocks {
863862
return fmt.Errorf(
864-
"failed to submit all blocks to DA layer, submitted %d of %d blocks after %d attempts",
863+
"failed to submit all blocks to DA layer, submitted %d blocks (%d left) after %d attempts",
865864
numSubmittedBlocks,
866-
numTotalBlocks,
867-
maxSubmitAttempts,
865+
len(blocksToSubmit),
866+
attempt,
868867
)
869868
}
870869
return nil

node/full.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -408,21 +408,25 @@ func (n *FullNode) GetGenesisChunks() ([]string, error) {
408408
}
409409

410410
// OnStop is a part of Service interface.
411+
//
412+
// p2pClient and sync services stop first, ceasing network activities. Then rest of services are halted.
413+
// Context is cancelled to signal goroutines managed by thread manager to stop.
414+
// Store is closed last because it's used by other services/goroutines.
411415
func (n *FullNode) OnStop() {
412416
n.Logger.Info("halting full node...")
413-
n.cancel()
414-
n.threadManager.Wait()
415417
n.Logger.Info("shutting down full node sub services...")
416-
err := n.p2pClient.Close()
417-
err = errors.Join(
418-
err,
418+
err := errors.Join(
419+
n.p2pClient.Close(),
419420
n.hSyncService.Stop(),
420421
n.bSyncService.Stop(),
421422
n.IndexerService.Stop(),
422423
)
423424
if n.prometheusSrv != nil {
424425
err = errors.Join(err, n.prometheusSrv.Shutdown(n.ctx))
425426
}
427+
n.cancel()
428+
n.threadManager.Wait()
429+
err = errors.Join(err, n.Store.Close())
426430
n.Logger.Error("errors while stopping node:", "errors", err)
427431
}
428432

node/full_node_integration_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,26 +94,30 @@ func TestTxGossipingAndAggregation(t *testing.T) {
9494
require := require.New(t)
9595

9696
clientNodes := 4
97-
aggCtx, aggCancel := context.WithCancel(context.Background())
98-
defer aggCancel()
99-
ctx, cancel := context.WithCancel(context.Background())
100-
defer cancel()
97+
aggCtx := context.Background()
98+
ctx := context.Background()
10199
nodes, apps := createNodes(aggCtx, ctx, clientNodes+1, getBMConfig(), t)
102100
startNodes(nodes, apps, t)
101+
defer func() {
102+
for _, n := range nodes {
103+
assert.NoError(n.Stop())
104+
}
105+
}()
103106

104107
// wait for nodes to start up and sync up till numBlocksToWaitFor
105108
numBlocksToWaitFor := 5
106109
for i := 1; i < len(nodes); i++ {
107110
require.NoError(waitForAtLeastNBlocks(nodes[i], numBlocksToWaitFor, Store))
108111
}
109112

110-
// Stop all the nodes before checking the calls to ABCI methods were done correctly
113+
// Cancel all the nodes before checking the calls to ABCI methods were done correctly
114+
// Can't stop here, because we need access to Store to test the state.
111115
for _, node := range nodes {
112-
assert.NoError(node.Stop())
116+
node.Cancel()
113117
}
114118

115-
// Now that the nodes have stopped, it should be safe to access the mock
116-
// calls outside of the mutex controlled methods.
119+
// Now that the nodes are cancelled, it should be safe to access the mock
120+
// calls outside the mutex controlled methods.
117121
//
118122
// The reason we do this is because in the beginning of the test, we
119123
// check that we have produced at least N blocks, which means we could

store/store.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ func New(ds ds.TxnDatastore) Store {
3939
}
4040
}
4141

42+
// Close safely closes underlying data storage, to ensure that data is actually saved.
43+
func (s *DefaultStore) Close() error {
44+
return s.db.Close()
45+
}
46+
4247
// SetHeight sets the height saved in the Store if it is higher than the existing height
4348
func (s *DefaultStore) SetHeight(ctx context.Context, height uint64) {
4449
for {

store/store_test.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,21 +129,40 @@ func TestRestart(t *testing.T) {
129129
t.Parallel()
130130

131131
assert := assert.New(t)
132+
require := require.New(t)
132133

133134
ctx, cancel := context.WithCancel(context.Background())
134135
defer cancel()
135-
kv, _ := NewDefaultInMemoryKVStore()
136+
137+
tmpDir, err := os.MkdirTemp("", t.Name())
138+
require.NoError(err)
139+
defer func() {
140+
_ = os.RemoveAll(tmpDir)
141+
}()
142+
143+
kv, err := NewDefaultKVStore(tmpDir, "test", "test")
144+
require.NoError(err)
145+
136146
s1 := New(kv)
137147
expectedHeight := uint64(10)
138-
err := s1.UpdateState(ctx, types.State{
148+
err = s1.UpdateState(ctx, types.State{
139149
LastBlockHeight: expectedHeight,
140150
})
141151
assert.NoError(err)
142152

153+
err = s1.Close()
154+
assert.NoError(err)
155+
156+
kv, err = NewDefaultKVStore(tmpDir, "test", "test")
157+
require.NoError(err)
158+
143159
s2 := New(kv)
144160
_, err = s2.GetState(ctx)
145161
assert.NoError(err)
146162

163+
err = s2.Close()
164+
assert.NoError(err)
165+
147166
assert.Equal(expectedHeight, s2.Height())
148167
}
149168

store/types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,7 @@ type Store interface {
4040
UpdateState(ctx context.Context, state types.State) error
4141
// GetState returns last state saved with UpdateState.
4242
GetState(ctx context.Context) (types.State, error)
43+
44+
// Close safely closes underlying data storage, to ensure that data is actually saved.
45+
Close() error
4346
}

0 commit comments

Comments
 (0)