Run(FileInfo mainScriptFile, string[] argv, bool inspect, bool pauseDebuggerOnStart, IImmutableSet <string> inspectLoadSet, Func <CancellationToken, Task <string> > f, Process parentProcess, bool verbose) { var rootDir = mainScriptFile.Directory; Debug.Assert(rootDir != null); var settings = new V8Settings { EnableDebugging = inspect || pauseDebuggerOnStart || inspectLoadSet.Any(), AwaitDebuggerAndPauseOnStart = pauseDebuggerOnStart || inspectLoadSet.Any(), }; using (var engine = new V8JsEngine(settings)) { var scheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler; var console = ConsoleService.Default; void Load(string module) { var path = Path.Combine(rootDir.FullName, module); var source = File.ReadAllText(path); if (inspectLoadSet.Contains(source)) { source = "debugger;" + source; } engine.Execute(source, module); } using (var host = new Host(Load, console, scheduler)) { string FormatMessage(object sender, string message) { var senderName = sender is string s ? s : sender.GetType().Name; var formatted = $"{senderName}[{Thread.CurrentThread.ManagedThreadId}]: {message}"; return(formatted.FormatFoldedLines().TrimNewLineAtTail()); } void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args) => console.Error(FormatMessage(sender, args.Exception.ToString())); TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; var infoLog = !verbose ? null : new LogEventHandler((sender, message) => host.Console.Info(FormatMessage(sender, message))); var warnLog = !verbose ? null : new ErrorLogEventHandler((sender, e, message) => host.Console.Warn(FormatMessage(sender, e == null ? message : string.IsNullOrEmpty(message) ? e.ToString() : message + Environment.NewLine + e))); var errorLog = !verbose ? null : new ErrorLogEventHandler((sender, e, message) => host.Console.Error(FormatMessage(sender, string.IsNullOrEmpty(message) ? e.ToString() : message + Environment.NewLine + e))); foreach (var service in new ILogSource[] { host, host.Timer, host.Xhr }) { service.InfoLog = infoLog; service.WarnLog = warnLog; service.ErrorLog = errorLog; } var tasks = new List <NamedTask>(); void AddTask(NamedTask task) { lock (tasks) tasks.Add(task); } void RemoveTask(NamedTask task) { lock (tasks) tasks.Remove(task); } host.TaskStarting += (_, task) => AddTask(task); host.TaskFinishing += (_, task) => RemoveTask(task); if (verbose) { host.ServiceCreated += (_, service) => { service.InfoLog = infoLog; service.WarnLog = warnLog; service.ErrorLog = errorLog; }; } engine.EmbedHostObject("host", host); var initScript = GetManifestResourceStream("init.js", typeof(Program)).ReadAsText(); dynamic init = engine.Evaluate(initScript, "__init.js"); init(host, engine.Evaluate("this"), argv); if (settings.AwaitDebuggerAndPauseOnStart) { console.Warn(FormatMessage(nameof(Program), "Will wait for debugger to attach.")); } Load(mainScriptFile.Name); void Schedule(string name, Action <AsyncTaskControl> action) { var task = AsyncTask.Create(name, thisTask => { Exception error = null; try { action(thisTask); } catch (Exception e) { error = e; } RemoveTask(thisTask); switch (error) { case null: thisTask.FlagSuccess(); break; case OperationCanceledException e: thisTask.FlagCanceled(e.CancellationToken); break; default: errorLog?.Invoke(nameof(Program), error, null); thisTask.FlagError(error); break; } }); AddTask(task); task.Start(scheduler); } var parentProcessTask = parentProcess.AsTask(dispose: true, p => p.ExitCode, _ => null, exit => exit); while (true) { var readCommandTask = f(CancellationToken.None); if (parentProcessTask != null) { if (parentProcessTask == await Task.WhenAny(readCommandTask, parentProcessTask)) { break; } } else { await readCommandTask; } var command = (await readCommandTask)?.Trim(); if (command == null) { break; } if (command.Length == 0) { continue; } const string ondata = "ondata"; Schedule(ondata, delegate { infoLog?.Invoke(nameof(Program), "STDIN: " + command); engine.CallFunction(ondata, command); }); } const string onclose = "onclose"; Schedule(onclose, delegate { engine.Execute(@"if (typeof onclose === 'function') onclose();"); }); host.Timer.CancelAll(); host.Xhr.AbortAll(); infoLog?.Invoke(typeof(Program), "Shutting down..."); ImmutableArray <Task> tasksSnapshot; lock (tasks) tasksSnapshot = ImmutableArray.CreateRange(from t in tasks select t.Task); if (await tasksSnapshot.WhenAll(TimeSpan.FromSeconds(30))) { Debug.Assert(tasks.Count == 0); } else { warnLog?.Invoke(typeof(Program), null, "Timed-out waiting for all tasks to end for a graceful shutdown!"); } infoLog?.Invoke(typeof(Program), "Shutdown completed."); TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException; } } }
public Host(Action <string> loader, ConsoleService console, TaskScheduler taskScheduler) { _loader = loader ?? throw new ArgumentNullException(nameof(loader)); if (taskScheduler == null) { throw new ArgumentNullException(nameof(taskScheduler)); } var scheduler = _scheduler = Schedule; Console = console; Timer = new TimerService(scheduler); Xhr = new XhrService(scheduler); void Schedule(ILogSource source, string name, CancellationToken cancellationToken, Func <CancellationToken, Task> onSchedule, Action <Exception> onError, Action <OperationCanceledException> onCancel, Action onFinally) { var task = AsyncTask.Create(name, async thisTask => { try { await onSchedule(cancellationToken); TaskFinishing?.Invoke(this, thisTask); thisTask.FlagSuccess(); } catch (OperationCanceledException e) { try { source.WarnLog?.Invoke(source, e, name); onCancel?.Invoke(e); } finally { TaskFinishing?.Invoke(this, thisTask); thisTask.FlagCanceled(e.CancellationToken); } } catch (Exception e) { try { source.ErrorLog?.Invoke(source, e, name); onError?.Invoke(e); } finally { TaskFinishing?.Invoke(this, thisTask); thisTask.FlagError(e); } } finally { try { onFinally?.Invoke(); } finally { TaskFinished?.Invoke(this, thisTask); } } }); TaskStarting?.Invoke(this, task); task.Start(taskScheduler); } }