@@ -28,6 +28,7 @@ import (
28
28
"runtime"
29
29
"strconv"
30
30
"strings"
31
+ "sync"
31
32
"time"
32
33
33
34
"github.com/ethereum/go-ethereum/common"
@@ -93,6 +94,49 @@ type Service struct {
93
94
94
95
pongCh chan struct {} // Pong notifications are fed into this channel
95
96
histCh chan []uint64 // History request block numbers are fed into this channel
97
+
98
+ }
99
+
100
+ // connWrapper is a wrapper to prevent concurrent-write or concurrent-read on the
101
+ // websocket.
102
+ // From Gorilla websocket docs:
103
+ // Connections support one concurrent reader and one concurrent writer.
104
+ // Applications are responsible for ensuring that no more than one goroutine calls the write methods
105
+ // - NextWriter, SetWriteDeadline, WriteMessage, WriteJSON, EnableWriteCompression, SetCompressionLevel
106
+ // concurrently and that no more than one goroutine calls the read methods
107
+ // - NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler
108
+ // concurrently.
109
+ // The Close and WriteControl methods can be called concurrently with all other methods.
110
+ //
111
+ // The connWrapper uses a single mutex for both reading and writing.
112
+ type connWrapper struct {
113
+ conn * websocket.Conn
114
+ mu sync.Mutex
115
+ }
116
+
117
+ func newConnectionWrapper (conn * websocket.Conn ) * connWrapper {
118
+ return & connWrapper {conn : conn }
119
+ }
120
+
121
+ // WriteJSON wraps corresponding method on the websocket but is safe for concurrent calling
122
+ func (w * connWrapper ) WriteJSON (v interface {}) error {
123
+ w .mu .Lock ()
124
+ defer w .mu .Unlock ()
125
+ return w .conn .WriteJSON (v )
126
+ }
127
+
128
+ // ReadJSON wraps corresponding method on the websocket but is safe for concurrent calling
129
+ func (w * connWrapper ) ReadJSON (v interface {}) error {
130
+ w .mu .Lock ()
131
+ defer w .mu .Unlock ()
132
+ return w .conn .ReadJSON (v )
133
+ }
134
+
135
+ // Close wraps corresponding method on the websocket but is safe for concurrent calling
136
+ func (w * connWrapper ) Close () error {
137
+ // The Close and WriteControl methods can be called concurrently with all other methods,
138
+ // so the mutex is not used here
139
+ return w .conn .Close ()
96
140
}
97
141
98
142
// New returns a monitoring service ready for stats reporting.
@@ -204,17 +248,19 @@ func (s *Service) loop() {
204
248
case <- errTimer .C :
205
249
// Establish a websocket connection to the server on any supported URL
206
250
var (
207
- conn * websocket. Conn
251
+ conn * connWrapper
208
252
err error
209
253
)
210
254
dialer := websocket.Dialer {HandshakeTimeout : 5 * time .Second }
211
255
header := make (http.Header )
212
256
header .Set ("origin" , "http://localhost" )
213
257
for _ , url := range urls {
214
- conn , _ , err = dialer .Dial (url , header )
215
- if err == nil {
258
+ c , _ , e := dialer .Dial (url , header )
259
+ if e == nil {
260
+ conn = newConnectionWrapper (c )
216
261
break
217
262
}
263
+ err = e
218
264
}
219
265
if err != nil {
220
266
log .Warn ("Stats server unreachable" , "err" , err )
@@ -282,7 +328,7 @@ func (s *Service) loop() {
282
328
// from the network socket. If any of them match an active request, it forwards
283
329
// it, if they themselves are requests it initiates a reply, and lastly it drops
284
330
// unknown packets.
285
- func (s * Service ) readLoop (conn * websocket. Conn ) {
331
+ func (s * Service ) readLoop (conn * connWrapper ) {
286
332
// If the read loop exists, close the connection
287
333
defer conn .Close ()
288
334
@@ -391,7 +437,7 @@ type authMsg struct {
391
437
}
392
438
393
439
// login tries to authorize the client at the remote server.
394
- func (s * Service ) login (conn * websocket. Conn ) error {
440
+ func (s * Service ) login (conn * connWrapper ) error {
395
441
// Construct and send the login authentication
396
442
infos := s .server .NodeInfo ()
397
443
@@ -436,7 +482,7 @@ func (s *Service) login(conn *websocket.Conn) error {
436
482
// report collects all possible data to report and send it to the stats server.
437
483
// This should only be used on reconnects or rarely to avoid overloading the
438
484
// server. Use the individual methods for reporting subscribed events.
439
- func (s * Service ) report (conn * websocket. Conn ) error {
485
+ func (s * Service ) report (conn * connWrapper ) error {
440
486
if err := s .reportLatency (conn ); err != nil {
441
487
return err
442
488
}
@@ -454,7 +500,7 @@ func (s *Service) report(conn *websocket.Conn) error {
454
500
455
501
// reportLatency sends a ping request to the server, measures the RTT time and
456
502
// finally sends a latency update.
457
- func (s * Service ) reportLatency (conn * websocket. Conn ) error {
503
+ func (s * Service ) reportLatency (conn * connWrapper ) error {
458
504
// Send the current time to the ethstats server
459
505
start := time .Now ()
460
506
@@ -523,7 +569,7 @@ func (s uncleStats) MarshalJSON() ([]byte, error) {
523
569
}
524
570
525
571
// reportBlock retrieves the current chain head and reports it to the stats server.
526
- func (s * Service ) reportBlock (conn * websocket. Conn , block * types.Block ) error {
572
+ func (s * Service ) reportBlock (conn * connWrapper , block * types.Block ) error {
527
573
// Gather the block details from the header or block chain
528
574
details := s .assembleBlockStats (block )
529
575
@@ -598,7 +644,7 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
598
644
599
645
// reportHistory retrieves the most recent batch of blocks and reports it to the
600
646
// stats server.
601
- func (s * Service ) reportHistory (conn * websocket. Conn , list []uint64 ) error {
647
+ func (s * Service ) reportHistory (conn * connWrapper , list []uint64 ) error {
602
648
// Figure out the indexes that need reporting
603
649
indexes := make ([]uint64 , 0 , historyUpdateRange )
604
650
if len (list ) > 0 {
@@ -660,7 +706,7 @@ type pendStats struct {
660
706
661
707
// reportPending retrieves the current number of pending transactions and reports
662
708
// it to the stats server.
663
- func (s * Service ) reportPending (conn * websocket. Conn ) error {
709
+ func (s * Service ) reportPending (conn * connWrapper ) error {
664
710
// Retrieve the pending count from the local blockchain
665
711
pending , _ := s .backend .Stats ()
666
712
// Assemble the transaction stats and send it to the server
@@ -691,7 +737,7 @@ type nodeStats struct {
691
737
692
738
// reportStats retrieves various stats about the node at the networking and
693
739
// mining layer and reports it to the stats server.
694
- func (s * Service ) reportStats (conn * websocket. Conn ) error {
740
+ func (s * Service ) reportStats (conn * connWrapper ) error {
695
741
// Gather the syncing and mining infos from the local miner instance
696
742
var (
697
743
mining bool
0 commit comments