Skip to content
This repository was archived by the owner on Jan 15, 2023. It is now read-only.
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
32 changes: 32 additions & 0 deletions dotnetcore2.0/run/MockBootstraps/DebuggerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace MockLambdaRuntime
{
internal static class DebuggerExtensions
{
/// <summary>
/// Tries to wait for the debugger to attach by inspecting <see cref="Debugger.IsAttached"/> property in a loop.
/// </summary>
/// <param name="queryInterval"><see cref="TimeSpan"/> representing the frequency of inspection.</param>
/// <param name="timeout"><see cref="TimeSpan"/> representing the timeout for the operation.</param>
/// <returns><c>True</c> if debugger was attached, false if timeout occured.</returns>
public static bool TryWaitForAttaching(TimeSpan queryInterval, TimeSpan timeout)
{
var stopwatch = Stopwatch.StartNew();

while (!Debugger.IsAttached)
{
if (stopwatch.Elapsed > timeout)
{
return false;
}

Task.Delay(queryInterval).Wait();
}

return true;
}
}
}
119 changes: 88 additions & 31 deletions dotnetcore2.0/run/MockBootstraps/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
Expand All @@ -9,50 +10,79 @@ namespace MockLambdaRuntime
{
class Program
{
private const string WaitForDebuggerFlag = "--debugger-spin-wait";
private const bool WaitForDebuggerFlagDefaultValue = false;

/// Task root of lambda task
static string lambdaTaskRoot = EnvHelper.GetOrDefault("LAMBDA_TASK_ROOT", "/var/task");

private static readonly TimeSpan _debuggerStatusQueryInterval = TimeSpan.FromMilliseconds(50);
private static readonly TimeSpan _debuggerStatusQueryTimeout = TimeSpan.FromMinutes(10);

/// Program entry point
static void Main(string[] args)
{
AssemblyLoadContext.Default.Resolving += OnAssemblyResolving;

var handler = GetFunctionHandler(args);
var body = GetEventBody(args);

var lambdaContext = new MockLambdaContext(handler, body);

var userCodeLoader = new UserCodeLoader(handler, InternalLogger.NO_OP_LOGGER);
userCodeLoader.Init(Console.Error.WriteLine);

var lambdaContextInternal = new LambdaContextInternal(lambdaContext.RemainingTime,
LogAction, new Lazy<CognitoClientContextInternal>(),
lambdaContext.RequestId,
new Lazy<string>(lambdaContext.Arn),
new Lazy<string>(string.Empty),
new Lazy<string>(string.Empty),
Environment.GetEnvironmentVariables());

Exception lambdaException = null;

LogRequestStart(lambdaContext);
try
{
userCodeLoader.Invoke(lambdaContext.InputStream, lambdaContext.OutputStream, lambdaContextInternal);
var shouldWaitForDebugger = GetShouldWaitForDebuggerFlag(args, out var positionalArgs);

var handler = GetFunctionHandler(positionalArgs);
var body = GetEventBody(positionalArgs);

if (shouldWaitForDebugger)
{
Console.Error.WriteLine("Waiting for the debugger to attach...");

if (!DebuggerExtensions.TryWaitForAttaching(
_debuggerStatusQueryInterval,
_debuggerStatusQueryTimeout))
{
Console.Error.WriteLine("Timeout. Proceeding without debugger.");
}
}

var lambdaContext = new MockLambdaContext(handler, body);

var userCodeLoader = new UserCodeLoader(handler, InternalLogger.NO_OP_LOGGER);
userCodeLoader.Init(Console.Error.WriteLine);

var lambdaContextInternal = new LambdaContextInternal(lambdaContext.RemainingTime,
LogAction, new Lazy<CognitoClientContextInternal>(),
lambdaContext.RequestId,
new Lazy<string>(lambdaContext.Arn),
new Lazy<string>(string.Empty),
new Lazy<string>(string.Empty),
Environment.GetEnvironmentVariables());

Exception lambdaException = null;

LogRequestStart(lambdaContext);
try
{
userCodeLoader.Invoke(lambdaContext.InputStream, lambdaContext.OutputStream, lambdaContextInternal);
}
catch (Exception ex)
{
lambdaException = ex;
}
LogRequestEnd(lambdaContext);

if (lambdaException == null)
{
Console.WriteLine(lambdaContext.OutputText);
}
else
{
Console.Error.WriteLine(lambdaException);
}
}
catch (Exception ex)
{
lambdaException = ex;
}
LogRequestEnd(lambdaContext);

if (lambdaException == null)
{
Console.WriteLine(lambdaContext.OutputText);
}
else
// Catch all unhandled exceptions from runtime, to prevent user from hanging on them while debugging
catch (Exception ex)
{
Console.Error.WriteLine(lambdaException);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this change

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added "\nUnhandled exception occured in runner:\n" to better constraint the exception origin for the user, while he is debugging. And outer try/catch block is used to prevent user from hanging on unhandled exception from the runnner program.

@mhart does it make sense?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so – I think it'd be better to match the behaviour of the rest of the runtimes w/ regards to error output. So errors should look the same as they do on Lambda itself.

If it doesn't currently do that (it might not by the looks of it) – then we should change it so that it does 👍

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't like the string, or how it behaves?

Copy link
Copy Markdown
Contributor Author

@ndobryanskyy ndobryanskyy Dec 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue

On this sample I've misused Lambda runner and got stuck on unhandled exception (default dotnet debugger behavior), that is why I have try/catch block around all the invocation.

This sample was run on unmodified version of lambci/lambda:dotnetcore2.1

@mhart Did I make my point clear?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about --debugger-spin-wait ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, happy with that! 👍

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated according to comments

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, I'm happy with that, feel free to update your SAM CLI PR 👍

I'll explore this PR more in the next couple of days, but I think the rest of it looks pretty good

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, take your time @mhart

Console.Error.WriteLine($"\nUnhandled exception occured in runner:\n{ex}");
}
}

Expand All @@ -68,6 +98,33 @@ private static void LogAction(string text)
Console.Error.WriteLine(text);
}

/// <summary>
/// Extracts "waitForDebugger" flag from args. Returns other unprocessed arguments.
/// </summary>
/// <param name="args">Args to look through</param>
/// <param name="unprocessed">Arguments except for the "waitForDebugger" ones</param>
/// <returns>"waitForDebugger" flag value</returns>
private static bool GetShouldWaitForDebuggerFlag(string[] args, out string[] unprocessed)
{
var flagValue = WaitForDebuggerFlagDefaultValue;

var unprocessedList = new List<string>();

foreach (var argument in args)
{
if (argument == WaitForDebuggerFlag)
{
flagValue = true;
continue;
}

unprocessedList.Add(argument);
}

unprocessed = unprocessedList.ToArray();
return flagValue;
}

static void LogRequestStart(MockLambdaContext context)
{
Console.Error.WriteLine($"START RequestId: {context.RequestId} Version: {context.FunctionVersion}");
Expand Down
32 changes: 32 additions & 0 deletions dotnetcore2.1/run/MockBootstraps/DebuggerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace MockLambdaRuntime
{
internal static class DebuggerExtensions
{
/// <summary>
/// Tries to wait for the debugger to attach by inspecting <see cref="Debugger.IsAttached"/> property in a loop.
/// </summary>
/// <param name="queryInterval"><see cref="TimeSpan"/> representing the frequency of inspection.</param>
/// <param name="timeout"><see cref="TimeSpan"/> representing the timeout for the operation.</param>
/// <returns><c>True</c> if debugger was attached, false if timeout occured.</returns>
public static bool TryWaitForAttaching(TimeSpan queryInterval, TimeSpan timeout)
{
var stopwatch = Stopwatch.StartNew();

while (!Debugger.IsAttached)
{
if (stopwatch.Elapsed > timeout)
{
return false;
}

Task.Delay(queryInterval).Wait();
}

return true;
}
}
}
119 changes: 88 additions & 31 deletions dotnetcore2.1/run/MockBootstraps/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
Expand All @@ -9,50 +10,79 @@ namespace MockLambdaRuntime
{
class Program
{
private const string WaitForDebuggerFlag = "--debugger-spin-wait";
private const bool WaitForDebuggerFlagDefaultValue = false;

/// Task root of lambda task
static string lambdaTaskRoot = EnvHelper.GetOrDefault("LAMBDA_TASK_ROOT", "/var/task");

private static readonly TimeSpan _debuggerStatusQueryInterval = TimeSpan.FromMilliseconds(50);
private static readonly TimeSpan _debuggerStatusQueryTimeout = TimeSpan.FromMinutes(10);

/// Program entry point
static void Main(string[] args)
{
AssemblyLoadContext.Default.Resolving += OnAssemblyResolving;

var handler = GetFunctionHandler(args);
var body = GetEventBody(args);

var lambdaContext = new MockLambdaContext(handler, body);

var userCodeLoader = new UserCodeLoader(handler, InternalLogger.NO_OP_LOGGER);
userCodeLoader.Init(Console.Error.WriteLine);

var lambdaContextInternal = new LambdaContextInternal(lambdaContext.RemainingTime,
LogAction, new Lazy<CognitoClientContextInternal>(),
lambdaContext.RequestId,
new Lazy<string>(lambdaContext.Arn),
new Lazy<string>(string.Empty),
new Lazy<string>(string.Empty),
Environment.GetEnvironmentVariables());

Exception lambdaException = null;

LogRequestStart(lambdaContext);
try
{
userCodeLoader.Invoke(lambdaContext.InputStream, lambdaContext.OutputStream, lambdaContextInternal);
var shouldWaitForDebugger = GetShouldWaitForDebuggerFlag(args, out var positionalArgs);

var handler = GetFunctionHandler(positionalArgs);
var body = GetEventBody(positionalArgs);

if (shouldWaitForDebugger)
{
Console.Error.WriteLine("Waiting for the debugger to attach...");

if (!DebuggerExtensions.TryWaitForAttaching(
_debuggerStatusQueryInterval,
_debuggerStatusQueryTimeout))
{
Console.Error.WriteLine("Timeout. Proceeding without debugger.");
}
}

var lambdaContext = new MockLambdaContext(handler, body);

var userCodeLoader = new UserCodeLoader(handler, InternalLogger.NO_OP_LOGGER);
userCodeLoader.Init(Console.Error.WriteLine);

var lambdaContextInternal = new LambdaContextInternal(lambdaContext.RemainingTime,
LogAction, new Lazy<CognitoClientContextInternal>(),
lambdaContext.RequestId,
new Lazy<string>(lambdaContext.Arn),
new Lazy<string>(string.Empty),
new Lazy<string>(string.Empty),
Environment.GetEnvironmentVariables());

Exception lambdaException = null;

LogRequestStart(lambdaContext);
try
{
userCodeLoader.Invoke(lambdaContext.InputStream, lambdaContext.OutputStream, lambdaContextInternal);
}
catch (Exception ex)
{
lambdaException = ex;
}
LogRequestEnd(lambdaContext);

if (lambdaException == null)
{
Console.WriteLine(lambdaContext.OutputText);
}
else
{
Console.Error.WriteLine(lambdaException);
}
}
catch (Exception ex)
{
lambdaException = ex;
}
LogRequestEnd(lambdaContext);

if (lambdaException == null)
{
Console.WriteLine(lambdaContext.OutputText);
}
else
// Catch all unhandled exceptions from runtime, to prevent user from hanging on them while debugging
catch (Exception ex)
{
Console.Error.WriteLine(lambdaException);
Console.Error.WriteLine($"\nUnhandled exception occured in runner:\n{ex}");
}
}

Expand All @@ -68,6 +98,33 @@ private static void LogAction(string text)
Console.Error.WriteLine(text);
}

/// <summary>
/// Extracts "waitForDebugger" flag from args. Returns other unprocessed arguments.
/// </summary>
/// <param name="args">Args to look through</param>
/// <param name="unprocessed">Arguments except for the "waitForDebugger" ones</param>
/// <returns>"waitForDebugger" flag value</returns>
private static bool GetShouldWaitForDebuggerFlag(string[] args, out string[] unprocessed)
{
var flagValue = WaitForDebuggerFlagDefaultValue;

var unprocessedList = new List<string>();

foreach (var argument in args)
{
if (argument == WaitForDebuggerFlag)
{
flagValue = true;
continue;
}

unprocessedList.Add(argument);
}

unprocessed = unprocessedList.ToArray();
return flagValue;
}

static void LogRequestStart(MockLambdaContext context)
{
Console.Error.WriteLine($"START RequestId: {context.RequestId} Version: {context.FunctionVersion}");
Expand Down