diff --git a/dotnetcore2.0/run/MockBootstraps/DebuggerExtensions.cs b/dotnetcore2.0/run/MockBootstraps/DebuggerExtensions.cs new file mode 100644 index 00000000..7c15ea8a --- /dev/null +++ b/dotnetcore2.0/run/MockBootstraps/DebuggerExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace MockLambdaRuntime +{ + internal static class DebuggerExtensions + { + /// + /// Tries to wait for the debugger to attach by inspecting property in a loop. + /// + /// representing the frequency of inspection. + /// representing the timeout for the operation. + /// True if debugger was attached, false if timeout occured. + 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; + } + } +} diff --git a/dotnetcore2.0/run/MockBootstraps/Program.cs b/dotnetcore2.0/run/MockBootstraps/Program.cs index ebbe0933..da0fcd1f 100644 --- a/dotnetcore2.0/run/MockBootstraps/Program.cs +++ b/dotnetcore2.0/run/MockBootstraps/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.Loader; @@ -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(), - lambdaContext.RequestId, - new Lazy(lambdaContext.Arn), - new Lazy(string.Empty), - new Lazy(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(), + lambdaContext.RequestId, + new Lazy(lambdaContext.Arn), + new Lazy(string.Empty), + new Lazy(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}"); } } @@ -68,6 +98,33 @@ private static void LogAction(string text) Console.Error.WriteLine(text); } + /// + /// Extracts "waitForDebugger" flag from args. Returns other unprocessed arguments. + /// + /// Args to look through + /// Arguments except for the "waitForDebugger" ones + /// "waitForDebugger" flag value + private static bool GetShouldWaitForDebuggerFlag(string[] args, out string[] unprocessed) + { + var flagValue = WaitForDebuggerFlagDefaultValue; + + var unprocessedList = new List(); + + 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}"); diff --git a/dotnetcore2.1/run/MockBootstraps/DebuggerExtensions.cs b/dotnetcore2.1/run/MockBootstraps/DebuggerExtensions.cs new file mode 100644 index 00000000..7c15ea8a --- /dev/null +++ b/dotnetcore2.1/run/MockBootstraps/DebuggerExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace MockLambdaRuntime +{ + internal static class DebuggerExtensions + { + /// + /// Tries to wait for the debugger to attach by inspecting property in a loop. + /// + /// representing the frequency of inspection. + /// representing the timeout for the operation. + /// True if debugger was attached, false if timeout occured. + 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; + } + } +} diff --git a/dotnetcore2.1/run/MockBootstraps/Program.cs b/dotnetcore2.1/run/MockBootstraps/Program.cs index ebbe0933..da0fcd1f 100644 --- a/dotnetcore2.1/run/MockBootstraps/Program.cs +++ b/dotnetcore2.1/run/MockBootstraps/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.Loader; @@ -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(), - lambdaContext.RequestId, - new Lazy(lambdaContext.Arn), - new Lazy(string.Empty), - new Lazy(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(), + lambdaContext.RequestId, + new Lazy(lambdaContext.Arn), + new Lazy(string.Empty), + new Lazy(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}"); } } @@ -68,6 +98,33 @@ private static void LogAction(string text) Console.Error.WriteLine(text); } + /// + /// Extracts "waitForDebugger" flag from args. Returns other unprocessed arguments. + /// + /// Args to look through + /// Arguments except for the "waitForDebugger" ones + /// "waitForDebugger" flag value + private static bool GetShouldWaitForDebuggerFlag(string[] args, out string[] unprocessed) + { + var flagValue = WaitForDebuggerFlagDefaultValue; + + var unprocessedList = new List(); + + 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}");