all: fix a bunch of inconsequential goroutine leaks (#20667)
The leaks were mostly in unit tests, and could all be resolved by adding suitably-sized channel buffers or by restructuring the test to not send on a channel after an error has occurred. There is an unavoidable goroutine leak in Console.Interactive: when we receive a signal, the line reader cannot be unblocked and will get stuck. This leak is now documented and I've tried to make it slightly less bad by adding a one-element buffer to the output channels of the line-reading loop. Should the reader eventually awake from its blocked state (i.e. when stdin is closed), at least it won't get stuck trying to send to the interpreter loop which has quit long ago. Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
@ -125,22 +125,25 @@ func TestSubscriptions(t *testing.T) {
|
||||
|
||||
// This test checks that unsubscribing works.
|
||||
func TestServerUnsubscribe(t *testing.T) {
|
||||
p1, p2 := net.Pipe()
|
||||
defer p2.Close()
|
||||
|
||||
// Start the server.
|
||||
server := newTestServer()
|
||||
service := ¬ificationTestService{unsubscribed: make(chan string)}
|
||||
service := ¬ificationTestService{unsubscribed: make(chan string, 1)}
|
||||
server.RegisterName("nftest2", service)
|
||||
p1, p2 := net.Pipe()
|
||||
go server.ServeCodec(NewCodec(p1), 0)
|
||||
|
||||
p2.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
|
||||
// Subscribe.
|
||||
p2.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
p2.Write([]byte(`{"jsonrpc":"2.0","id":1,"method":"nftest2_subscribe","params":["someSubscription",0,10]}`))
|
||||
|
||||
// Handle received messages.
|
||||
resps := make(chan subConfirmation)
|
||||
notifications := make(chan subscriptionResult)
|
||||
errors := make(chan error)
|
||||
var (
|
||||
resps = make(chan subConfirmation)
|
||||
notifications = make(chan subscriptionResult)
|
||||
errors = make(chan error, 1)
|
||||
)
|
||||
go waitForMessages(json.NewDecoder(p2), resps, notifications, errors)
|
||||
|
||||
// Receive the subscription ID.
|
||||
@ -173,34 +176,45 @@ type subConfirmation struct {
|
||||
subid ID
|
||||
}
|
||||
|
||||
// waitForMessages reads RPC messages from 'in' and dispatches them into the given channels.
|
||||
// It stops if there is an error.
|
||||
func waitForMessages(in *json.Decoder, successes chan subConfirmation, notifications chan subscriptionResult, errors chan error) {
|
||||
for {
|
||||
var msg jsonrpcMessage
|
||||
if err := in.Decode(&msg); err != nil {
|
||||
errors <- fmt.Errorf("decode error: %v", err)
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case msg.isNotification():
|
||||
var res subscriptionResult
|
||||
if err := json.Unmarshal(msg.Params, &res); err != nil {
|
||||
errors <- fmt.Errorf("invalid subscription result: %v", err)
|
||||
} else {
|
||||
notifications <- res
|
||||
}
|
||||
case msg.isResponse():
|
||||
var c subConfirmation
|
||||
if msg.Error != nil {
|
||||
errors <- msg.Error
|
||||
} else if err := json.Unmarshal(msg.Result, &c.subid); err != nil {
|
||||
errors <- fmt.Errorf("invalid response: %v", err)
|
||||
} else {
|
||||
json.Unmarshal(msg.ID, &c.reqid)
|
||||
successes <- c
|
||||
}
|
||||
default:
|
||||
errors <- fmt.Errorf("unrecognized message: %v", msg)
|
||||
resp, notification, err := readAndValidateMessage(in)
|
||||
if err != nil {
|
||||
errors <- err
|
||||
return
|
||||
} else if resp != nil {
|
||||
successes <- *resp
|
||||
} else {
|
||||
notifications <- *notification
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readAndValidateMessage(in *json.Decoder) (*subConfirmation, *subscriptionResult, error) {
|
||||
var msg jsonrpcMessage
|
||||
if err := in.Decode(&msg); err != nil {
|
||||
return nil, nil, fmt.Errorf("decode error: %v", err)
|
||||
}
|
||||
switch {
|
||||
case msg.isNotification():
|
||||
var res subscriptionResult
|
||||
if err := json.Unmarshal(msg.Params, &res); err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid subscription result: %v", err)
|
||||
}
|
||||
return nil, &res, nil
|
||||
case msg.isResponse():
|
||||
var c subConfirmation
|
||||
if msg.Error != nil {
|
||||
return nil, nil, msg.Error
|
||||
} else if err := json.Unmarshal(msg.Result, &c.subid); err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid response: %v", err)
|
||||
} else {
|
||||
json.Unmarshal(msg.ID, &c.reqid)
|
||||
return &c, nil, nil
|
||||
}
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unrecognized message: %v", msg)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user