Skip to content

Commit

Permalink
rpcserver: use semaphore to limit # of goroutines in SendPayment
Browse files Browse the repository at this point in the history
This commit fixes a prior oversight in the implementation of
SendPayment that could result in tens of thousands of goroutines
OOM’ing an lnd daemon. Previously we didn’t limit the number of
outstanding payments that were allowed by a client. Users on machines
with a small amount of RAM were reporting crashes when sending a very
large number of payments in a consistent stream. This commit fixes this
issue by now using a semaphore to limit the number of outstanding
payments (and therefore) goroutines allowed in the SendPayment method.
  • Loading branch information
Roasbeef committed Apr 12, 2017
1 parent 41a5414 commit 9ff4a7a
Showing 1 changed file with 18 additions and 4 deletions.
22 changes: 18 additions & 4 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,15 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
errChan := make(chan error, 1)
payChan := make(chan *lnrpc.SendRequest)

// In order to limit the level of concurrency and prevent a client from
// attempting to OOM the server, we'll set up a semaphore to create an
// upper ceiling on the number of outstanding payments.
const numOutstandingPayments = 2000
htlcSema := make(chan struct{}, numOutstandingPayments)
for i := 0; i < numOutstandingPayments; i++ {
htlcSema <- struct{}{}
}

// Launch a new goroutine to handle reading new payment requests from
// the client. This way we can handle errors independently of blocking
// and waiting for the next payment request to come through.
Expand Down Expand Up @@ -980,13 +989,18 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
// We launch a new goroutine to execute the current
// payment so we can continue to serve requests while
// this payment is being dispatched.
//
// TODO(roasbeef): semaphore to limit num outstanding
// goroutines.
go func() {
// Attempt to grab a free semaphore slot, using
// a defer to eventually release the slot
// regardless of payment success.
<-htlcSema
defer func() {
htlcSema <- struct{}{}
}()

// Construct a payment request to send to the
// channel router. If the payment is
// successful, the the route chosen will be
// successful, the route chosen will be
// returned. Otherwise, we'll get a non-nil
// error.
payment := &routing.LightningPayment{
Expand Down

0 comments on commit 9ff4a7a

Please sign in to comment.