Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debugger: Clean up on Disconnection and Identifier Thread Safety #3629

Merged
merged 19 commits into from
Apr 2, 2020
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ bool ISourceMap.TryGetValue(object item, out SourceRange range)
}
}

void IBreakpoints.Clear()
{
lock (gate)
{
this.rows.Clear();
this.items.Clear();
this.dirty = true;
}
}

IReadOnlyList<Protocol.Breakpoint> IBreakpoints.SetBreakpoints(Protocol.Source source, IReadOnlyList<Protocol.SourceBreakpoint> sourceBreakpoints)
{
lock (gate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ public sealed class DialogDebugAdapter : DebugTransport, IMiddleware, IDialogDeb

// lifetime scoped to IMiddleware.OnTurnAsync
private readonly ConcurrentDictionary<string, ThreadModel> threadByTurnId = new ConcurrentDictionary<string, ThreadModel>();
private readonly IIdentifier<ThreadModel> threads = new Identifier<ThreadModel>();
private readonly IIdentifier<ThreadModel> threads = new Identifier<ThreadModel>().WithMutex();

// https://en.wikipedia.org/wiki/Region-based_memory_management
private readonly IIdentifier<ArenaModel> arenas = new Identifier<ArenaModel>();
private readonly IIdentifier<ArenaModel> arenas = new Identifier<ArenaModel>().WithMutex();
private readonly OutputModel output = new OutputModel();

private readonly Task task;
Expand Down Expand Up @@ -270,7 +270,7 @@ protected override async Task AcceptAsync(CancellationToken cancellationToken)
{
this.Logger.LogError(error, error.Message);

this.ContinueAllThreads();
this.ResetOnDisconnect();

throw;
}
Expand Down Expand Up @@ -315,6 +315,16 @@ private void DecodeFrame(ulong frameCode, out ThreadModel thread, out ICodePoint
frame = thread.FrameCodes[valueCode];
}

private void ResetOnDisconnect()
{
// consider resetting this.events filter enabled state to defaults from constructor

this.options = new Protocol.LaunchAttach();
this.breakpoints.Clear();
this.output.ValueCodes.Clear();
ContinueAllThreads();
}

private void ContinueAllThreads()
{
var errors = new List<Exception>();
Expand Down Expand Up @@ -647,7 +657,7 @@ private Protocol.Capabilities MakeCapabilities()
}
else
{
this.ContinueAllThreads();
this.ResetOnDisconnect();
}

return Protocol.Response.From(NextSeq, disconnect, new { });
Expand Down Expand Up @@ -702,15 +712,15 @@ protected ArenaModel(IIdentifier<object> valueCodes)
private sealed class OutputModel : ArenaModel
{
public OutputModel()
: base(new IdentifierCache<object>(new Identifier<object>(), count: 25))
: base(new Identifier<object>().WithCache(count: 25).WithMutex())
{
}
}

private sealed class ThreadModel : ArenaModel
{
public ThreadModel(ITurnContext turnContext, ICodeModel codeModel)
: base(new Identifier<object>())
: base(new Identifier<object>().WithMutex())
{
TurnContext = turnContext;
CodeModel = codeModel;
Expand Down Expand Up @@ -740,7 +750,7 @@ public IReadOnlyList<ICodePoint> Frames

public RunModel Run { get; } = new RunModel();

public IIdentifier<ICodePoint> FrameCodes { get; } = new Identifier<ICodePoint>();
public IIdentifier<ICodePoint> FrameCodes { get; } = new Identifier<ICodePoint>().WithMutex();

public DialogContext LastContext { get; private set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public interface IBreakpoints

object ItemFor(Protocol.Breakpoint breakpoint);

void Clear();

IReadOnlyList<Protocol.Breakpoint> SetBreakpoints(Protocol.Source source, IReadOnlyList<Protocol.SourceBreakpoint> sourceBreakpoints);

IReadOnlyList<Protocol.Breakpoint> SetBreakpoints(IReadOnlyList<Protocol.FunctionBreakpoint> functionBreakpoints);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ ulong this[T item]

bool TryGetValue(T item, out ulong code);

void Clear();

ulong Add(T item);

void Remove(T item);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ public IdentifierCache(IIdentifier<T> inner, int count)

bool IIdentifier<T>.TryGetValue(T item, out ulong code) => this.inner.TryGetValue(item, out code);

void IIdentifier<T>.Clear()
{
this.inner.Clear();
this.queue.Clear();
}

ulong IIdentifier<T>.Add(T item)
{
if (this.inner.TryGetValue(item, out var code))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Microsoft.Bot.Builder.Dialogs.Debugging
{
public static class IdentifierFactory
{
public static IIdentifier<T> WithCache<T>(this IIdentifier<T> identifier, int count)
=> new IdentifierCache<T>(identifier, count);

public static IIdentifier<T> WithMutex<T>(this IIdentifier<T> identifier)
=> new IdentifierMutex<T>(identifier);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Microsoft.Bot.Builder.Dialogs.Debugging
{
public sealed class IdentifierMutex<T> : IIdentifier<T>
{
private readonly IIdentifier<T> inner;
private readonly object gate = new object();

public IdentifierMutex(IIdentifier<T> inner)
{
this.inner = inner ?? throw new ArgumentNullException(nameof(inner));
}

IEnumerable<T> IIdentifier<T>.Items
{
get
{
lock (this.gate)
{
return this.inner.Items.ToList();
}
}
}

T IIdentifier<T>.this[ulong code]
{
get
{
lock (this.gate)
{
return this.inner[code];
}
}
}

ulong IIdentifier<T>.this[T item]
{
get
{
lock (this.gate)
{
return this.inner[item];
}
}
}

bool IIdentifier<T>.TryGetValue(ulong code, out T item)
{
lock (this.gate)
{
return this.inner.TryGetValue(code, out item);
}
}

bool IIdentifier<T>.TryGetValue(T item, out ulong code)
{
lock (this.gate)
{
return this.inner.TryGetValue(item, out code);
}
}

ulong IIdentifier<T>.Add(T item)
{
lock (this.gate)
{
return this.inner.Add(item);
}
}

void IIdentifier<T>.Remove(T item)
{
lock (this.gate)
{
this.inner.Remove(item);
}
}

void IIdentifier<T>.Clear()
{
lock (this.gate)
{
this.inner.Clear();
}
}

IEnumerator<KeyValuePair<ulong, T>> IEnumerable<KeyValuePair<ulong, T>>.GetEnumerator()
{
lock (this.gate)
{
return this.inner.ToList().GetEnumerator();
}
}

IEnumerator IEnumerable.GetEnumerator()
{
lock (this.gate)
{
return this.inner.ToList().GetEnumerator();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,92 +21,54 @@ public sealed class Identifier<T> : IIdentifier<T>
{
private readonly Dictionary<T, ulong> codeByItem = new Dictionary<T, ulong>(ReferenceEquality<T>.Instance);
private readonly Dictionary<ulong, T> itemByCode = new Dictionary<ulong, T>();
private readonly object gate = new object();
private ulong last = 0;

IEnumerable<T> IIdentifier<T>.Items
{
get
{
lock (gate)
{
return this.itemByCode.Values.ToArray();
}
}
}
=> this.itemByCode.Values.ToArray();

T IIdentifier<T>.this[ulong code]
{
get
{
lock (gate)
{
return this.itemByCode[code];
}
}
}
=> this.itemByCode[code];

ulong IIdentifier<T>.this[T item]
{
get
{
lock (gate)
{
return this.codeByItem[item];
}
}
}
=> this.codeByItem[item];

bool IIdentifier<T>.TryGetValue(ulong code, out T item)
{
lock (gate)
{
return this.itemByCode.TryGetValue(code, out item);
}
}
=> this.itemByCode.TryGetValue(code, out item);

bool IIdentifier<T>.TryGetValue(T item, out ulong code)
=> this.codeByItem.TryGetValue(item, out code);

void IIdentifier<T>.Clear()
{
lock (gate)
{
return this.codeByItem.TryGetValue(item, out code);
}
// do not reset the last code to avoid any risk of https://en.wikipedia.org/wiki/ABA_problem
this.itemByCode.Clear();
this.codeByItem.Clear();
}

ulong IIdentifier<T>.Add(T item)
{
lock (gate)
if (!this.codeByItem.TryGetValue(item, out var code))
{
if (!this.codeByItem.TryGetValue(item, out var code))
{
// avoid false values
code = ++last;
this.codeByItem.Add(item, code);
this.itemByCode.Add(code, item);
}

return code;
// avoid false values
code = ++last;
this.codeByItem.Add(item, code);
this.itemByCode.Add(code, item);
}

return code;
}

void IIdentifier<T>.Remove(T item)
{
lock (gate)
{
var code = this.codeByItem[item];
this.itemByCode.Remove(code);
this.codeByItem.Remove(item);
}
var code = this.codeByItem[item];
this.itemByCode.Remove(code);
this.codeByItem.Remove(item);
}

IEnumerator<KeyValuePair<ulong, T>> IEnumerable<KeyValuePair<ulong, T>>.GetEnumerator()
{
lock (gate)
{
return this.itemByCode.ToList().GetEnumerator();
}
}
=> this.itemByCode.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<KeyValuePair<ulong, T>>)this).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> this.itemByCode.GetEnumerator();
}
}