Merge pull request #2137 from halseth/payintent-recv-eof
rpcserver: fix error handling in bidirectional sendPayment
This commit is contained in:
1 file changed
+72
-19
+72
-19
@@ -3680,25 +3680,28 @@ func (r *rpcServer) sendPayment(stream *paymentStream) error {
|
|||||||
htlcSema <- struct{}{}
|
htlcSema <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We keep track of the running goroutines and set up a quit signal we
|
||||||
|
// can use to request them to exit if the method returns because of an
|
||||||
|
// encountered error.
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
reqQuit := make(chan struct{})
|
||||||
|
defer close(reqQuit)
|
||||||
|
|
||||||
// Launch a new goroutine to handle reading new payment requests from
|
// Launch a new goroutine to handle reading new payment requests from
|
||||||
// the client. This way we can handle errors independently of blocking
|
// the client. This way we can handle errors independently of blocking
|
||||||
// and waiting for the next payment request to come through.
|
// and waiting for the next payment request to come through.
|
||||||
reqQuit := make(chan struct{})
|
|
||||||
defer func() {
|
|
||||||
close(reqQuit)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// TODO(joostjager): Callers expect result to come in in the same order
|
// TODO(joostjager): Callers expect result to come in in the same order
|
||||||
// as the request were sent, but this is far from guarantueed in the
|
// as the request were sent, but this is far from guarantueed in the
|
||||||
// code below.
|
// code below.
|
||||||
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-reqQuit:
|
case <-reqQuit:
|
||||||
return
|
return
|
||||||
case <-r.quit:
|
|
||||||
errChan <- nil
|
|
||||||
return
|
|
||||||
default:
|
default:
|
||||||
// Receive the next pending payment within the
|
// Receive the next pending payment within the
|
||||||
// stream sent by the client. If we read the
|
// stream sent by the client. If we read the
|
||||||
@@ -3706,13 +3709,15 @@ func (r *rpcServer) sendPayment(stream *paymentStream) error {
|
|||||||
// stream, and we can exit normally.
|
// stream, and we can exit normally.
|
||||||
nextPayment, err := stream.recv()
|
nextPayment, err := stream.recv()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
errChan <- nil
|
close(payChan)
|
||||||
return
|
return
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed receiving from "+
|
||||||
|
"stream: %v", err)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case errChan <- err:
|
case errChan <- err:
|
||||||
case <-reqQuit:
|
default:
|
||||||
return
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -3730,18 +3735,22 @@ func (r *rpcServer) sendPayment(stream *paymentStream) error {
|
|||||||
PaymentError: err.Error(),
|
PaymentError: err.Error(),
|
||||||
PaymentHash: payIntent.rHash[:],
|
PaymentHash: payIntent.rHash[:],
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
rpcsLog.Errorf("Failed "+
|
||||||
|
"sending on "+
|
||||||
|
"stream: %v", err)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case errChan <- err:
|
case errChan <- err:
|
||||||
case <-reqQuit:
|
default:
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the payment was well formed, then we'll
|
// If the payment was well formed, then we'll
|
||||||
// send to the dispatch goroutine, or exit,
|
// send to the dispatch goroutine, or exit,
|
||||||
// which ever comes first
|
// which ever comes first.
|
||||||
select {
|
select {
|
||||||
case payChan <- &payIntent:
|
case payChan <- &payIntent:
|
||||||
case <-reqQuit:
|
case <-reqQuit:
|
||||||
@@ -3751,20 +3760,41 @@ func (r *rpcServer) sendPayment(stream *paymentStream) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
sendLoop:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
||||||
|
// If we encounter and error either during sending or
|
||||||
|
// receiving, we return directly, closing the stream.
|
||||||
case err := <-errChan:
|
case err := <-errChan:
|
||||||
return err
|
return err
|
||||||
|
|
||||||
case payIntent := <-payChan:
|
case <-r.quit:
|
||||||
|
return errors.New("rpc server shutting down")
|
||||||
|
|
||||||
|
case payIntent, ok := <-payChan:
|
||||||
|
// If the receive loop is done, we break the send loop
|
||||||
|
// and wait for the ongoing payments to finish before
|
||||||
|
// exiting.
|
||||||
|
if !ok {
|
||||||
|
break sendLoop
|
||||||
|
}
|
||||||
|
|
||||||
// We launch a new goroutine to execute the current
|
// We launch a new goroutine to execute the current
|
||||||
// payment so we can continue to serve requests while
|
// payment so we can continue to serve requests while
|
||||||
// this payment is being dispatched.
|
// this payment is being dispatched.
|
||||||
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
// Attempt to grab a free semaphore slot, using
|
// Attempt to grab a free semaphore slot, using
|
||||||
// a defer to eventually release the slot
|
// a defer to eventually release the slot
|
||||||
// regardless of payment success.
|
// regardless of payment success.
|
||||||
<-htlcSema
|
select {
|
||||||
|
case <-htlcSema:
|
||||||
|
case <-reqQuit:
|
||||||
|
return
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
htlcSema <- struct{}{}
|
htlcSema <- struct{}{}
|
||||||
}()
|
}()
|
||||||
@@ -3778,7 +3808,13 @@ func (r *rpcServer) sendPayment(stream *paymentStream) error {
|
|||||||
// payment, then we'll return the error to the
|
// payment, then we'll return the error to the
|
||||||
// user, and terminate.
|
// user, and terminate.
|
||||||
case saveErr != nil:
|
case saveErr != nil:
|
||||||
errChan <- saveErr
|
rpcsLog.Errorf("Failed dispatching "+
|
||||||
|
"payment intent: %v", saveErr)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case errChan <- saveErr:
|
||||||
|
default:
|
||||||
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
// If we receive payment error than, instead of
|
// If we receive payment error than, instead of
|
||||||
@@ -3790,7 +3826,14 @@ func (r *rpcServer) sendPayment(stream *paymentStream) error {
|
|||||||
PaymentHash: payIntent.rHash[:],
|
PaymentHash: payIntent.rHash[:],
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- err
|
rpcsLog.Errorf("Failed "+
|
||||||
|
"sending error "+
|
||||||
|
"response: %v", err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case errChan <- err:
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -3810,12 +3853,22 @@ func (r *rpcServer) sendPayment(stream *paymentStream) error {
|
|||||||
PaymentRoute: marshalledRouted,
|
PaymentRoute: marshalledRouted,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- err
|
rpcsLog.Errorf("Failed sending "+
|
||||||
|
"response: %v", err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case errChan <- err:
|
||||||
|
default:
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for all goroutines to finish before closing the stream.
|
||||||
|
wg.Wait()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendPaymentSync is the synchronous non-streaming version of SendPayment.
|
// SendPaymentSync is the synchronous non-streaming version of SendPayment.
|
||||||
|
|||||||
Reference in new issue
Block a user