Unverified Commit bb981525 authored by Marc Gravell's avatar Marc Gravell Committed by GitHub

fix #1108 - introduce LogProxy as an intermediary between the TextWriter; move...

fix #1108 - introduce LogProxy as an intermediary between the TextWriter; move the sync to there - allows safe detach from the logging (#1116)
parent 93ee0fb6
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using static StackExchange.Redis.ConnectionMultiplexer;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
...@@ -523,7 +524,7 @@ internal bool HasDnsEndPoints() ...@@ -523,7 +524,7 @@ internal bool HasDnsEndPoints()
return false; return false;
} }
internal async Task ResolveEndPointsAsync(ConnectionMultiplexer multiplexer, TextWriter log) internal async Task ResolveEndPointsAsync(ConnectionMultiplexer multiplexer, LogProxy log)
{ {
var cache = new Dictionary<string, IPAddress>(StringComparer.OrdinalIgnoreCase); var cache = new Dictionary<string, IPAddress>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < EndPoints.Count; i++) for (int i = 0; i < EndPoints.Count; i++)
...@@ -542,12 +543,12 @@ internal async Task ResolveEndPointsAsync(ConnectionMultiplexer multiplexer, Tex ...@@ -542,12 +543,12 @@ internal async Task ResolveEndPointsAsync(ConnectionMultiplexer multiplexer, Tex
} }
else else
{ {
multiplexer.LogLocked(log, "Using DNS to resolve '{0}'...", dns.Host); log?.WriteLine($"Using DNS to resolve '{dns.Host}'...");
var ips = await Dns.GetHostAddressesAsync(dns.Host).ObserveErrors().ForAwait(); var ips = await Dns.GetHostAddressesAsync(dns.Host).ObserveErrors().ForAwait();
if (ips.Length == 1) if (ips.Length == 1)
{ {
ip = ips[0]; ip = ips[0];
multiplexer.LogLocked(log, "'{0}' => {1}", dns.Host, ip); log?.WriteLine($"'{dns.Host}' => {ip}");
cache[dns.Host] = ip; cache[dns.Host] = ip;
EndPoints[i] = new IPEndPoint(ip, dns.Port); EndPoints[i] = new IPEndPoint(ip, dns.Port);
} }
...@@ -556,7 +557,7 @@ internal async Task ResolveEndPointsAsync(ConnectionMultiplexer multiplexer, Tex ...@@ -556,7 +557,7 @@ internal async Task ResolveEndPointsAsync(ConnectionMultiplexer multiplexer, Tex
catch (Exception ex) catch (Exception ex)
{ {
multiplexer.OnInternalError(ex); multiplexer.OnInternalError(ex);
multiplexer.LogLocked(log, ex.Message); log?.WriteLine(ex.Message);
} }
} }
} }
......
...@@ -20,7 +20,7 @@ partial class ConnectionMultiplexer ...@@ -20,7 +20,7 @@ partial class ConnectionMultiplexer
Debug.WriteLine(message, Environment.CurrentManagedThreadId + " ~ " + category); Debug.WriteLine(message, Environment.CurrentManagedThreadId + " ~ " + category);
} }
partial void OnTraceLog(TextWriter log, string caller) partial void OnTraceLog(LogProxy log, string caller)
{ {
lock (UniqueId) lock (UniqueId)
{ {
......
...@@ -8,20 +8,21 @@ ...@@ -8,20 +8,21 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StackExchange.Redis.Profiling; using StackExchange.Redis.Profiling;
using static StackExchange.Redis.ConnectionMultiplexer;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
internal sealed class LoggingMessage : Message internal sealed class LoggingMessage : Message
{ {
public readonly TextWriter log; public readonly LogProxy log;
private readonly Message tail; private readonly Message tail;
public static Message Create(TextWriter log, Message tail) public static Message Create(LogProxy log, Message tail)
{ {
return log == null ? tail : new LoggingMessage(log, tail); return log == null ? tail : new LoggingMessage(log, tail);
} }
private LoggingMessage(TextWriter log, Message tail) : base(tail.Db, tail.Flags, tail.Command) private LoggingMessage(LogProxy log, Message tail) : base(tail.Db, tail.Flags, tail.Command)
{ {
this.log = log; this.log = log;
this.tail = tail; this.tail = tail;
...@@ -39,14 +40,14 @@ protected override void WriteImpl(PhysicalConnection physical) ...@@ -39,14 +40,14 @@ protected override void WriteImpl(PhysicalConnection physical)
try try
{ {
var bridge = physical.BridgeCouldBeNull; var bridge = physical.BridgeCouldBeNull;
bridge?.Multiplexer?.LogLocked(log, "Writing to {0}: {1}", bridge, tail.CommandAndKey); log?.WriteLine($"Writing to {bridge}: {tail.CommandAndKey}");
} }
catch { } catch { }
tail.WriteTo(physical); tail.WriteTo(physical);
} }
public override int ArgCount => tail.ArgCount; public override int ArgCount => tail.ArgCount;
public TextWriter Log => log; public LogProxy Log => log;
} }
internal abstract class Message : ICompletable internal abstract class Message : ICompletable
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
using Pipelines.Sockets.Unofficial; using Pipelines.Sockets.Unofficial;
using Pipelines.Sockets.Unofficial.Threading; using Pipelines.Sockets.Unofficial.Threading;
using static Pipelines.Sockets.Unofficial.Threading.MutexSlim; using static Pipelines.Sockets.Unofficial.Threading.MutexSlim;
using static StackExchange.Redis.ConnectionMultiplexer;
using PendingSubscriptionState = global::StackExchange.Redis.ConnectionMultiplexer.Subscription.PendingSubscriptionState; using PendingSubscriptionState = global::StackExchange.Redis.ConnectionMultiplexer.Subscription.PendingSubscriptionState;
namespace StackExchange.Redis namespace StackExchange.Redis
...@@ -129,7 +130,7 @@ public void ReportNextFailure() ...@@ -129,7 +130,7 @@ public void ReportNextFailure()
public override string ToString() => ConnectionType + "/" + Format.ToString(ServerEndPoint.EndPoint); public override string ToString() => ConnectionType + "/" + Format.ToString(ServerEndPoint.EndPoint);
public void TryConnect(TextWriter log) => GetConnection(log); public void TryConnect(LogProxy log) => GetConnection(log);
private WriteResult QueueOrFailMessage(Message message) private WriteResult QueueOrFailMessage(Message message)
{ {
...@@ -380,7 +381,7 @@ internal void KeepAlive() ...@@ -380,7 +381,7 @@ internal void KeepAlive()
} }
} }
internal async Task OnConnectedAsync(PhysicalConnection connection, TextWriter log) internal async Task OnConnectedAsync(PhysicalConnection connection, LogProxy log)
{ {
Trace("OnConnected"); Trace("OnConnected");
if (physical == connection && !isDisposed && ChangeState(State.Connecting, State.ConnectedEstablishing)) if (physical == connection && !isDisposed && ChangeState(State.Connecting, State.ConnectedEstablishing))
...@@ -1097,7 +1098,7 @@ private bool ChangeState(State oldState, State newState) ...@@ -1097,7 +1098,7 @@ private bool ChangeState(State oldState, State newState)
return result; return result;
} }
private PhysicalConnection GetConnection(TextWriter log) private PhysicalConnection GetConnection(LogProxy log)
{ {
if (state == (int)State.Disconnected) if (state == (int)State.Disconnected)
{ {
...@@ -1105,7 +1106,7 @@ private PhysicalConnection GetConnection(TextWriter log) ...@@ -1105,7 +1106,7 @@ private PhysicalConnection GetConnection(TextWriter log)
{ {
if (!Multiplexer.IsDisposed) if (!Multiplexer.IsDisposed)
{ {
Multiplexer.LogLocked(log, "Connecting {0}...", Name); log?.WriteLine($"Connecting {Name}...");
Multiplexer.Trace("Connecting...", Name); Multiplexer.Trace("Connecting...", Name);
if (ChangeState(State.Disconnected, State.Connecting)) if (ChangeState(State.Disconnected, State.Connecting))
{ {
...@@ -1122,7 +1123,7 @@ private PhysicalConnection GetConnection(TextWriter log) ...@@ -1122,7 +1123,7 @@ private PhysicalConnection GetConnection(TextWriter log)
} }
catch (Exception ex) catch (Exception ex)
{ {
Multiplexer.LogLocked(log, "Connect {0} failed: {1}", Name, ex.Message); log?.WriteLine($"Connect {Name} failed: {ex.Message}");
Multiplexer.Trace("Connect failed: " + ex.Message, Name); Multiplexer.Trace("Connect failed: " + ex.Message, Name);
ChangeState(State.Disconnected); ChangeState(State.Disconnected);
OnInternalError(ex); OnInternalError(ex);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Pipelines.Sockets.Unofficial; using Pipelines.Sockets.Unofficial;
using Pipelines.Sockets.Unofficial.Arenas; using Pipelines.Sockets.Unofficial.Arenas;
using static StackExchange.Redis.ConnectionMultiplexer;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
...@@ -85,7 +86,7 @@ public PhysicalConnection(PhysicalBridge bridge) ...@@ -85,7 +86,7 @@ public PhysicalConnection(PhysicalBridge bridge)
OnCreateEcho(); OnCreateEcho();
} }
internal async Task BeginConnectAsync(TextWriter log) internal async Task BeginConnectAsync(LogProxy log)
{ {
var bridge = BridgeCouldBeNull; var bridge = BridgeCouldBeNull;
var endpoint = bridge?.ServerEndPoint?.EndPoint; var endpoint = bridge?.ServerEndPoint?.EndPoint;
...@@ -97,7 +98,7 @@ internal async Task BeginConnectAsync(TextWriter log) ...@@ -97,7 +98,7 @@ internal async Task BeginConnectAsync(TextWriter log)
Trace("Connecting..."); Trace("Connecting...");
_socket = SocketManager.CreateSocket(endpoint); _socket = SocketManager.CreateSocket(endpoint);
bridge.Multiplexer.OnConnecting(endpoint, bridge.ConnectionType); bridge.Multiplexer.OnConnecting(endpoint, bridge.ConnectionType);
bridge.Multiplexer.LogLocked(log, "BeginConnect: {0}", Format.ToString(endpoint)); log?.WriteLine($"BeginConnect: {Format.ToString(endpoint)}");
CancellationTokenSource timeoutSource = null; CancellationTokenSource timeoutSource = null;
try try
...@@ -141,7 +142,7 @@ internal async Task BeginConnectAsync(TextWriter log) ...@@ -141,7 +142,7 @@ internal async Task BeginConnectAsync(TextWriter log)
} }
else if (await ConnectedAsync(x, log, bridge.Multiplexer.SocketManager).ForAwait()) else if (await ConnectedAsync(x, log, bridge.Multiplexer.SocketManager).ForAwait())
{ {
bridge.Multiplexer.LogLocked(log, "Starting read"); log?.WriteLine("Starting read");
try try
{ {
StartReading(); StartReading();
...@@ -161,7 +162,7 @@ internal async Task BeginConnectAsync(TextWriter log) ...@@ -161,7 +162,7 @@ internal async Task BeginConnectAsync(TextWriter log)
} }
catch (ObjectDisposedException) catch (ObjectDisposedException)
{ {
bridge.Multiplexer.LogLocked(log, "(socket shutdown)"); log?.WriteLine("(socket shutdown)");
try { RecordConnectionFailed(ConnectionFailureType.UnableToConnect, isInitialConnect: true); } try { RecordConnectionFailed(ConnectionFailureType.UnableToConnect, isInitialConnect: true); }
catch (Exception inner) catch (Exception inner)
{ {
...@@ -1251,7 +1252,7 @@ private static LocalCertificateSelectionCallback GetAmbientClientCertificateCall ...@@ -1251,7 +1252,7 @@ private static LocalCertificateSelectionCallback GetAmbientClientCertificateCall
return null; return null;
} }
internal async ValueTask<bool> ConnectedAsync(Socket socket, TextWriter log, SocketManager manager) internal async ValueTask<bool> ConnectedAsync(Socket socket, LogProxy log, SocketManager manager)
{ {
var bridge = BridgeCouldBeNull; var bridge = BridgeCouldBeNull;
if (bridge == null) return false; if (bridge == null) return false;
...@@ -1270,7 +1271,7 @@ internal async ValueTask<bool> ConnectedAsync(Socket socket, TextWriter log, Soc ...@@ -1270,7 +1271,7 @@ internal async ValueTask<bool> ConnectedAsync(Socket socket, TextWriter log, Soc
if (config.Ssl) if (config.Ssl)
{ {
bridge.Multiplexer.LogLocked(log, "Configuring SSL"); log?.WriteLine("Configuring TLS");
var host = config.SslHost; var host = config.SslHost;
if (string.IsNullOrWhiteSpace(host)) host = Format.ToStringHostOnly(bridge.ServerEndPoint.EndPoint); if (string.IsNullOrWhiteSpace(host)) host = Format.ToStringHostOnly(bridge.ServerEndPoint.EndPoint);
...@@ -1290,7 +1291,7 @@ internal async ValueTask<bool> ConnectedAsync(Socket socket, TextWriter log, Soc ...@@ -1290,7 +1291,7 @@ internal async ValueTask<bool> ConnectedAsync(Socket socket, TextWriter log, Soc
bridge.Multiplexer?.SetAuthSuspect(); bridge.Multiplexer?.SetAuthSuspect();
throw; throw;
} }
bridge.Multiplexer.LogLocked(log, $"SSL connection established successfully using protocol: {ssl.SslProtocol}"); log?.WriteLine($"TLS connection established successfully using protocol: {ssl.SslProtocol}");
} }
catch (AuthenticationException authexception) catch (AuthenticationException authexception)
{ {
...@@ -1308,7 +1309,7 @@ internal async ValueTask<bool> ConnectedAsync(Socket socket, TextWriter log, Soc ...@@ -1308,7 +1309,7 @@ internal async ValueTask<bool> ConnectedAsync(Socket socket, TextWriter log, Soc
_ioPipe = pipe; _ioPipe = pipe;
bridge.Multiplexer.LogLocked(log, "Connected {0}", bridge); log?.WriteLine($"Connected {bridge}");
await bridge.OnConnectedAsync(this, log).ForAwait(); await bridge.OnConnectedAsync(this, log).ForAwait();
return true; return true;
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using static StackExchange.Redis.ConnectionMultiplexer;
#pragma warning disable RCS1231 // Make parameter ref read-only. #pragma warning disable RCS1231 // Make parameter ref read-only.
...@@ -320,7 +321,10 @@ public Task<DateTime> LastSaveAsync(CommandFlags flags = CommandFlags.None) ...@@ -320,7 +321,10 @@ public Task<DateTime> LastSaveAsync(CommandFlags flags = CommandFlags.None)
public void MakeMaster(ReplicationChangeOptions options, TextWriter log = null) public void MakeMaster(ReplicationChangeOptions options, TextWriter log = null)
{ {
multiplexer.MakeMaster(server, options, log); using (var proxy = LogProxy.TryCreate(log))
{
multiplexer.MakeMaster(server, options, proxy);
}
} }
public void Save(SaveType type, CommandFlags flags = CommandFlags.None) public void Save(SaveType type, CommandFlags flags = CommandFlags.None)
......
...@@ -175,7 +175,7 @@ public virtual bool SetResult(PhysicalConnection connection, Message message, in ...@@ -175,7 +175,7 @@ public virtual bool SetResult(PhysicalConnection connection, Message message, in
{ {
try try
{ {
bridge?.Multiplexer?.LogLocked(logging.Log, "Response from {0} / {1}: {2}", bridge, message.CommandAndKey, result); logging.Log?.WriteLine($"Response from {bridge} / {message.CommandAndKey}: {result}");
} }
catch { } catch { }
} }
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using static StackExchange.Redis.ConnectionMultiplexer;
using static StackExchange.Redis.PhysicalBridge; using static StackExchange.Redis.PhysicalBridge;
namespace StackExchange.Redis namespace StackExchange.Redis
...@@ -155,7 +156,7 @@ public void Dispose() ...@@ -155,7 +156,7 @@ public void Dispose()
tmp?.Dispose(); tmp?.Dispose();
} }
public PhysicalBridge GetBridge(ConnectionType type, bool create = true, TextWriter log = null) public PhysicalBridge GetBridge(ConnectionType type, bool create = true, LogProxy log = null)
{ {
if (isDisposed) return null; if (isDisposed) return null;
switch (type) switch (type)
...@@ -237,7 +238,7 @@ public void SetUnselectable(UnselectableFlags flags) ...@@ -237,7 +238,7 @@ public void SetUnselectable(UnselectableFlags flags)
public ValueTask<WriteResult> TryWriteAsync(Message message) => GetBridge(message.Command)?.TryWriteAsync(message, isSlave) ?? new ValueTask<WriteResult>(WriteResult.NoConnectionAvailable); public ValueTask<WriteResult> TryWriteAsync(Message message) => GetBridge(message.Command)?.TryWriteAsync(message, isSlave) ?? new ValueTask<WriteResult>(WriteResult.NoConnectionAvailable);
internal void Activate(ConnectionType type, TextWriter log) internal void Activate(ConnectionType type, LogProxy log)
{ {
GetBridge(type, true, log); GetBridge(type, true, log);
} }
...@@ -467,7 +468,7 @@ internal bool IsSelectable(RedisCommand command, bool allowDisconnected = false) ...@@ -467,7 +468,7 @@ internal bool IsSelectable(RedisCommand command, bool allowDisconnected = false)
return bridge != null && (allowDisconnected || bridge.IsConnected); return bridge != null && (allowDisconnected || bridge.IsConnected);
} }
internal Task OnEstablishingAsync(PhysicalConnection connection, TextWriter log) internal Task OnEstablishingAsync(PhysicalConnection connection, LogProxy log)
{ {
try try
{ {
...@@ -624,7 +625,7 @@ internal void ReportNextFailure() ...@@ -624,7 +625,7 @@ internal void ReportNextFailure()
subscription?.ReportNextFailure(); subscription?.ReportNextFailure();
} }
internal Task<bool> SendTracer(TextWriter log = null) internal Task<bool> SendTracer(LogProxy log = null)
{ {
var msg = GetTracerMessage(false); var msg = GetTracerMessage(false);
msg = LoggingMessage.Create(log, msg); msg = LoggingMessage.Create(log, msg);
...@@ -727,7 +728,7 @@ internal void WriteDirectOrQueueFireAndForgetSync<T>(PhysicalConnection connecti ...@@ -727,7 +728,7 @@ internal void WriteDirectOrQueueFireAndForgetSync<T>(PhysicalConnection connecti
} }
} }
private PhysicalBridge CreateBridge(ConnectionType type, TextWriter log) private PhysicalBridge CreateBridge(ConnectionType type, LogProxy log)
{ {
if (Multiplexer.IsDisposed) return null; if (Multiplexer.IsDisposed) return null;
Multiplexer.Trace(type.ToString()); Multiplexer.Trace(type.ToString());
...@@ -736,9 +737,9 @@ private PhysicalBridge CreateBridge(ConnectionType type, TextWriter log) ...@@ -736,9 +737,9 @@ private PhysicalBridge CreateBridge(ConnectionType type, TextWriter log)
return bridge; return bridge;
} }
private async Task HandshakeAsync(PhysicalConnection connection, TextWriter log) private async Task HandshakeAsync(PhysicalConnection connection, LogProxy log)
{ {
Multiplexer.LogLocked(log, "Server handshake"); log?.WriteLine("Server handshake");
if (connection == null) if (connection == null)
{ {
Multiplexer.Trace("No connection!?"); Multiplexer.Trace("No connection!?");
...@@ -748,7 +749,7 @@ private async Task HandshakeAsync(PhysicalConnection connection, TextWriter log) ...@@ -748,7 +749,7 @@ private async Task HandshakeAsync(PhysicalConnection connection, TextWriter log)
string password = Multiplexer.RawConfig.Password; string password = Multiplexer.RawConfig.Password;
if (!string.IsNullOrWhiteSpace(password)) if (!string.IsNullOrWhiteSpace(password))
{ {
Multiplexer.LogLocked(log, "Authenticating (password)"); log?.WriteLine("Authenticating (password)");
msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.AUTH, (RedisValue)password); msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.AUTH, (RedisValue)password);
msg.SetInternalCall(); msg.SetInternalCall();
await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait();
...@@ -762,7 +763,7 @@ private async Task HandshakeAsync(PhysicalConnection connection, TextWriter log) ...@@ -762,7 +763,7 @@ private async Task HandshakeAsync(PhysicalConnection connection, TextWriter log)
name = nameSanitizer.Replace(name, ""); name = nameSanitizer.Replace(name, "");
if (!string.IsNullOrWhiteSpace(name)) if (!string.IsNullOrWhiteSpace(name))
{ {
Multiplexer.LogLocked(log, "Setting client name: {0}", name); log?.WriteLine($"Setting client name: {name}");
msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.SETNAME, (RedisValue)name); msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.SETNAME, (RedisValue)name);
msg.SetInternalCall(); msg.SetInternalCall();
await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait();
...@@ -779,10 +780,10 @@ private async Task HandshakeAsync(PhysicalConnection connection, TextWriter log) ...@@ -779,10 +780,10 @@ private async Task HandshakeAsync(PhysicalConnection connection, TextWriter log)
if (connType == ConnectionType.Interactive) if (connType == ConnectionType.Interactive)
{ {
Multiplexer.LogLocked(log, "Auto-configure..."); log?.WriteLine("Auto-configure...");
AutoConfigure(connection); AutoConfigure(connection);
} }
Multiplexer.LogLocked(log, "Sending critical tracer: {0}", bridge); log?.WriteLine($"Sending critical tracer: {bridge}");
var tracer = GetTracerMessage(true); var tracer = GetTracerMessage(true);
tracer = LoggingMessage.Create(log, tracer); tracer = LoggingMessage.Create(log, tracer);
await WriteDirectOrQueueFireAndForgetAsync(connection, tracer, ResultProcessor.EstablishConnection).ForAwait(); await WriteDirectOrQueueFireAndForgetAsync(connection, tracer, ResultProcessor.EstablishConnection).ForAwait();
...@@ -798,7 +799,7 @@ private async Task HandshakeAsync(PhysicalConnection connection, TextWriter log) ...@@ -798,7 +799,7 @@ private async Task HandshakeAsync(PhysicalConnection connection, TextWriter log)
await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.TrackSubscriptions).ForAwait(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.TrackSubscriptions).ForAwait();
} }
} }
Multiplexer.LogLocked(log, "Flushing outbound buffer"); log?.WriteLine("Flushing outbound buffer");
await connection.FlushAsync().ForAwait(); await connection.FlushAsync().ForAwait();
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment