@@ -18,6 +18,7 @@ import (
18
18
"github.com/onflow/flow-go/module/irrecoverable"
19
19
"github.com/onflow/flow-go/module/mempool"
20
20
"github.com/onflow/flow-go/module/metrics"
21
+ "github.com/onflow/flow-go/module/util"
21
22
)
22
23
23
24
// defaultVoteAggregatorWorkers number of workers to dispatch events for vote aggregators
@@ -99,24 +100,36 @@ func NewVoteAggregator(
99
100
aggregator .queuedMessagesProcessingLoop (ctx )
100
101
})
101
102
}
102
- componentBuilder .AddWorker (func (_ irrecoverable.SignalerContext , ready component.ReadyFunc ) {
103
+ componentBuilder .AddWorker (func (parentCtx irrecoverable.SignalerContext , ready component.ReadyFunc ) {
103
104
// create new context which is not connected to parent
104
105
// we need to ensure that our internal workers stop before asking
105
106
// vote collectors to stop. We want to avoid delivering events to already stopped vote collectors
106
107
ctx , cancel := context .WithCancel (context .Background ())
107
- signalerCtx , _ := irrecoverable .WithSignaler (ctx )
108
+ signalerCtx , errCh := irrecoverable .WithSignaler (ctx )
109
+
108
110
// start vote collectors
109
111
collectors .Start (signalerCtx )
110
- <- collectors .Ready ()
111
112
112
- ready ()
113
+ // Handle the component lifecycle in a separate goroutine so we can capture any errors
114
+ // thrown during initialization in the main goroutine.
115
+ go func () {
116
+ if err := util .WaitClosed (parentCtx , collectors .Ready ()); err == nil {
117
+ // only signal ready when collectors are ready, but always handle shutdown
118
+ ready ()
119
+ }
113
120
114
- // wait for internal workers to stop
115
- wg .Wait ()
116
- // signal vote collectors to stop
117
- cancel ()
118
- // wait for it to stop
119
- <- collectors .Done ()
121
+ // wait for internal workers to stop, then signal vote collectors to stop
122
+ wg .Wait ()
123
+ cancel ()
124
+ }()
125
+
126
+ // since we are breaking the connection between parentCtx and signalerCtx, we need to
127
+ // explicitly rethrow any errors from signalerCtx to parentCtx, otherwise they are dropped.
128
+ // Handle errors in the main worker goroutine to guarantee that they are rethrown to the parent
129
+ // before the component is marked done.
130
+ if err := util .WaitError (errCh , collectors .Done ()); err != nil {
131
+ parentCtx .Throw (err )
132
+ }
120
133
})
121
134
componentBuilder .AddWorker (func (ctx irrecoverable.SignalerContext , ready component.ReadyFunc ) {
122
135
ready ()
0 commit comments