@@ -82,17 +82,48 @@ type Service struct {
82
82
pongCh chan struct {} // Pong notifications are fed into this channel
83
83
histCh chan []uint64 // History request block numbers are fed into this channel
84
84
85
- // Gorilla websocket docs:
86
- // Connections support one concurrent reader and one concurrent writer.
87
- // Applications are responsible for ensuring that no more than one goroutine calls the write methods
88
- // - NextWriter, SetWriteDeadline, WriteMessage, WriteJSON, EnableWriteCompression, SetCompressionLevel
89
- // concurrently and that no more than one goroutine calls the read methods
90
- // - NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler
91
- // concurrently.
92
- // The Close and WriteControl methods can be called concurrently with all other methods.
93
- //
94
- // In our case, we use a single mutex for both reading and writing.
95
- connMu sync.Mutex // Mutex to prevent concurrent write/read on the websocket connection
85
+ }
86
+
87
+ // connWrapper is a wrapper to prevent concurrent-write or concurrent-read on the
88
+ // websocket.
89
+ // From Gorilla websocket docs:
90
+ // Connections support one concurrent reader and one concurrent writer.
91
+ // Applications are responsible for ensuring that no more than one goroutine calls the write methods
92
+ // - NextWriter, SetWriteDeadline, WriteMessage, WriteJSON, EnableWriteCompression, SetCompressionLevel
93
+ // concurrently and that no more than one goroutine calls the read methods
94
+ // - NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler
95
+ // concurrently.
96
+ // The Close and WriteControl methods can be called concurrently with all other methods.
97
+ //
98
+ // The connWrapper uses a single mutex for both reading and writing.
99
+ type connWrapper struct {
100
+ conn * websocket.Conn
101
+ mu sync.Mutex
102
+ }
103
+
104
+ func newConnectionWrapper (conn * websocket.Conn ) * connWrapper {
105
+ return & connWrapper {conn : conn }
106
+ }
107
+
108
+ // WriteJSON wraps corresponding method on the websocket but is safe for concurrent calling
109
+ func (w * connWrapper ) WriteJSON (v interface {}) error {
110
+ w .mu .Lock ()
111
+ defer w .mu .Unlock ()
112
+ return w .conn .WriteJSON (v )
113
+ }
114
+
115
+ // ReadJSON wraps corresponding method on the websocket but is safe for concurrent calling
116
+ func (w * connWrapper ) ReadJSON (v interface {}) error {
117
+ w .mu .Lock ()
118
+ defer w .mu .Unlock ()
119
+ return w .conn .ReadJSON (v )
120
+ }
121
+
122
+ // Close wraps corresponding method on the websocket but is safe for concurrent calling
123
+ func (w * connWrapper ) Close () error {
124
+ // The Close and WriteControl methods can be called concurrently with all other methods,
125
+ // so the mutex is not used here
126
+ return w .conn .Close ()
96
127
}
97
128
98
129
// New returns a monitoring service ready for stats reporting.
@@ -227,17 +258,19 @@ func (s *Service) loop() {
227
258
case <- errTimer .C :
228
259
// Establish a websocket connection to the server on any supported URL
229
260
var (
230
- conn * websocket. Conn
261
+ conn * connWrapper
231
262
err error
232
263
)
233
264
dialer := websocket.Dialer {HandshakeTimeout : 5 * time .Second }
234
265
header := make (http.Header )
235
266
header .Set ("origin" , "http://localhost" )
236
267
for _ , url := range urls {
237
- conn , _ , err = dialer .Dial (url , header )
238
- if err == nil {
268
+ c , _ , e := dialer .Dial (url , header )
269
+ if e == nil {
270
+ conn = newConnectionWrapper (c )
239
271
break
240
272
}
273
+ err = e
241
274
}
242
275
if err != nil {
243
276
log .Warn ("Stats server unreachable" , "err" , err )
@@ -305,31 +338,26 @@ func (s *Service) loop() {
305
338
// from the network socket. If any of them match an active request, it forwards
306
339
// it, if they themselves are requests it initiates a reply, and lastly it drops
307
340
// unknown packets.
308
- func (s * Service ) readLoop (conn * websocket. Conn ) {
341
+ func (s * Service ) readLoop (conn * connWrapper ) {
309
342
// If the read loop exists, close the connection
310
343
defer conn .Close ()
311
344
312
345
for {
313
346
// Retrieve the next generic network packet and bail out on error
314
347
var blob json.RawMessage
315
- s .connMu .Lock ()
316
348
if err := conn .ReadJSON (& blob ); err != nil {
317
349
log .Warn ("Failed to retrieve stats server message" , "err" , err )
318
- s .connMu .Unlock ()
319
350
return
320
351
}
321
352
// If the network packet is a system ping, respond to it directly
322
353
var ping string
323
354
if err := json .Unmarshal (blob , & ping ); err == nil && strings .HasPrefix (ping , "primus::ping::" ) {
324
355
if err := conn .WriteJSON (strings .Replace (ping , "ping" , "pong" , - 1 )); err != nil {
325
356
log .Warn ("Failed to respond to system ping message" , "err" , err )
326
- s .connMu .Unlock ()
327
357
return
328
358
}
329
- s .connMu .Unlock ()
330
359
continue
331
360
}
332
- s .connMu .Unlock ()
333
361
// Not a system ping, try to decode an actual state message
334
362
var msg map [string ][]interface {}
335
363
if err := json .Unmarshal (blob , & msg ); err != nil {
@@ -419,7 +447,7 @@ type authMsg struct {
419
447
}
420
448
421
449
// login tries to authorize the client at the remote server.
422
- func (s * Service ) login (conn * websocket. Conn ) error {
450
+ func (s * Service ) login (conn * connWrapper ) error {
423
451
// Construct and send the login authentication
424
452
infos := s .server .NodeInfo ()
425
453
@@ -450,8 +478,6 @@ func (s *Service) login(conn *websocket.Conn) error {
450
478
login := map [string ][]interface {}{
451
479
"emit" : {"hello" , auth },
452
480
}
453
- s .connMu .Lock ()
454
- defer s .connMu .Unlock ()
455
481
if err := conn .WriteJSON (login ); err != nil {
456
482
return err
457
483
}
@@ -466,7 +492,7 @@ func (s *Service) login(conn *websocket.Conn) error {
466
492
// report collects all possible data to report and send it to the stats server.
467
493
// This should only be used on reconnects or rarely to avoid overloading the
468
494
// server. Use the individual methods for reporting subscribed events.
469
- func (s * Service ) report (conn * websocket. Conn ) error {
495
+ func (s * Service ) report (conn * connWrapper ) error {
470
496
if err := s .reportLatency (conn ); err != nil {
471
497
return err
472
498
}
@@ -484,7 +510,7 @@ func (s *Service) report(conn *websocket.Conn) error {
484
510
485
511
// reportLatency sends a ping request to the server, measures the RTT time and
486
512
// finally sends a latency update.
487
- func (s * Service ) reportLatency (conn * websocket. Conn ) error {
513
+ func (s * Service ) reportLatency (conn * connWrapper ) error {
488
514
// Send the current time to the ethstats server
489
515
start := time .Now ()
490
516
@@ -494,8 +520,6 @@ func (s *Service) reportLatency(conn *websocket.Conn) error {
494
520
"clientTime" : start .String (),
495
521
}},
496
522
}
497
- s .connMu .Lock ()
498
- defer s .connMu .Unlock ()
499
523
if err := conn .WriteJSON (ping ); err != nil {
500
524
return err
501
525
}
@@ -555,7 +579,7 @@ func (s uncleStats) MarshalJSON() ([]byte, error) {
555
579
}
556
580
557
581
// reportBlock retrieves the current chain head and reports it to the stats server.
558
- func (s * Service ) reportBlock (conn * websocket. Conn , block * types.Block ) error {
582
+ func (s * Service ) reportBlock (conn * connWrapper , block * types.Block ) error {
559
583
// Gather the block details from the header or block chain
560
584
details := s .assembleBlockStats (block )
561
585
@@ -569,8 +593,6 @@ func (s *Service) reportBlock(conn *websocket.Conn, block *types.Block) error {
569
593
report := map [string ][]interface {}{
570
594
"emit" : {"block" , stats },
571
595
}
572
- s .connMu .Lock ()
573
- defer s .connMu .Unlock ()
574
596
return conn .WriteJSON (report )
575
597
}
576
598
@@ -629,7 +651,7 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
629
651
630
652
// reportHistory retrieves the most recent batch of blocks and reports it to the
631
653
// stats server.
632
- func (s * Service ) reportHistory (conn * websocket. Conn , list []uint64 ) error {
654
+ func (s * Service ) reportHistory (conn * connWrapper , list []uint64 ) error {
633
655
// Figure out the indexes that need reporting
634
656
indexes := make ([]uint64 , 0 , historyUpdateRange )
635
657
if len (list ) > 0 {
@@ -685,8 +707,6 @@ func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error {
685
707
report := map [string ][]interface {}{
686
708
"emit" : {"history" , stats },
687
709
}
688
- s .connMu .Unlock ()
689
- defer s .connMu .Unlock ()
690
710
return conn .WriteJSON (report )
691
711
}
692
712
@@ -697,7 +717,7 @@ type pendStats struct {
697
717
698
718
// reportPending retrieves the current number of pending transactions and reports
699
719
// it to the stats server.
700
- func (s * Service ) reportPending (conn * websocket. Conn ) error {
720
+ func (s * Service ) reportPending (conn * connWrapper ) error {
701
721
// Retrieve the pending count from the local blockchain
702
722
var pending int
703
723
if s .eth != nil {
@@ -717,8 +737,6 @@ func (s *Service) reportPending(conn *websocket.Conn) error {
717
737
report := map [string ][]interface {}{
718
738
"emit" : {"pending" , stats },
719
739
}
720
- s .connMu .Lock ()
721
- defer s .connMu .Unlock ()
722
740
return conn .WriteJSON (report )
723
741
}
724
742
@@ -735,7 +753,7 @@ type nodeStats struct {
735
753
736
754
// reportPending retrieves various stats about the node at the networking and
737
755
// mining layer and reports it to the stats server.
738
- func (s * Service ) reportStats (conn * websocket. Conn ) error {
756
+ func (s * Service ) reportStats (conn * connWrapper ) error {
739
757
// Gather the syncing and mining infos from the local miner instance
740
758
var (
741
759
mining bool
@@ -774,7 +792,5 @@ func (s *Service) reportStats(conn *websocket.Conn) error {
774
792
report := map [string ][]interface {}{
775
793
"emit" : {"stats" , stats },
776
794
}
777
- s .connMu .Lock ()
778
- defer s .connMu .Unlock ()
779
795
return conn .WriteJSON (report )
780
796
}
0 commit comments