Merge pull request #1836 from Microsoft/wportnoy/ExceptionBreakpoints

use VSCode exception filters to expose DialogEvent
This commit is contained in:
Tom Laird-McConnell 2019-04-29 22:20:50 -07:00 коммит произвёл GitHub
Родитель dc529842fe e658be4efb
Коммит 74d2cb2d38
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 159 добавлений и 76 удалений

Просмотреть файл

@ -9,9 +9,11 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs.Adaptive.Rules;
using Microsoft.Bot.Builder.Dialogs.Adaptive.Selectors;
using Microsoft.Bot.Builder.Dialogs.Debugging;
using Microsoft.Bot.Builder.Expressions;
using Microsoft.Bot.Schema;
using Newtonsoft.Json.Linq;
using static Microsoft.Bot.Builder.Dialogs.Debugging.DebugSupport;
namespace Microsoft.Bot.Builder.Dialogs.Adaptive
{
@ -605,7 +607,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Adaptive
var context = planning.Context;
if (Recognizer != null)
{
await planning.DebuggerStepAsync(Recognizer, cancellationToken).ConfigureAwait(false);
await planning.DebuggerStepAsync(Recognizer, DialogContext.DialogEvents.OnRecognize, cancellationToken).ConfigureAwait(false);
var result = await Recognizer.RecognizeAsync(context, cancellationToken).ConfigureAwait(false);
// only allow one intent
var topIntent = result.GetTopScoringIntent();

Просмотреть файл

@ -369,11 +369,8 @@ namespace Microsoft.Bot.Builder.Dialogs.Adaptive
}
}
public class AdaptiveEvents
public class AdaptiveEvents : DialogContext.DialogEvents
{
public const string BeginDialog = "beginDialog";
public const string ConsultDialog = "consultDialog";
public const string CancelDialog = "cancelDialog";
public const string ActivityReceived = "activityReceived";
public const string RecognizedIntent = "recognizedIntent";
public const string UnknownIntent = "unknownIntent";

Просмотреть файл

@ -12,9 +12,9 @@ namespace Microsoft.Bot.Builder.Dialogs.Adaptive.Rules
{
public static partial class Extensions
{
public static async Task DebuggerStepAsync(this DialogContext context, IRule rule, DialogEvent dialogEvent, CancellationToken cancellationToken, [CallerMemberName]string memberName = null)
public static async Task DebuggerStepAsync(this DialogContext context, IRule rule, DialogEvent dialogEvent, CancellationToken cancellationToken)
{
var more = $"{memberName}-{dialogEvent.Name}";
var more = dialogEvent.Name;
await context.GetDebugger().StepAsync(context, rule, more, cancellationToken).ConfigureAwait(false);
}
}

Просмотреть файл

@ -94,6 +94,6 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
public override string ToString() => Name;
object ICodePoint.Evaluate(string expression) => DialogContext.State.GetValue<JToken>(expression);
object ICodePoint.Evaluate(string expression) => DialogContext.State.GetValue<object>(expression);
}
}

Просмотреть файл

@ -22,10 +22,11 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
private readonly IDataModel dataModel;
private readonly Source.IRegistry registry;
private readonly IBreakpoints breakpoints;
private readonly IEvents events;
private readonly Action terminate;
// lifetime scoped to IMiddleware.OnTurnAsync
private readonly ConcurrentDictionary<string, ThreadModel> threadByContext = new ConcurrentDictionary<string, ThreadModel>();
private readonly ConcurrentDictionary<string, ThreadModel> threadByTurnId = new ConcurrentDictionary<string, ThreadModel>();
private readonly Identifier<ThreadModel> threads = new Identifier<ThreadModel>();
private sealed class ThreadModel
@ -105,9 +106,10 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
private readonly Task task;
public DebugAdapter(int port, Source.IRegistry registry, IBreakpoints breakpoints, Action terminate, ICodeModel codeModel = null, IDataModel dataModel = null, ILogger logger = null, ICoercion coercion = null)
public DebugAdapter(int port, Source.IRegistry registry, IBreakpoints breakpoints, Action terminate, IEvents events = null,ICodeModel codeModel = null, IDataModel dataModel = null, ILogger logger = null, ICoercion coercion = null)
: base(logger)
{
this.events = events ?? new Events();
this.codeModel = codeModel ?? new CodeModel();
this.dataModel = dataModel ?? new DataModel(coercion ?? new Coercion());
this.registry = registry ?? throw new ArgumentNullException(nameof(registry));
@ -130,7 +132,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
{
var thread = new ThreadModel(turnContext, codeModel);
var threadId = threads.Add(thread);
threadByContext.TryAdd(getThreadId(turnContext), thread);
threadByTurnId.TryAdd(TurnIdFor(turnContext), thread);
try
{
thread.Run.Post(Phase.Started);
@ -145,12 +147,12 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
thread.Run.Post(Phase.Exited);
await UpdateThreadPhaseAsync(thread, null, cancellationToken).ConfigureAwait(false);
threadByContext.TryRemove(getThreadId(turnContext), out var ignored);
threadByTurnId.TryRemove(TurnIdFor(turnContext), out var ignored);
threads.Remove(thread);
}
}
private static string getThreadId(ITurnContext turnContext)
private static string TurnIdFor(ITurnContext turnContext)
{
return $"{turnContext.Activity.ChannelId}-{turnContext.Activity.Id}";
}
@ -163,14 +165,14 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
await UpdateBreakpointsAsync(cancellationToken).ConfigureAwait(false);
if (threadByContext.TryGetValue(getThreadId(context.Context), out ThreadModel thread))
if (threadByTurnId.TryGetValue(TurnIdFor(context.Context), out ThreadModel thread))
{
thread.LastContext = context;
thread.LastItem = item;
thread.LastMore = more;
var run = thread.Run;
if (breakpoints.IsBreakPoint(item))
if (breakpoints.IsBreakPoint(item) && events[more])
{
run.Post(Phase.Breakpoint);
}
@ -182,11 +184,13 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
// TODO: remove synchronous waits
UpdateThreadPhaseAsync(thread, item, cancellationToken).GetAwaiter().GetResult();
// while the stopped condition is true, atomically release the mutex
while (!(run.Phase == Phase.Started || run.Phase == Phase.Continue || run.Phase == Phase.Next))
{
Monitor.Wait(run.Gate);
}
// started is just use the signal to Visual Studio Code that there is a new thread
if (run.Phase == Phase.Started)
{
run.Phase = Phase.Continue;
@ -195,6 +199,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
// TODO: remove synchronous waits
UpdateThreadPhaseAsync(thread, item, cancellationToken).GetAwaiter().GetResult();
// allow one step to progress since next was requested
if (run.Phase == Phase.Next)
{
run.Phase = Phase.Step;
@ -272,7 +277,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
threadId,
text = description,
preserveFocusHint = false,
allThreadsStopped = true,
allThreadsStopped = false,
};
await SendAsync(Protocol.Event.From(NextSeq, "stopped", body), cancellationToken).ConfigureAwait(false);
@ -304,19 +309,26 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
private int sequence = 0;
private int NextSeq => Interlocked.Increment(ref sequence);
private Protocol.Capabilities MakeCapabilities()
{
// TODO: there is a "capabilities" event for dynamic updates, but exceptionBreakpointFilters does not seem to be dynamically updateable
return new Protocol.Capabilities()
{
supportsConfigurationDoneRequest = true,
supportsSetVariable = true,
supportsEvaluateForHovers = true,
supportsFunctionBreakpoints = true,
exceptionBreakpointFilters = this.events.Filters,
supportTerminateDebuggee = this.terminate != null,
supportsTerminateRequest = this.terminate != null,
};
}
private async Task<Protocol.Message> DispatchAsync(Protocol.Message message, CancellationToken cancellationToken)
{
if (message is Protocol.Request<Protocol.Initialize> initialize)
{
var body = new Protocol.Capabilities()
{
supportsConfigurationDoneRequest = true,
supportsSetVariable = true,
supportsEvaluateForHovers = true,
supportsFunctionBreakpoints = true,
supportTerminateDebuggee = this.terminate != null,
supportsTerminateRequest = this.terminate != null,
};
var body = MakeCapabilities();
var response = Protocol.Response.From(NextSeq, initialize, body);
await SendAsync(response, cancellationToken).ConfigureAwait(false);
return Protocol.Event.From(NextSeq, "initialized", new { });
@ -363,6 +375,13 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
return Protocol.Response.From(NextSeq, setFunctionBreakpoints, new { breakpoints });
}
else if (message is Protocol.Request<Protocol.SetExceptionBreakpoints> setExceptionBreakpoints)
{
var arguments = setExceptionBreakpoints.arguments;
this.events.Reset(arguments.filters);
return Protocol.Response.From(NextSeq, setExceptionBreakpoints, new { });
}
else if (message is Protocol.Request<Protocol.Threads> threads)
{
var body = new
@ -389,11 +408,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
if (this.registry.TryGetValue(frame.Item, out var range))
{
stackFrame.source = new Protocol.Source(range.Path);
stackFrame.line = range.Start.LineIndex;
stackFrame.column = range.Start.CharIndex;
stackFrame.endLine = range.After.LineIndex;
stackFrame.endColumn = range.After.CharIndex;
SourceMap.Assign(stackFrame, range);
}
stackFrames.Add(stackFrame);

Просмотреть файл

@ -15,12 +15,13 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
/// <param name="registry">IRegistry to use (default will be SourceMap())</param>
/// <param name="breakpoints">IBreakpoints to use (default will be SourceMap())</param>
/// <param name="terminate">Termination function (Default is Environment.Exit()</param>
/// <param name="events">IEvents to use (Default is Events)</param>
/// <param name="logger">ILogger to use (Default is NullLogger)</param>
/// <param name="codeModel">ICodeModel to use (default is internal implementation)</param>
/// <param name="dataModel">IDataModel to use (default is internal implementation)</param>
/// <param name="coercion">ICoercion to use (default is internal implementation)</param>
/// <returns></returns>
public static BotAdapter UseDebugger(this BotAdapter botAdapter, int port, Source.IRegistry registry = null, IBreakpoints breakpoints = null, Action terminate = null, ICodeModel codeModel = null, IDataModel dataModel = null, ILogger logger = null, ICoercion coercion = null)
public static BotAdapter UseDebugger(this BotAdapter botAdapter, int port, Source.IRegistry registry = null, IBreakpoints breakpoints = null, Action terminate = null, IEvents events = null, ICodeModel codeModel = null, IDataModel dataModel = null, ILogger logger = null, ICoercion coercion = null)
{
codeModel = codeModel ?? new CodeModel();
var sourceMap = new SourceMap(codeModel);
@ -29,6 +30,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
registry: registry ?? sourceMap,
breakpoints: breakpoints ?? registry as IBreakpoints ?? sourceMap,
terminate: terminate,
events: events,
codeModel: codeModel,
dataModel: dataModel,
logger: logger));

Просмотреть файл

@ -0,0 +1,65 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Bot.Builder.Dialogs.Debugging
{
public interface IEvents
{
Protocol.ExceptionBreakpointFilter[] Filters
{
get;
}
void Reset(IEnumerable<string> filters);
bool this[string filter]
{
get;
set;
}
}
public sealed class Events : IEvents
{
private readonly ConcurrentDictionary<string, bool> stateByFilter = new ConcurrentDictionary<string, bool>();
public Events(IEnumerable<string> filters = null)
{
if (filters == null)
{
filters = from field in typeof(DialogContext.DialogEvents).GetFields()
where field.FieldType == typeof(string)
select (string)field.GetValue(null);
filters = filters.ToArray();
}
foreach (var filter in filters)
{
this.stateByFilter.TryAdd(filter, true);
}
this.stateByFilter[DialogContext.DialogEvents.EndDialog] = false;
}
void IEvents.Reset(IEnumerable<string> filters)
{
var index = new HashSet<string>(filters);
foreach (var filter in stateByFilter.Keys)
{
stateByFilter[filter] = index.Contains(filter);
}
}
bool IEvents.this[string filter]
{
get => this.stateByFilter.TryGetValue(filter, out var state) ? state : false;
set => this.stateByFilter[filter] = value;
}
Protocol.ExceptionBreakpointFilter[] IEvents.Filters => this.stateByFilter.Select(kv => new Protocol.ExceptionBreakpointFilter() { label = kv.Key, filter = kv.Key, @default = kv.Value }).ToArray();
}
}

Просмотреть файл

@ -242,6 +242,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
case "initialize": return token.ToObject<Request<Initialize>>();
case "setBreakpoints": return token.ToObject<Request<SetBreakpoints>>();
case "setFunctionBreakpoints": return token.ToObject<Request<SetFunctionBreakpoints>>();
case "setExceptionBreakpoints": return token.ToObject<Request<SetExceptionBreakpoints>>();
case "configurationDone": return token.ToObject<Request<ConfigurationDone>>();
case "threads": return token.ToObject<Request<Threads>>();
case "stackTrace": return token.ToObject<Request<StackTrace>>();
@ -256,7 +257,6 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
case "stepOut": return token.ToObject<Request<Next>>();
case "terminate": return token.ToObject<Request<Terminate>>();
case "disconnect": return token.ToObject<Request<Disconnect>>();
case "setExceptionBreakpoints":
default: return token.ToObject<Request>();
}
default:

Просмотреть файл

@ -83,33 +83,31 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
private static bool PathEquals(string one, string two) =>
string.Equals(one, two, StringComparison.CurrentCultureIgnoreCase);
public static bool Equals(Protocol.Breakpoint breakpoint, Source.Range range) =>
(breakpoint.source == null && range == null)
|| (breakpoint.source.path == range.Path
&& breakpoint.line == range.Start.LineIndex
&& breakpoint.endLine == range.After.LineIndex
&& breakpoint.column == range.Start.CharIndex
&& breakpoint.endColumn == range.After.CharIndex);
public static bool Equals(Protocol.Range target, Source.Range source) =>
(target.source == null && source == null)
|| (PathEquals(target.source.path, source.Path)
&& target.line == source.Start.LineIndex
&& target.endLine == source.After.LineIndex
&& target.column == source.Start.CharIndex
&& target.endColumn == source.After.CharIndex);
public static void Assign(Protocol.Breakpoint breakpoint, Source.Range range)
public static void Assign(Protocol.Range target, Source.Range source)
{
if (range != null)
if (source != null)
{
breakpoint.verified = true;
breakpoint.source = new Protocol.Source(range.Path);
breakpoint.line = range.Start.LineIndex;
breakpoint.endLine = range.After.LineIndex;
breakpoint.column = range.Start.CharIndex;
breakpoint.endColumn = range.After.CharIndex;
target.source = new Protocol.Source(source.Path);
target.line = source.Start.LineIndex;
target.endLine = source.After.LineIndex;
target.column = source.Start.CharIndex;
target.endColumn = source.After.CharIndex;
}
else
{
breakpoint.verified = false;
breakpoint.source = null;
breakpoint.line = null;
breakpoint.endLine = null;
breakpoint.column = null;
breakpoint.endColumn = null;
target.source = null;
target.line = null;
target.endLine = null;
target.column = null;
target.endColumn = null;
}
}
@ -123,6 +121,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
}
row.item = item;
row.Breakpoint.verified = source != null;
Assign(row.Breakpoint, source);
return true;
}

Просмотреть файл

@ -10,7 +10,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
{
public static partial class DebugSupport
{
public static Source.IRegistry SourceRegistry { get; set; } = NullRegistry.Instance;
public static IRegistry SourceRegistry { get; set; } = NullRegistry.Instance;
public interface IDebugger
{
@ -23,14 +23,14 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
public static IDebugger GetDebugger(this DialogContext context) =>
context.Context.GetDebugger();
public static async Task DebuggerStepAsync(this DialogContext context, IDialog dialog, CancellationToken cancellationToken, [CallerMemberName]string memberName = null)
public static async Task DebuggerStepAsync(this DialogContext context, IDialog dialog, string more, CancellationToken cancellationToken)
{
await context.GetDebugger().StepAsync(context, dialog, memberName, cancellationToken).ConfigureAwait(false);
await context.GetDebugger().StepAsync(context, dialog, more, cancellationToken).ConfigureAwait(false);
}
public static async Task DebuggerStepAsync(this DialogContext context, IRecognizer recognizer, CancellationToken cancellationToken, [CallerMemberName]string memberName = null)
public static async Task DebuggerStepAsync(this DialogContext context, IRecognizer recognizer, string more, CancellationToken cancellationToken)
{
await context.GetDebugger().StepAsync(context, recognizer, memberName, cancellationToken).ConfigureAwait(false);
await context.GetDebugger().StepAsync(context, recognizer, more, cancellationToken).ConfigureAwait(false);
}
private sealed class NullDebugger : IDebugger
@ -43,11 +43,6 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
Task IDebugger.StepAsync(DialogContext context, object item, string more, CancellationToken cancellationToken)
{
if (item is Dialog)
{
System.Diagnostics.Trace.TraceInformation($"{item.GetType().Name} {((Dialog)item).Id} {more}");
}
return Task.CompletedTask;
}
}

Просмотреть файл

@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs.Debugging;
using Microsoft.Extensions.Configuration;
using static Microsoft.Bot.Builder.Dialogs.Debugging.DebugSupport;
namespace Microsoft.Bot.Builder.Dialogs
{
@ -241,7 +242,7 @@ namespace Microsoft.Bot.Builder.Dialogs
State.SetValue("dialog.result", stateBindings);
// Call dialogs BeginAsync() method.
await DebuggerStepAsync(dialog, cancellationToken).ConfigureAwait(false);
await this.DebuggerStepAsync(dialog, DialogEvents.BeginDialog, cancellationToken).ConfigureAwait(false);
return await dialog.BeginDialogAsync(this, options: options, cancellationToken: cancellationToken).ConfigureAwait(false);
}
@ -327,7 +328,7 @@ namespace Microsoft.Bot.Builder.Dialogs
}
// Return result to previous dialog
await DebuggerStepAsync(dialog, cancellationToken).ConfigureAwait(false);
await this.DebuggerStepAsync(dialog, DialogEvents.ResumeDialog, cancellationToken).ConfigureAwait(false);
return await dialog.ResumeDialogAsync(this, DialogReason.EndCalled, result, cancellationToken).ConfigureAwait(false);
}
else
@ -343,10 +344,10 @@ namespace Microsoft.Bot.Builder.Dialogs
/// <returns>The dialog context.</returns>
public Task<DialogTurnResult> CancelAllDialogsAsync(CancellationToken cancellationToken)
{
return CancelAllDialogsAsync("cancelDialog", null, cancellationToken);
return CancelAllDialogsAsync(DialogEvents.CancelDialog, null, cancellationToken);
}
public async Task<DialogTurnResult> CancelAllDialogsAsync(string eventName = "cancelDialog", object eventValue = null, CancellationToken cancellationToken = default(CancellationToken))
public async Task<DialogTurnResult> CancelAllDialogsAsync(string eventName = DialogEvents.CancelDialog, object eventValue = null, CancellationToken cancellationToken = default(CancellationToken))
{
if (eventValue is CancellationToken)
{
@ -430,7 +431,7 @@ namespace Microsoft.Bot.Builder.Dialogs
public async Task RepromptDialogAsync(CancellationToken cancellationToken = default(CancellationToken))
{
// Emit 'RepromptDialog' event
var handled = await EmitEventAsync("repromptDialog", null, false, cancellationToken).ConfigureAwait(false);
var handled = await EmitEventAsync(DialogEvents.RepromptDialog, null, false, cancellationToken).ConfigureAwait(false);
if (!handled)
{
@ -445,7 +446,7 @@ namespace Microsoft.Bot.Builder.Dialogs
}
// Ask dialog to re-prompt if supported
await DebuggerStepAsync(dialog, cancellationToken).ConfigureAwait(false);
await this.DebuggerStepAsync(dialog, DialogEvents.RepromptDialog, cancellationToken).ConfigureAwait(false);
await dialog.RepromptDialogAsync(Context, ActiveDialog, cancellationToken).ConfigureAwait(false);
}
}
@ -475,6 +476,17 @@ namespace Microsoft.Bot.Builder.Dialogs
return null;
}
public class DialogEvents
{
public const string BeginDialog = "beginDialog";
public const string ResumeDialog = "resumeDialog";
public const string RepromptDialog = "repromptDialog";
public const string ConsultDialog = "consultDialog";
public const string CancelDialog = "cancelDialog";
public const string EndDialog = "endDialog";
public const string OnRecognize = "onRecognize";
}
/// <summary>
/// Searches for a dialog with a given ID.
/// Emits a named event for the current dialog, or someone who started it, to handle.
@ -508,6 +520,7 @@ namespace Microsoft.Bot.Builder.Dialogs
if (dialog != null)
{
await this.DebuggerStepAsync(dialog, name, cancellationToken).ConfigureAwait(false);
handled = await dialog.OnDialogEventAsync(dc, dialogEvent, cancellationToken).ConfigureAwait(false);
}
}
@ -548,7 +561,7 @@ namespace Microsoft.Bot.Builder.Dialogs
}
// Consult dialog
await DebuggerStepAsync(dialog, cancellationToken).ConfigureAwait(false);
await this.DebuggerStepAsync(dialog, DialogEvents.ConsultDialog, cancellationToken).ConfigureAwait(false);
return await dialog.ConsultDialogAsync(this, cancellationToken).ConfigureAwait(false);
}
else
@ -557,11 +570,6 @@ namespace Microsoft.Bot.Builder.Dialogs
}
}
public async Task DebuggerStepAsync(object item, CancellationToken cancellationToken, [CallerMemberName]string memberName = null)
{
await Context.GetDebugger().StepAsync(this, item, memberName, cancellationToken).ConfigureAwait(false);
}
private async Task EndActiveDialogAsync(DialogReason reason, object result = null, CancellationToken cancellationToken = default(CancellationToken))
{
if (result is CancellationToken)
@ -577,7 +585,7 @@ namespace Microsoft.Bot.Builder.Dialogs
if (dialog != null)
{
// Notify dialog of end
await DebuggerStepAsync(dialog, cancellationToken).ConfigureAwait(false);
await this.DebuggerStepAsync(dialog, DialogEvents.EndDialog, cancellationToken).ConfigureAwait(false);
await dialog.EndDialogAsync(Context, instance, reason, cancellationToken).ConfigureAwait(false);
}