66 _ "embed"
77 "errors"
88 "fmt"
9- "os"
109 "os/signal"
1110 "syscall"
1211 "time"
@@ -173,7 +172,10 @@ var SensorCmd = &cobra.Command{
173172 return nil
174173 },
175174 RunE : func (cmd * cobra.Command , args []string ) error {
176- db , err := newDatabase (cmd .Context ())
175+ ctx , stop := signal .NotifyContext (cmd .Context (), syscall .SIGINT , syscall .SIGTERM )
176+ defer stop ()
177+
178+ db , err := newDatabase (ctx )
177179 if err != nil {
178180 return err
179181 }
@@ -214,7 +216,7 @@ var SensorCmd = &cobra.Command{
214216 })
215217
216218 opts := p2p.EthProtocolOptions {
217- Context : cmd . Context () ,
219+ Context : ctx ,
218220 Database : db ,
219221 GenesisHash : common .HexToHash (inputSensorParams .GenesisHash ),
220222 RPC : inputSensorParams .RPC ,
@@ -259,64 +261,36 @@ var SensorCmd = &cobra.Command{
259261 if err = server .Start (); err != nil {
260262 return err
261263 }
262- defer server . Stop ( )
264+ defer stopServer ( & server )
263265
264266 events := make (chan * ethp2p.PeerEvent )
265267 sub := server .SubscribeEvents (events )
266268 defer sub .Unsubscribe ()
267269
268- ticker := time .NewTicker (2 * time .Second ) // Ticker for recurring tasks every 2 seconds.
269- ticker1h := time .NewTicker (time .Hour ) // Ticker for running DNS discovery every hour.
270+ ticker := time .NewTicker (2 * time .Second )
270271 defer ticker .Stop ()
271- defer ticker1h .Stop ()
272-
273- dnsLock := make (chan struct {}, 1 )
274- signals := make (chan os.Signal , 1 )
275- signal .Notify (signals , syscall .SIGINT , syscall .SIGTERM )
276-
277- // Create a cancellable context for graceful shutdown of background goroutines.
278- ctx , cancel := context .WithCancel (cmd .Context ())
279- defer cancel ()
280272
281273 if inputSensorParams .ShouldRunPprof {
282- go handlePprof (ctx )
274+ go handlePprof ()
283275 }
284276
285277 if inputSensorParams .ShouldRunPrometheus {
286- go handlePrometheus (ctx )
278+ go handlePrometheus ()
287279 }
288280
289- go handleAPI (ctx , & server , conns )
290-
291- // Start the RPC server for receiving transactions
292- go handleRPC (ctx , conns , inputSensorParams .NetworkID )
293-
294- // Run DNS discovery immediately at startup.
295- go handleDNSDiscovery (ctx , & server , dnsLock )
281+ go handleAPI (& server , conns )
282+ go handleRPC (conns , inputSensorParams .NetworkID )
283+ go handleDNSDiscovery (& server )
296284
297285 for {
298286 select {
299287 case <- ticker .C :
300288 peersGauge .Set (float64 (server .PeerCount ()))
301- db .WritePeers (cmd .Context (), server .Peers (), time .Now ())
302-
289+ db .WritePeers (ctx , server .Peers (), time .Now ())
303290 metrics .Update (conns .HeadBlock ().Block , conns .OldestBlock ())
304-
305- urls := []string {}
306- for _ , peer := range server .Peers () {
307- urls = append (urls , peer .Node ().URLv4 ())
308- }
309-
310- if err := p2p .WritePeers (inputSensorParams .NodesFile , urls ); err != nil {
311- log .Error ().Err (err ).Msg ("Failed to write nodes to file" )
312- }
313- case <- ticker1h .C :
314- go handleDNSDiscovery (ctx , & server , dnsLock )
315- case <- signals :
316- // This gracefully stops the sensor so that the peers can be written to
317- // the nodes file.
291+ writePeers (server .Peers ())
292+ case <- ctx .Done ():
318293 log .Info ().Msg ("Stopping sensor..." )
319- cancel ()
320294 return nil
321295 case event := <- events :
322296 log .Debug ().Any ("event" , event ).Send ()
@@ -327,70 +301,78 @@ var SensorCmd = &cobra.Command{
327301 },
328302}
329303
330- // handlePprof starts a server for performance profiling using pprof on the
331- // specified port. This allows for real-time monitoring and analysis of the
332- // sensor's performance. The port number is configured through
333- // inputSensorParams.PprofPort. The server gracefully shuts down when the
334- // context is cancelled.
335- func handlePprof (ctx context.Context ) {
336- addr := fmt .Sprintf (":%d" , inputSensorParams .PprofPort )
337- server := & http.Server {Addr : addr }
304+ // writePeers writes the enode URLs of connected peers to the nodes file.
305+ func writePeers (peers []* ethp2p.Peer ) {
306+ urls := make ([]string , 0 , len (peers ))
307+ for _ , peer := range peers {
308+ urls = append (urls , peer .Node ().URLv4 ())
309+ }
310+
311+ if err := p2p .WritePeers (inputSensorParams .NodesFile , urls ); err != nil {
312+ log .Error ().Err (err ).Msg ("Failed to write nodes to file" )
313+ }
314+ }
315+
316+ // stopServer stops the p2p server with a timeout to avoid hanging on shutdown.
317+ // This is necessary because go-ethereum's discovery shutdown can deadlock.
318+ func stopServer (server * ethp2p.Server ) {
319+ done := make (chan struct {})
338320
339321 go func () {
340- <- ctx .Done ()
341- if err := server .Shutdown (context .Background ()); err != nil {
342- log .Error ().Err (err ).Msg ("Failed to shutdown pprof server" )
343- }
322+ server .Stop ()
323+ close (done )
344324 }()
345325
346- if err := server .ListenAndServe (); err != nil && err != http .ErrServerClosed {
326+ select {
327+ case <- done :
328+ case <- time .After (5 * time .Second ):
329+ }
330+ }
331+
332+ // handlePprof starts a server for performance profiling using pprof on the
333+ // specified port. This allows for real-time monitoring and analysis of the
334+ // sensor's performance. The port number is configured through
335+ // inputSensorParams.PprofPort. An error is logged if the server fails to start.
336+ func handlePprof () {
337+ addr := fmt .Sprintf (":%d" , inputSensorParams .PprofPort )
338+ if err := http .ListenAndServe (addr , nil ); err != nil {
347339 log .Error ().Err (err ).Msg ("Failed to start pprof" )
348340 }
349341}
350342
351343// handlePrometheus starts a server to expose Prometheus metrics at the /metrics
352344// endpoint. This enables Prometheus to scrape and collect metrics data for
353345// monitoring purposes. The port number is configured through
354- // inputSensorParams.PrometheusPort. The server gracefully shuts down when the
355- // context is cancelled.
356- func handlePrometheus (ctx context.Context ) {
357- mux := http .NewServeMux ()
358- mux .Handle ("/metrics" , promhttp .Handler ())
359-
346+ // inputSensorParams.PrometheusPort. An error is logged if the server fails to
347+ // start.
348+ func handlePrometheus () {
349+ http .Handle ("/metrics" , promhttp .Handler ())
360350 addr := fmt .Sprintf (":%d" , inputSensorParams .PrometheusPort )
361- server := & http.Server {Addr : addr , Handler : mux }
362-
363- go func () {
364- <- ctx .Done ()
365- if err := server .Shutdown (context .Background ()); err != nil {
366- log .Error ().Err (err ).Msg ("Failed to shutdown Prometheus server" )
367- }
368- }()
369-
370- if err := server .ListenAndServe (); err != nil && err != http .ErrServerClosed {
351+ if err := http .ListenAndServe (addr , nil ); err != nil {
371352 log .Error ().Err (err ).Msg ("Failed to start Prometheus handler" )
372353 }
373354}
374355
375356// handleDNSDiscovery performs DNS-based peer discovery and adds new peers to
376357// the p2p server. It uses an iterator to discover peers incrementally rather
377- // than loading all nodes at once. The lock channel prevents concurrent runs.
378- // Discovery stops when the context is cancelled.
379- func handleDNSDiscovery (ctx context.Context , server * ethp2p.Server , lock chan struct {}) {
358+ // than loading all nodes at once. Runs immediately and then hourly.
359+ func handleDNSDiscovery (server * ethp2p.Server ) {
380360 if len (inputSensorParams .DiscoveryDNS ) == 0 {
381361 return
382362 }
383363
384- select {
385- case lock <- struct {}{}:
386- defer func () { <- lock }()
387- case <- ctx .Done ():
388- return
389- default :
390- log .Warn ().Msg ("DNS discovery already running, skipping" )
391- return
364+ discoverPeers (server )
365+
366+ ticker := time .NewTicker (time .Hour )
367+ defer ticker .Stop ()
368+
369+ for range ticker .C {
370+ discoverPeers (server )
392371 }
372+ }
393373
374+ // discoverPeers performs a single DNS discovery round.
375+ func discoverPeers (server * ethp2p.Server ) {
394376 log .Info ().
395377 Str ("discovery-dns" , inputSensorParams .DiscoveryDNS ).
396378 Msg ("Starting DNS discovery" )
@@ -403,27 +385,13 @@ func handleDNSDiscovery(ctx context.Context, server *ethp2p.Server, lock chan st
403385 }
404386 defer iter .Close ()
405387
406- // Add DNS-discovered peers using the iterator.
407388 count := 0
408389 for iter .Next () {
409- // Check for context cancellation to stop discovery promptly.
410- select {
411- case <- ctx .Done ():
412- log .Info ().
413- Int ("discovered_peers" , count ).
414- Msg ("DNS discovery interrupted" )
415- return
416- default :
417- }
418-
419390 node := iter .Node ()
420391 log .Debug ().
421392 Str ("enode" , node .URLv4 ()).
422393 Msg ("Discovered peer through DNS" )
423394
424- // Add the peer to the static node set. The server itself handles whether to
425- // connect to the peer if it's already connected. If a node is part of the
426- // static peer set, the server will handle reconnecting after disconnects.
427395 server .AddPeer (node )
428396 count ++
429397 }
0 commit comments