Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions src/OneScript.DebugProtocol/TcpServer/DefaultMessageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,9 @@ private void RunCommandsLoop()
}
catch (ChannelException e)
{
if (e.StopChannel)
{
// критичные исключения сразу должны завершать сервер
_serverStopped = true;
break;
}
// критичные исключения (например, соединение закрыто) завершают сервер,
// но подписчики узнают о них, чтобы закрыть свою сессию
var critical = e.StopChannel;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

var eventData = new CommunicationEventArgs
{
Expand All @@ -85,8 +82,10 @@ private void RunCommandsLoop()
// Считаем, что факап подписчика - его проблемы.
}

// свойство в исключении может быть уcтановлено в обработчике евента
_serverStopped = e.StopChannel;
// свойство в исключении может быть уcтановлено в обработчике евента,
// а обработчик мог и сам остановить сервер
if (critical || e.StopChannel)
_serverStopped = true;
}
catch (ObjectDisposedException)
{
Expand Down Expand Up @@ -147,7 +146,9 @@ public void Stop()

_serverStopped = true;

if (_messageThread?.IsAlive == true)
// Из обработчика сообщения свой поток не прерываем: цикл и так завершится,
// а прерывание сорвало бы ближайшее ожидание в самом обработчике
if (_messageThread?.IsAlive == true && _messageThread != Thread.CurrentThread)
{
_messageThread.Interrupt();
}
Expand Down
18 changes: 15 additions & 3 deletions src/OneScript.DebugProtocol/TcpServer/JsonDtoChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ public sealed class JsonDtoChannel : IMessageChannel
private readonly IDebuggerClient _client;
private readonly Stream _dataStream;

private bool _enabled = true;
// Пишут и поток сообщений (ответы), и потоки скриптов (события остановки)
private readonly object _writeLock = new object();
private volatile bool _enabled = true;

public JsonDtoChannel(IDebuggerClient client)
{
Expand All @@ -34,9 +36,10 @@ public JsonDtoChannel(Stream dataStream)

public void Dispose()
{
// Без блокировки записи: закрытие потока как раз прерывает зависшую запись
_enabled = false;
_dataStream.Dispose();
_client?.Dispose();
_enabled = false;
}

public void Write(object data)
Expand All @@ -55,7 +58,11 @@ public void Write(object data)
writer.Write(contentBytes, 0, contentBytes.Length);

bufferedStream.Position = 0;
bufferedStream.CopyTo(_dataStream);
// Сообщение целиком, чтобы сообщения разных потоков не перемешались
lock (_writeLock)
{
bufferedStream.CopyTo(_dataStream);
}
}
}
}
Expand Down Expand Up @@ -85,6 +92,11 @@ public object Read()
}
}
}
catch (Exception ex) when (ex is IOException or ObjectDisposedException)
{
// Соединение закрыто или оборвалось: дальше читать нечего
throw new ChannelException("Channel is closed", true, ex);
}
catch (Exception ex)
{
throw new ChannelException("Channel read exception", ex);
Expand Down
14 changes: 13 additions & 1 deletion src/OneScript.DebugServices/DefaultDebugger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ This Source Code Form is subject to the terms of the
at http://mozilla.org/MPL/2.0/.
----------------------------------------------------------*/

using System.Threading;
using OneScript.DebugProtocol.Abstractions;
using OneScript.DebugProtocol.TcpServer;
using OneScript.DebugServices.Internal;
Expand All @@ -22,6 +23,9 @@ public class DefaultDebugger : IDebugger
// NB! должен быть согласован с файлом ProtocolVersions в адаптере
private const short SUPPORTED_FORMAT_VERSION = 4;

// Клиент, который подключился и молчит, не должен занимать прием подключений
private const int HANDSHAKE_TIMEOUT_MS = 5000;

private readonly IDebugServer _transport;
private IDebugSession _session;

Expand Down Expand Up @@ -60,7 +64,15 @@ private void TransportOnOnClientConnected(object sender, IDebuggerClient debugge
}

var dataStream = debuggerClient.GetDataStream();
if (FormatReconcileUtils.CheckReconcileRequest(dataStream))
if (dataStream.CanTimeout)
dataStream.ReadTimeout = HANDSHAKE_TIMEOUT_MS;

var isReconcileRequest = FormatReconcileUtils.CheckReconcileRequest(dataStream);

if (dataStream.CanTimeout)
dataStream.ReadTimeout = Timeout.Infinite;

if (isReconcileRequest)
{
// Да, это наш фейковый заголовок
FormatReconcileUtils.WriteReconcileResponse(dataStream, JSON_FORMAT_MARKER, SUPPORTED_FORMAT_VERSION);
Expand Down
31 changes: 30 additions & 1 deletion src/OneScript.DebugServices/Internal/DebugSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace OneScript.DebugServices.Internal
internal class DebugSession : IDebugSession
{
private bool _isStarted;
private int _disposed;
private readonly ThreadManager _threadManager;
private readonly TcpEventCallbackChannel _callbackChannel;
private readonly DispatchingService<IDebuggerService> _messageServer;
Expand All @@ -39,6 +40,7 @@ public DebugSession(IDebuggerClient connectedClient, bool attachMode)
};

ipcServer.OnError += CommunicationError;
ipcServer.DataReceived += ConnectionLost;

BreakpointManager = new DefaultBreakpointManager();
_threadManager = new ThreadManager();
Expand All @@ -59,6 +61,13 @@ private void CommunicationError(object sender, CommunicationEventArgs e)
Dispose();
}

private void ConnectionLost(object sender, CommunicationEventArgs e)
{
// IDE закрыла соединение без Disconnect или связь оборвалась
if (e.Exception?.StopChannel == true)
Dispose();
}

private void ThreadManagerOnThreadStopped(object sender, ThreadStoppedEventArgs e)
{
MachineWaitToken token;
Expand All @@ -71,19 +80,39 @@ private void ThreadManagerOnThreadStopped(object sender, ThreadStoppedEventArgs
return;
}

// Остановка внутри вычисления отладчика (watch вызвал метод с точкой останова):
// отладчик ждет результат и продолжить поток не сможет
if (token.IsRunningDebuggerWork)
return;

token.Reset();
try
{
_callbackChannel.ThreadStoppedEx(e.ThreadId, ConvertStopReason(e.StopReason), e.ErrorMessage);
}
catch
{
// Остановку не сообщили - поток не должен числиться остановленным
token.Set();
throw;
}

_callbackChannel.ThreadStoppedEx(e.ThreadId, ConvertStopReason(e.StopReason), e.ErrorMessage);
token.Wait();
}

public void Dispose()
{
if (Interlocked.Exchange(ref _disposed, 1) != 0)
return;

_threadManager.ThreadStopped -= ThreadManagerOnThreadStopped;
_threadManager.Dispose();
_messageServer.Stop();
IsActive = false;

// IDE отключилась до команды запуска: выполняемся без отладчика, а не ждем вечно
_startEvent.Set();

OnClose?.Invoke(this);
}

Expand Down
35 changes: 27 additions & 8 deletions src/OneScript.DebugServices/Internal/DebuggerServiceImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This Source Code Form is subject to the terms of the
using OneScript.Contexts;
using OneScript.DebugProtocol;
using OneScript.Language;
using OneScript.Values;
using ScriptEngine.Machine;
using StackFrame = OneScript.DebugProtocol.StackFrame;
using Variable = OneScript.DebugProtocol.Variable;
Expand Down Expand Up @@ -101,7 +102,7 @@ public Breakpoint[] SetMachineBreakpoints(Breakpoint[] breaksToSet)

public StackFrame[] GetStackFrames(int threadId)
{
var machine = _threadManager.GetTokenForThread(threadId).Machine;
var machine = GetMachine(threadId);
var frames = machine.GetExecutionFrames();
var result = new StackFrame[frames.Count];
int index = 0;
Expand All @@ -121,7 +122,26 @@ public StackFrame[] GetStackFrames(int threadId)

private MachineInstance GetMachine(int threadId)
{
return _threadManager.GetTokenForThread(threadId).Machine;
return GetStoppedToken(threadId).Machine;
}

// Состояние машины можно читать и менять, только пока ее поток стоит. Например, после
// «Продолжить» IDE еще запрашивает стек потока, событие остановки которого не успела обработать
private MachineWaitToken GetStoppedToken(int threadId)
{
var token = _threadManager.GetTokenForThread(threadId);
if (!token.IsStopped)
throw new InvalidOperationException($"Thread {threadId} is running");

return token;
}

// Выражение может вызвать методы сценария: они выполняются в потоке машины,
// с его блокировками, а не в потоке отладчика
private BslValue EvaluateOnStoppedThread(int threadId, int frameIndex, string expression)
{
var token = GetStoppedToken(threadId);
return token.RunOnStoppedThread(() => token.Machine.EvaluateInFrame(expression, frameIndex));
}

public Variable[] GetVariables(int threadId, int frameIndex, int[] path)
Expand Down Expand Up @@ -160,7 +180,7 @@ public Variable[] GetEvaluatedVariables(string expression, int threadId, int fra

try
{
value = GetMachine(threadId).EvaluateInFrame(expression, frameIndex);
value = EvaluateOnStoppedThread(threadId, frameIndex, expression);
}
catch (Exception e)
{
Expand All @@ -182,8 +202,7 @@ public Variable Evaluate(int threadId, int contextFrame, string expression)
{
try
{
var value = GetMachine(threadId)
.EvaluateInFrame(expression, contextFrame);
var value = EvaluateOnStoppedThread(threadId, contextFrame, expression);

var variable = _visualizer.GetVariable(MachineVariable.Create(value, "$evalResult"));
return variable;
Expand All @@ -197,21 +216,21 @@ public Variable Evaluate(int threadId, int contextFrame, string expression)

public void Next(int threadId)
{
var t = _threadManager.GetTokenForThread(threadId);
var t = GetStoppedToken(threadId);
t.Machine.StepOver();
t.Set();
}

public void StepIn(int threadId)
{
var t = _threadManager.GetTokenForThread(threadId);
var t = GetStoppedToken(threadId);
t.Machine.StepIn();
t.Set();
}

public void StepOut(int threadId)
{
var t = _threadManager.GetTokenForThread(threadId);
var t = GetStoppedToken(threadId);
t.Machine.StepOut();
t.Set();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ This Source Code Form is subject to the terms of the
at http://mozilla.org/MPL/2.0/.
----------------------------------------------------------*/

using System;
using System.IO;
using OneScript.DebugProtocol;
using OneScript.DebugProtocol.Abstractions;
Expand Down Expand Up @@ -52,6 +53,10 @@ private void Write(RpcCall dto)
{
// Ignore
}
catch (ObjectDisposedException)
{
// Канал закрыли, пока поток скрипта отправлял событие
}
}
}
}
Loading