Skip to content

Commit 17c1baf

Browse files
author
Artem Morozov
committed
1. Логирование WS-сообщений на уровне Information (PrinterModel.cs:535-555 (file:///Users/temmo/Projects/print-winapp/PrinterApp/PrinterModel.cs)) — теперь в prod-логах будет видно: тип фрейма, размер,
JSON-payload и факт получения Close-фрейма от сервера. Это даст ответ на вопрос «что именно шлёт сервер сразу после connect». 2. Рекурсивный retry → цикл (PrinterModel.cs:449-502 (file:///Users/temmo/Projects/print-winapp/PrinterApp/PrinterModel.cs)) — SocketsStartAsync разбит на два метода: внешний управляет переподключениями через while (!_socketClose), внутренний RunSocketSessionAsync делает одну сессию. Раньше catch-блоки вызывали SocketsStartAsync() рекурсивно — каждое падение наращивало стек и плодило сокеты. 3. socket.Dispose() в finally (PrinterModel.cs:491-494 (file:///Users/temmo/Projects/print-winapp/PrinterApp/PrinterModel.cs)) — раньше сокет не диспозился, держал TCP-сокет/handles. 4. return после Close() в ParseResponseFromSocket (PrinterModel.cs:582 (file:///Users/temmo/Projects/print-winapp/PrinterApp/PrinterModel.cs)) — Close() всё равно вызывает Environment.Exit, но теперь не 5. GenerateQr через UI-Dispatcher (PrinterModel.cs:659-674 (file:///Users/temmo/Projects/print-winapp/PrinterApp/PrinterModel.cs)) — корневой фикс утечки Win32Exception (8). По стек-трейсу из лога 29 апреля видно: PathGeometry..ctor → new Dispatcher() → HwndWrapper..ctor. Каждый вызов из non-UI потока создавал новый Win32-Dispatcher с собственным окном, через ~10к QR’ов процесс упирался в лимит USER-объектов. Перенос на UI-поток переиспользует один Dispatcher.
1 parent 4ec5986 commit 17c1baf

1 file changed

Lines changed: 118 additions & 106 deletions

File tree

PrinterApp/PrinterModel.cs

Lines changed: 118 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -450,114 +450,121 @@ private async void SocketsStartAsync()
450450
{
451451
Log.Information("=== [WEBSOCKET START] ===");
452452
Log.Information($"[WEBSOCKET] Target URL: {WebSockUrl}");
453-
454-
var socket = new ClientWebSocket();
455-
456-
try
453+
_socketClose = false;
454+
455+
while (!_socketClose)
457456
{
458-
Log.Information($"[WEBSOCKET] Authorization header: {_httpClient.DefaultRequestHeaders.Authorization}");
459-
socket.Options.SetRequestHeader("Authorization",
460-
_httpClient.DefaultRequestHeaders.Authorization!.ToString());
461-
462-
Log.Information("[WEBSOCKET] Attempting connection...");
463-
464-
// Добавим timeout
465-
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
466-
await socket.ConnectAsync(new Uri(WebSockUrl), cts.Token);
467-
468-
Log.Information($"[WEBSOCKET] Connected! State: {socket.State}");
469-
470-
if (socket.State != WebSocketState.Open)
457+
var socket = new ClientWebSocket();
458+
try
459+
{
460+
await RunSocketSessionAsync(socket);
461+
if (_socketClose) break;
462+
}
463+
catch (OperationCanceledException timeoutEx)
464+
{
465+
Log.Error("[WEBSOCKET] *** TIMEOUT EXCEPTION ***");
466+
Log.Error($"[WEBSOCKET] Message: {timeoutEx.Message}");
467+
Marketing.SocketException(status: $"Timeout: {timeoutEx.Message}");
468+
socket.Abort();
469+
}
470+
catch (WebSocketException wsEx)
471471
{
472-
Log.Error($"[WEBSOCKET] *** STATE IS NOT OPEN: {socket.State} ***");
473-
Marketing.SocketException(
474-
status: $"WebSocketState not Open state:{socket.State}");
472+
Log.Error("[WEBSOCKET] *** WEBSOCKET EXCEPTION ***");
473+
Log.Error($"[WEBSOCKET] Type: {wsEx.GetType().Name}");
474+
Log.Error($"[WEBSOCKET] Message: {wsEx.Message}");
475475
Log.Error(
476-
$"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: WebSocketState not Open state:{socket.State}");
477-
return;
476+
$"[WEBSOCKET] Inner: {wsEx.InnerException?.GetType().Name} {wsEx.InnerException?.Message}");
477+
Marketing.SocketException(status: wsEx.Message);
478+
socket.Abort();
479+
}
480+
catch (Exception exception)
481+
{
482+
Log.Error("[WEBSOCKET] *** GENERAL EXCEPTION ***");
483+
Log.Error($"[WEBSOCKET] Type: {exception.GetType().Name}");
484+
Log.Error($"[WEBSOCKET] Message: {exception.Message}");
485+
Log.Error(
486+
$"[WEBSOCKET] Inner: {exception.InnerException?.GetType().Name} {exception.InnerException?.Message}");
487+
Marketing.SocketException(status: exception.Message);
488+
PrinterViewModel.PrintQr = null!;
489+
socket.Abort();
490+
}
491+
finally
492+
{
493+
socket.Dispose();
478494
}
479495

480-
Log.Information("[WEBSOCKET] Socket state is OPEN, starting message loop...");
481-
Marketing.SocketConnected();
482-
_socketClose = false;
483-
var buffer = new byte[128 * 1024];
484-
485-
while (!_socketClose)
496+
if (_socketClose) break;
497+
Log.Information("[WEBSOCKET] Reconnecting in 5 seconds...");
498+
await Task.Delay(5000);
499+
}
500+
501+
Log.Information("=== [WEBSOCKET END] ===");
502+
}
503+
504+
private async Task RunSocketSessionAsync(ClientWebSocket socket)
505+
{
506+
Log.Information(
507+
$"[WEBSOCKET] Authorization header: {_httpClient.DefaultRequestHeaders.Authorization}");
508+
socket.Options.SetRequestHeader("Authorization",
509+
_httpClient.DefaultRequestHeaders.Authorization!.ToString());
510+
511+
Log.Information("[WEBSOCKET] Attempting connection...");
512+
513+
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
514+
await socket.ConnectAsync(new Uri(WebSockUrl), cts.Token);
515+
516+
Log.Information($"[WEBSOCKET] Connected! State: {socket.State}");
517+
518+
if (socket.State != WebSocketState.Open)
519+
{
520+
Log.Error($"[WEBSOCKET] *** STATE IS NOT OPEN: {socket.State} ***");
521+
Marketing.SocketException(
522+
status: $"WebSocketState not Open state:{socket.State}");
523+
return;
524+
}
525+
526+
Log.Information("[WEBSOCKET] Socket state is OPEN, starting message loop...");
527+
Marketing.SocketConnected();
528+
var buffer = new byte[128 * 1024];
529+
530+
while (!_socketClose)
531+
{
532+
var result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer),
533+
CancellationToken.None);
534+
535+
Log.Information(
536+
$"[WEBSOCKET] Received {result.Count} bytes, type={result.MessageType}, EndOfMessage={result.EndOfMessage}");
537+
538+
if (result.MessageType == WebSocketMessageType.Close)
486539
{
487-
Log.Debug("[WEBSOCKET] Waiting for message...");
488-
var result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer),
489-
CancellationToken.None);
490-
491-
Log.Debug($"[WEBSOCKET] Received {result.Count} bytes, EndOfMessage: {result.EndOfMessage}");
492-
493-
var json = Encoding.UTF8.GetString(buffer, 0, result.Count);
494-
Log.Debug($"[WEBSOCKET] Message content: {json}");
495-
496-
if (!result.EndOfMessage)
497-
{
498-
Thread.Sleep(100);
499-
result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer),
500-
CancellationToken.None);
501-
json += Encoding.UTF8.GetString(buffer, 0, result.Count);
502-
Log.Debug($"[WEBSOCKET] Combined message: {json}");
503-
}
540+
Log.Warning(
541+
$"[WEBSOCKET] Server sent Close frame: status={result.CloseStatus}, description={result.CloseStatusDescription}");
542+
break;
543+
}
544+
545+
var json = Encoding.UTF8.GetString(buffer, 0, result.Count);
546+
Log.Information($"[WEBSOCKET] Message content: {json}");
504547

505-
await ParseResponseFromSocket(
506-
JsonConvert.DeserializeObject<WebsocketReceiveOptions>(json));
548+
if (!result.EndOfMessage)
549+
{
550+
Thread.Sleep(100);
551+
result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer),
552+
CancellationToken.None);
553+
json += Encoding.UTF8.GetString(buffer, 0, result.Count);
554+
Log.Information($"[WEBSOCKET] Combined message: {json}");
507555
}
508556

557+
await ParseResponseFromSocket(
558+
JsonConvert.DeserializeObject<WebsocketReceiveOptions>(json));
559+
}
560+
561+
if (socket.State == WebSocketState.Open)
562+
{
509563
Log.Information("[WEBSOCKET] Closing connection...");
510564
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Good Bye",
511565
CancellationToken.None);
512566
Log.Information("[WEBSOCKET] Closed successfully");
513567
}
514-
catch (OperationCanceledException timeoutEx)
515-
{
516-
Log.Error("[WEBSOCKET] *** TIMEOUT EXCEPTION ***");
517-
Log.Error($"[WEBSOCKET] Message: {timeoutEx.Message}");
518-
Log.Error($"[WEBSOCKET] Stack trace: {timeoutEx.StackTrace}");
519-
_socketClose = true;
520-
Marketing.SocketException(status: $"Timeout: {timeoutEx.Message}");
521-
socket.Abort();
522-
}
523-
catch (WebSocketException wsEx)
524-
{
525-
Log.Error("[WEBSOCKET] *** WEBSOCKET EXCEPTION ***");
526-
Log.Error($"[WEBSOCKET] Type: {wsEx.GetType().Name}");
527-
Log.Error($"[WEBSOCKET] Message: {wsEx.Message}");
528-
Log.Error($"[WEBSOCKET] Inner exception: {wsEx.InnerException?.GetType().Name}");
529-
Log.Error($"[WEBSOCKET] Inner message: {wsEx.InnerException?.Message}");
530-
Log.Error($"[WEBSOCKET] Stack trace: {wsEx.StackTrace}");
531-
_socketClose = true;
532-
Marketing.SocketException(status: wsEx.Message);
533-
socket.Abort();
534-
535-
Log.Information("[WEBSOCKET] Retrying in 5 seconds...");
536-
await Task.Delay(5000);
537-
SocketsStartAsync();
538-
}
539-
catch (Exception exception)
540-
{
541-
Log.Error("[WEBSOCKET] *** GENERAL EXCEPTION ***");
542-
Log.Error($"[WEBSOCKET] Type: {exception.GetType().Name}");
543-
Log.Error($"[WEBSOCKET] Message: {exception.Message}");
544-
Log.Error($"[WEBSOCKET] Inner exception: {exception.InnerException?.GetType().Name}");
545-
Log.Error($"[WEBSOCKET] Inner message: {exception.InnerException?.Message}");
546-
Log.Error($"[WEBSOCKET] Stack trace: {exception.StackTrace}");
547-
548-
_socketClose = true;
549-
Marketing.SocketException(status: exception.Message);
550-
PrinterViewModel.PrintQr = null!;
551-
socket.Abort();
552-
553-
Log.Information("[WEBSOCKET] Retrying in 5 seconds...");
554-
await Task.Delay(5000);
555-
SocketsStartAsync();
556-
}
557-
finally
558-
{
559-
Log.Information("=== [WEBSOCKET END] ===");
560-
}
561568
}
562569

563570
private async Task ParseResponseFromSocket(WebsocketReceiveOptions? websocketReceiveOptions)
@@ -570,8 +577,10 @@ private async Task ParseResponseFromSocket(WebsocketReceiveOptions? websocketRec
570577

571578
if (websocketReceiveOptions.Error != null && websocketReceiveOptions.Error != "")
572579
{
580+
Log.Error($"[WEBSOCKET] Server returned error in payload: {websocketReceiveOptions.Error}");
573581
Marketing.SocketException(websocketReceiveOptions.Error);
574582
Close();
583+
return;
575584
}
576585

577586
if (websocketReceiveOptions.ManualUpdate)
@@ -647,18 +656,21 @@ private void GenerateQr(string value)
647656
{
648657
Log.Debug(
649658
$"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: new qr code {value}");
650-
try
659+
Application.Current.Dispatcher.Invoke(() =>
651660
{
652-
var image = _barcodeWriterGeometry.Write(value);
653-
image.Freeze();
654-
PrinterViewModel.PrintQr = image;
655-
}
656-
catch (Exception exception)
657-
{
658-
Log.Error(
659-
$"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: {exception}");
660-
Marketing.QrGeneratorException(exception.ToString());
661-
PrinterViewModel.PrintQr = null!;
662-
}
661+
try
662+
{
663+
var image = _barcodeWriterGeometry.Write(value);
664+
image.Freeze();
665+
PrinterViewModel.PrintQr = image;
666+
}
667+
catch (Exception exception)
668+
{
669+
Log.Error(
670+
$"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: {exception}");
671+
Marketing.QrGeneratorException(exception.ToString());
672+
PrinterViewModel.PrintQr = null!;
673+
}
674+
});
663675
}
664676
}

0 commit comments

Comments
 (0)