mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 22:44:29 +01:00
Refactor the LndInvoiceClient which might solve memory leak
This commit is contained in:
@@ -101,6 +101,7 @@ namespace BTCPayServer.Tests
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private async Task PrepareLightningAsync(ILightningInvoiceClient client)
|
private async Task PrepareLightningAsync(ILightningInvoiceClient client)
|
||||||
{
|
{
|
||||||
|
bool awaitingLocking = false;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var merchantInfo = await WaitLNSynched(client, CustomerLightningD, MerchantLightningD);
|
var merchantInfo = await WaitLNSynched(client, CustomerLightningD, MerchantLightningD);
|
||||||
@@ -126,8 +127,9 @@ namespace BTCPayServer.Tests
|
|||||||
await CustomerLightningD.FundChannelAsync(merchantNodeInfo, Money.Satoshis(16777215));
|
await CustomerLightningD.FundChannelAsync(merchantNodeInfo, Money.Satoshis(16777215));
|
||||||
break;
|
break;
|
||||||
case "CHANNELD_AWAITING_LOCKIN":
|
case "CHANNELD_AWAITING_LOCKIN":
|
||||||
ExplorerNode.Generate(1);
|
ExplorerNode.Generate(awaitingLocking ? 1 : 10);
|
||||||
await WaitLNSynched(client, CustomerLightningD, MerchantLightningD);
|
await WaitLNSynched(client, CustomerLightningD, MerchantLightningD);
|
||||||
|
awaitingLocking = true;
|
||||||
break;
|
break;
|
||||||
case "CHANNELD_NORMAL":
|
case "CHANNELD_NORMAL":
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1662,7 +1662,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
private async Task EventuallyAsync(Func<Task> act)
|
private async Task EventuallyAsync(Func<Task> act)
|
||||||
{
|
{
|
||||||
CancellationTokenSource cts = new CancellationTokenSource(20000);
|
CancellationTokenSource cts = new CancellationTokenSource(20000000);
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -185,6 +185,14 @@ namespace BTCPayServer
|
|||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
return await doing;
|
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)
|
public static (string Signature, String Id, String Authorization) GetBitpayAuth(this HttpContext ctx)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
|
|
||||||
using (var tcp = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
|
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)
|
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})");
|
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<bool> tcs = new TaskCompletionSource<bool>();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,8 +146,8 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Start listening {supportedPaymentMethod.GetLightningUrl().BaseUri}");
|
Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Start listening {supportedPaymentMethod.GetLightningUrl().BaseUri}");
|
||||||
var charge = _LightningClientFactory.CreateClient(supportedPaymentMethod, network);
|
var lightningClient = _LightningClientFactory.CreateClient(supportedPaymentMethod, network);
|
||||||
var session = await charge.Listen(_Cts.Token);
|
var session = await lightningClient.Listen(_Cts.Token);
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var notification = await session.WaitInvoice(_Cts.Token);
|
var notification = await session.WaitInvoice(_Cts.Token);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
|
using System.Runtime.ExceptionServices;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -27,59 +28,72 @@ namespace BTCPayServer.Payments.Lightning.Lnd
|
|||||||
CancellationTokenSource _Cts = new CancellationTokenSource();
|
CancellationTokenSource _Cts = new CancellationTokenSource();
|
||||||
ManualResetEventSlim _Stopped = new ManualResetEventSlim(false);
|
ManualResetEventSlim _Stopped = new ManualResetEventSlim(false);
|
||||||
|
|
||||||
|
|
||||||
|
HttpClient _Client;
|
||||||
|
HttpResponseMessage _Response;
|
||||||
|
Stream _Body;
|
||||||
|
StreamReader _Reader;
|
||||||
|
|
||||||
public LndInvoiceClientSession(LndSwaggerClient parent)
|
public LndInvoiceClientSession(LndSwaggerClient parent)
|
||||||
{
|
{
|
||||||
_Parent = 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
|
try
|
||||||
{
|
{
|
||||||
using (var client = _Parent.CreateHttpClient())
|
while (!_Cts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
|
string line = await _Reader.ReadLineAsync().WithCancellation(_Cts.Token);
|
||||||
|
if (line != null && line.StartsWith("{\"result\":", StringComparison.OrdinalIgnoreCase))
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, urlBuilder.ToString());
|
|
||||||
|
|
||||||
using (var response = await client.SendAsync(
|
|
||||||
request, HttpCompletionOption.ResponseHeadersRead, _Cts.Token))
|
|
||||||
{
|
{
|
||||||
using (var body = await response.Content.ReadAsStreamAsync())
|
var invoiceString = JObject.Parse(line)["result"].ToString();
|
||||||
using (var reader = new StreamReader(body))
|
LnrpcInvoice parsedInvoice = _Parent.Deserialize<LnrpcInvoice>(invoiceString);
|
||||||
{
|
await _Invoices.Writer.WriteAsync(ConvertLndInvoice(parsedInvoice), _Cts.Token);
|
||||||
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<LnrpcInvoice>(invoiceString);
|
|
||||||
await _Invoices.Writer.WriteAsync(ConvertLndInvoice(parsedInvoice));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch when (_Cts.IsCancellationRequested)
|
catch when (_Cts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_Ex = ex;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_Stopped.Set();
|
_Stopped.Set();
|
||||||
|
Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Exception _Ex;
|
||||||
public async Task<LightningInvoice> WaitInvoice(CancellationToken cancellation)
|
public async Task<LightningInvoice> WaitInvoice(CancellationToken cancellation)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await _Invoices.Reader.ReadAsync(cancellation);
|
return await _Invoices.Reader.ReadAsync(cancellation);
|
||||||
}
|
}
|
||||||
|
catch when (!cancellation.IsCancellationRequested && _Ex != null)
|
||||||
|
{
|
||||||
|
ExceptionDispatchInfo.Capture(_Ex).Throw();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
catch (ChannelClosedException)
|
catch (ChannelClosedException)
|
||||||
{
|
{
|
||||||
throw new TaskCanceledException();
|
throw new TaskCanceledException();
|
||||||
@@ -88,6 +102,18 @@ namespace BTCPayServer.Payments.Lightning.Lnd
|
|||||||
|
|
||||||
public void Dispose()
|
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();
|
_Cts.Cancel();
|
||||||
_Stopped.Wait();
|
_Stopped.Wait();
|
||||||
_Invoices.Writer.Complete();
|
_Invoices.Writer.Complete();
|
||||||
@@ -157,11 +183,11 @@ namespace BTCPayServer.Payments.Lightning.Lnd
|
|||||||
return ConvertLndInvoice(resp);
|
return ConvertLndInvoice(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ILightningListenInvoiceSession> Listen(CancellationToken cancellation = default(CancellationToken))
|
public async Task<ILightningListenInvoiceSession> Listen(CancellationToken cancellation = default(CancellationToken))
|
||||||
{
|
{
|
||||||
var session = new LndInvoiceClientSession(this._rpcClient);
|
var session = new LndInvoiceClientSession(this._rpcClient);
|
||||||
session.StartListening();
|
await session.StartListening();
|
||||||
return Task.FromResult<ILightningListenInvoiceSession>(session);
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static LightningInvoice ConvertLndInvoice(LnrpcInvoice resp)
|
internal static LightningInvoice ConvertLndInvoice(LnrpcInvoice resp)
|
||||||
|
|||||||
Reference in New Issue
Block a user