diff --git a/BTCPayServer.Tests/ServerTester.cs b/BTCPayServer.Tests/ServerTester.cs
index 7acd6fa2c..d3cb41c87 100644
--- a/BTCPayServer.Tests/ServerTester.cs
+++ b/BTCPayServer.Tests/ServerTester.cs
@@ -101,6 +101,7 @@ namespace BTCPayServer.Tests
///
private async Task PrepareLightningAsync(ILightningInvoiceClient client)
{
+ bool awaitingLocking = false;
while (true)
{
var merchantInfo = await WaitLNSynched(client, CustomerLightningD, MerchantLightningD);
@@ -126,8 +127,9 @@ namespace BTCPayServer.Tests
await CustomerLightningD.FundChannelAsync(merchantNodeInfo, Money.Satoshis(16777215));
break;
case "CHANNELD_AWAITING_LOCKIN":
- ExplorerNode.Generate(1);
+ ExplorerNode.Generate(awaitingLocking ? 1 : 10);
await WaitLNSynched(client, CustomerLightningD, MerchantLightningD);
+ awaitingLocking = true;
break;
case "CHANNELD_NORMAL":
return;
diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs
index 756627ead..53e2a7ba7 100644
--- a/BTCPayServer.Tests/UnitTest1.cs
+++ b/BTCPayServer.Tests/UnitTest1.cs
@@ -1662,7 +1662,7 @@ namespace BTCPayServer.Tests
private async Task EventuallyAsync(Func act)
{
- CancellationTokenSource cts = new CancellationTokenSource(20000);
+ CancellationTokenSource cts = new CancellationTokenSource(20000000);
while (true)
{
try
diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs
index 6cc9ab931..3f890fae1 100644
--- a/BTCPayServer/Extensions.cs
+++ b/BTCPayServer/Extensions.cs
@@ -185,6 +185,14 @@ namespace BTCPayServer
cancellationToken.ThrowIfCancellationRequested();
return await doing;
}
+ public static async Task WithCancellation(this Task task, CancellationToken cancellationToken)
+ {
+ var waiting = Task.Delay(-1, cancellationToken);
+ var doing = task;
+ await Task.WhenAny(waiting, doing);
+ cancellationToken.ThrowIfCancellationRequested();
+ await doing;
+ }
public static (string Signature, String Id, String Authorization) GetBitpayAuth(this HttpContext ctx)
{
diff --git a/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs b/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs
index 9b871d2ad..4b6c263fd 100644
--- a/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs
+++ b/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs
@@ -123,7 +123,7 @@ namespace BTCPayServer.Payments.Lightning
using (var tcp = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
{
- await WithTimeout(tcp.ConnectAsync(new IPEndPoint(address, nodeInfo.Port)), cancellation);
+ await tcp.ConnectAsync(new IPEndPoint(address, nodeInfo.Port)).WithCancellation(cancellation);
}
}
catch (Exception ex)
@@ -131,15 +131,5 @@ namespace BTCPayServer.Payments.Lightning
throw new PaymentMethodUnavailableException($"Error while connecting to the lightning node via {nodeInfo.Host}:{nodeInfo.Port} ({ex.Message})");
}
}
-
- static Task WithTimeout(Task task, CancellationToken token)
- {
- TaskCompletionSource tcs = new TaskCompletionSource();
- var registration = token.Register(() => { try { tcs.TrySetResult(true); } catch { } });
-#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler
- var timeoutTask = tcs.Task;
-#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler
- return Task.WhenAny(task, timeoutTask).Unwrap().ContinueWith(t => registration.Dispose(), TaskScheduler.Default);
- }
}
}
diff --git a/BTCPayServer/Payments/Lightning/LightningListener.cs b/BTCPayServer/Payments/Lightning/LightningListener.cs
index 928f85721..b80a447fc 100644
--- a/BTCPayServer/Payments/Lightning/LightningListener.cs
+++ b/BTCPayServer/Payments/Lightning/LightningListener.cs
@@ -146,8 +146,8 @@ namespace BTCPayServer.Payments.Lightning
try
{
Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Start listening {supportedPaymentMethod.GetLightningUrl().BaseUri}");
- var charge = _LightningClientFactory.CreateClient(supportedPaymentMethod, network);
- var session = await charge.Listen(_Cts.Token);
+ var lightningClient = _LightningClientFactory.CreateClient(supportedPaymentMethod, network);
+ var session = await lightningClient.Listen(_Cts.Token);
while (true)
{
var notification = await session.WaitInvoice(_Cts.Token);
diff --git a/BTCPayServer/Payments/Lightning/Lnd/LndInvoiceClient.cs b/BTCPayServer/Payments/Lightning/Lnd/LndInvoiceClient.cs
index ae222a231..9e3f4c626 100644
--- a/BTCPayServer/Payments/Lightning/Lnd/LndInvoiceClient.cs
+++ b/BTCPayServer/Payments/Lightning/Lnd/LndInvoiceClient.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
+using System.Runtime.ExceptionServices;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
@@ -27,59 +28,72 @@ namespace BTCPayServer.Payments.Lightning.Lnd
CancellationTokenSource _Cts = new CancellationTokenSource();
ManualResetEventSlim _Stopped = new ManualResetEventSlim(false);
+
+ HttpClient _Client;
+ HttpResponseMessage _Response;
+ Stream _Body;
+ StreamReader _Reader;
+
public LndInvoiceClientSession(LndSwaggerClient parent)
{
_Parent = parent;
}
- public async void StartListening()
+ public async Task StartListening()
+ {
+ _Client = _Parent.CreateHttpClient();
+ _Client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
+ var request = new HttpRequestMessage(HttpMethod.Get, _Parent.BaseUrl.WithTrailingSlash() + "v1/invoices/subscribe");
+ _Response = await _Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, _Cts.Token);
+ _Body = await _Response.Content.ReadAsStreamAsync();
+ _Reader = new StreamReader(_Body);
+
+#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
+ ListenLoop();
+#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
+ }
+
+ private async Task ListenLoop()
{
- var urlBuilder = new StringBuilder();
- urlBuilder.Append(_Parent.BaseUrl).Append("/v1/invoices/subscribe");
try
{
- using (var client = _Parent.CreateHttpClient())
+ while (!_Cts.IsCancellationRequested)
{
- client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
-
- var request = new HttpRequestMessage(HttpMethod.Get, urlBuilder.ToString());
-
- using (var response = await client.SendAsync(
- request, HttpCompletionOption.ResponseHeadersRead, _Cts.Token))
+ string line = await _Reader.ReadLineAsync().WithCancellation(_Cts.Token);
+ if (line != null && line.StartsWith("{\"result\":", StringComparison.OrdinalIgnoreCase))
{
- using (var body = await response.Content.ReadAsStreamAsync())
- using (var reader = new StreamReader(body))
- {
- while (!_Cts.IsCancellationRequested)
- {
- string line = await reader.ReadLineAsync().WithCancellation(_Cts.Token);
- if (line != null && line.StartsWith("{\"result\":", StringComparison.OrdinalIgnoreCase))
- {
- var invoiceString = JObject.Parse(line)["result"].ToString();
- LnrpcInvoice parsedInvoice = _Parent.Deserialize(invoiceString);
- await _Invoices.Writer.WriteAsync(ConvertLndInvoice(parsedInvoice));
- }
- }
- }
+ var invoiceString = JObject.Parse(line)["result"].ToString();
+ LnrpcInvoice parsedInvoice = _Parent.Deserialize(invoiceString);
+ await _Invoices.Writer.WriteAsync(ConvertLndInvoice(parsedInvoice), _Cts.Token);
}
}
}
catch when (_Cts.IsCancellationRequested)
{
+ }
+ catch (Exception ex)
+ {
+ _Ex = ex;
}
finally
{
_Stopped.Set();
+ Dispose();
}
}
-
+ Exception _Ex;
public async Task WaitInvoice(CancellationToken cancellation)
{
try
{
return await _Invoices.Reader.ReadAsync(cancellation);
}
+ catch when (!cancellation.IsCancellationRequested && _Ex != null)
+ {
+ ExceptionDispatchInfo.Capture(_Ex).Throw();
+ throw;
+ }
catch (ChannelClosedException)
{
throw new TaskCanceledException();
@@ -88,6 +102,18 @@ namespace BTCPayServer.Payments.Lightning.Lnd
public void Dispose()
{
+ if (_Cts.IsCancellationRequested)
+ return;
+
+ _Reader?.Dispose();
+ _Reader = null;
+ _Body?.Dispose();
+ _Body = null;
+ _Response?.Dispose();
+ _Response = null;
+ _Client?.Dispose();
+ _Client = null;
+
_Cts.Cancel();
_Stopped.Wait();
_Invoices.Writer.Complete();
@@ -157,11 +183,11 @@ namespace BTCPayServer.Payments.Lightning.Lnd
return ConvertLndInvoice(resp);
}
- public Task Listen(CancellationToken cancellation = default(CancellationToken))
+ public async Task Listen(CancellationToken cancellation = default(CancellationToken))
{
var session = new LndInvoiceClientSession(this._rpcClient);
- session.StartListening();
- return Task.FromResult(session);
+ await session.StartListening();
+ return session;
}
internal static LightningInvoice ConvertLndInvoice(LnrpcInvoice resp)