Skip to content

Commit

Permalink
Minor cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
tareqimbasher committed Nov 20, 2023
1 parent 96210dc commit 22fbc18
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 50 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Additional requirements only if you plan to create and use database connections:

[Download](https://github.com/tareqimbasher/NetPad/releases) the latest version for free!

On **macOS** see [this](https://github.com/tareqimbasher/NetPad/wiki/Troubleshooting#netpad-is-damaged-and-cant-be-opened-you-should-move-it-to-the-trash) if you have trouble opening NetPad.

## Updates

NetPad checks for updates on startup and will let you know when a new version is available.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class Window extends WindowBase {

let tabIndex = this.tabs.findIndex(t => t.route === this.startupOptions.get("tab"));
if (tabIndex < 0)
tabIndex = 3;
tabIndex = 0;

this.selectedTab = this.tabs[tabIndex];
this.editableSettings = this.settings.clone();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace NetPad.Services;

/// <summary>
/// An <see cref="IOutputWriter{TOutput}"/> that coordinates sending of script output messages emitted by ScriptEnvironments to IPC clients.
/// It employs queueing and max output limits to prevent over-flooding IPC with too much data.
/// </summary>
public sealed record ScriptEnvironmentIpcOutputWriter : IOutputWriter<object>, IDisposable
{
Expand All @@ -31,7 +32,7 @@ public sealed record ScriptEnvironmentIpcOutputWriter : IOutputWriter<object>, I
private readonly Timer _sendMessageQueueTimer;
private const int _sendMessageQueueBatchSize = 1000;
private const int _processSendMessageQueueEveryMs = 50;
private const int _maxUserOutputMessagePerRun = 10100;
private const int _maxUserOutputMessagesPerRun = 10100;
private int _userOutputMessagesSentThisRun;
private bool _outputLimitReachedMessageSent;
private readonly object _outputLimitReachedMessageSendLock = new ();
Expand Down Expand Up @@ -188,7 +189,7 @@ public async Task WriteAsync(object? output, string? title = null, CancellationT

private bool HasReachedUserOutputMessageLimitForThisRun()
{
return _userOutputMessagesSentThisRun >= _maxUserOutputMessagePerRun;
return _userOutputMessagesSentThisRun >= _maxUserOutputMessagesPerRun;
}

private void QueueMessage(ScriptOutput output, bool isCancellable)
Expand Down
72 changes: 44 additions & 28 deletions src/Core/NetPad.Application/Scripts/ScriptEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class ScriptEnvironment : IDisposable, IAsyncDisposable
private readonly IDataConnectionResourcesCache _dataConnectionResourcesCache;
private IServiceScope _serviceScope;
private IInputReader<string> _inputReader;
private IOutputWriter<object> _outputAdapter;
private IOutputWriter<object> _outputWriter;
private ScriptStatus _status;
private IScriptRuntime? _runtime;
private bool _isDisposed;
Expand All @@ -30,10 +30,19 @@ public ScriptEnvironment(Script script, IServiceScope serviceScope)
_dataConnectionResourcesCache = _serviceScope.ServiceProvider.GetRequiredService<IDataConnectionResourcesCache>();
_logger = _serviceScope.ServiceProvider.GetRequiredService<ILogger<ScriptEnvironment>>();
_inputReader = ActionInputReader<string>.Null;
_outputAdapter = ActionOutputWriter<object>.Null;
_outputWriter = ActionOutputWriter<object>.Null;
_status = ScriptStatus.Ready;

Initialize();
// Forwards the following 2 property change notifications as messages on the event bus. They will eventually be pushed to IPC clients.
Script.OnPropertyChanged.Add(async args =>
{
await _eventBus.PublishAsync(new ScriptPropertyChangedEvent(Script.Id, args.PropertyName, args.OldValue, args.NewValue));
});

Script.Config.OnPropertyChanged.Add(async args =>
{
await _eventBus.PublishAsync(new ScriptConfigPropertyChangedEvent(Script.Id, args.PropertyName, args.OldValue, args.NewValue));
});
}

public Script Script { get; }
Expand Down Expand Up @@ -80,7 +89,7 @@ public async Task RunAsync(RunOptions runOptions)
catch (Exception ex)
{
_logger.LogError(ex, "Error running script");
await _outputAdapter.WriteAsync(new ErrorScriptOutput(ex));
await _outputWriter.WriteAsync(new ErrorScriptOutput(ex));
await SetStatusAsync(ScriptStatus.Error);
}
finally
Expand Down Expand Up @@ -171,13 +180,13 @@ public async Task StopAsync()
await _runtime.StopScriptAsync();
}

await _outputAdapter.WriteAsync(new RawScriptOutput($"Script stopped at: {stopTime}"));
await _outputWriter.WriteAsync(new RawScriptOutput($"Script stopped at: {stopTime}"));
await SetStatusAsync(ScriptStatus.Ready);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error stopping script");
await _outputAdapter.WriteAsync(new ErrorScriptOutput(ex));
await _outputWriter.WriteAsync(new ErrorScriptOutput(ex));
await SetStatusAsync(ScriptStatus.Error);
}
finally
Expand All @@ -186,34 +195,25 @@ public async Task StopAsync()
}
}

public void SetIO(IInputReader<string> inputReader, IOutputWriter<object> outputAdapter)
public void SetIO(IInputReader<string> inputReader, IOutputWriter<object> outputWriter)
{
EnsureNotDisposed();

RemoveScriptRuntimeIOHandlers();

_inputReader = inputReader ?? throw new ArgumentNullException(nameof(inputReader));
_outputAdapter = outputAdapter;
_outputWriter = outputWriter;

AddScriptRuntimeIOHandlers();
}

private void Initialize()
private async Task SetStatusAsync(ScriptStatus status)
{
Script.OnPropertyChanged.Add(async args =>
if (status == _status)
{
await _eventBus.PublishAsync(new ScriptPropertyChangedEvent(Script.Id, args.PropertyName, args.OldValue, args.NewValue));
});

Script.Config.OnPropertyChanged.Add(async args =>
{
await _eventBus.PublishAsync(
new ScriptConfigPropertyChangedEvent(Script.Id, args.PropertyName, args.OldValue, args.NewValue));
});
}
return;
}

private async Task SetStatusAsync(ScriptStatus status)
{
var oldValue = _status;
_status = status;
await _eventBus.PublishAsync(new EnvironmentPropertyChangedEvent(Script.Id, nameof(Status), oldValue, status));
Expand Down Expand Up @@ -244,13 +244,13 @@ private async Task<IScriptRuntime> GetRuntimeAsync()
private void AddScriptRuntimeIOHandlers()
{
_runtime?.AddInput(_inputReader);
_runtime?.AddOutput(_outputAdapter);
_runtime?.AddOutput(_outputWriter);
}

private void RemoveScriptRuntimeIOHandlers()
{
_runtime?.RemoveInput(_inputReader);
_runtime?.RemoveOutput(_outputAdapter);
_runtime?.RemoveOutput(_outputWriter);
}

private void EnsureNotDisposed()
Expand Down Expand Up @@ -289,15 +289,23 @@ protected void Dispose(bool disposing)
{
if (disposing)
{
AsyncUtil.RunSync(async () => await StopAsync());
Try.Run(() => AsyncUtil.RunSync(async () => await StopAsync()));

Script.RemoveAllPropertyChangedHandlers();
Script.Config.RemoveAllPropertyChangedHandlers();

_inputReader = ActionInputReader<string>.Null;
_outputAdapter = ActionOutputWriter<object>.Null;
_outputWriter = ActionOutputWriter<object>.Null;

try
{
_runtime?.Dispose();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing runtime of type: {RuntimeType}", _runtime!.GetType().FullName);
}

_runtime?.Dispose();
_runtime = null;

_serviceScope.Dispose();
Expand All @@ -307,12 +315,20 @@ protected void Dispose(bool disposing)

protected async ValueTask DisposeAsyncCore()
{
await StopAsync();
await Try.RunAsync(async () => await StopAsync());

Script.RemoveAllPropertyChangedHandlers();
Script.Config.RemoveAllPropertyChangedHandlers();

_runtime?.Dispose();
try
{
_runtime?.Dispose();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing runtime of type: {RuntimeType}", _runtime!.GetType().FullName);
}

_runtime = null;

if (_serviceScope is IAsyncDisposable asyncDisposable)
Expand Down
25 changes: 20 additions & 5 deletions src/Core/NetPad.Domain/Runtimes/IScriptRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,30 @@
namespace NetPad.Runtimes;

/// <summary>
/// Handles all operations related to running a <see cref="Scripts.Script"/>.
/// An execution engine that runs <see cref="Scripts.Script"/>s.
/// </summary>
public interface IScriptRuntime : IDisposable
{
Task<RunResult> RunScriptAsync(RunOptions runOptions);
Task StopScriptAsync();

void AddInput(IInputReader<string> outputAdapter);
void RemoveInput(IInputReader<string> outputAdapter);
void AddOutput(IOutputWriter<object> outputAdapter);
void RemoveOutput(IOutputWriter<object> outputAdapter);
/// <summary>
/// Adds an input reader that will be invoked whenever script makes a request for user input.
/// </summary>
void AddInput(IInputReader<string> inputReader);

/// <summary>
/// Removes a previously added input reader.
/// </summary>
void RemoveInput(IInputReader<string> inputReader);

/// <summary>
/// Adds an output writer that will be invoked whenever script, or this runtime itself, emits any output.
/// </summary>
void AddOutput(IOutputWriter<object> outputWriter);

/// <summary>
/// Removes a previously added output writer.
/// </summary>
void RemoveOutput(IOutputWriter<object> outputWriter);
}
1 change: 1 addition & 0 deletions src/Core/NetPad.Domain/Scripts/ScriptKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace NetPad.Scripts;

public enum ScriptKind
{
// TODO maybe remove Expression. Its not really needed anymore. ScriptRuntime runs expression already with Program type
Expression = 0,
Program = 1,
SQL = 10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,11 @@ public string GetUserProgram(string scriptCode, ScriptKind kind)
return userCode;
}

public string GetBootstrapperProgram()
private static string GetBootstrapperProgram()
{
return AssemblyUtil.ReadEmbeddedResource(typeof(ScriptRuntimeServices).Assembly, $"{nameof(ScriptRuntimeServices)}.cs");
var scriptRuntimeServicesCode = AssemblyUtil.ReadEmbeddedResource(typeof(ScriptRuntimeServices).Assembly, $"{nameof(ScriptRuntimeServices)}.cs");

return scriptRuntimeServicesCode +
$"\n\npublic partial class Program {{ static Program() {{ {nameof(ScriptRuntimeServices)}.{nameof(ScriptRuntimeServices.UseStandardIO)}(); }} }}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public void RemoveOutput(IOutputWriter<object> outputWriter)
_externalOutputWriters.Remove(outputWriter);
}

/// <summary>
/// Processes raw external process output data.
/// </summary>
/// <param name="raw">Raw output data as written to STD OUT of external process.</param>
/// <exception cref="FormatException"></exception>
private async Task OnProcessOutputReceived(string raw)
{
if (raw == "[INPUT_REQUEST]")
Expand Down Expand Up @@ -92,6 +97,11 @@ private async Task OnProcessOutputReceived(string raw)
await _output.WriteAsync(output);
}

/// <summary>
/// Processes raw external process error data.
/// </summary>
/// <param name="raw">Raw error data as written to STD OUT of external process.</param>
/// <param name="userProgramStartLineNumber">The line number the user's program starts. Used to correct line numbers.</param>
private Task OnProcessErrorReceived(string raw, int userProgramStartLineNumber)
{
raw = CorrectUncaughtExceptionStackTraceLineNumber(raw, userProgramStartLineNumber);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ public partial class ExternalProcessScriptRuntime

private async Task<RunDependencies?> GetRunDependencies(RunOptions runOptions)
{
// Add code that initializes runtime services
runOptions.AdditionalCode.Add(new SourceCode("public partial class Program " +
$"{{ static Program() {{ {nameof(ScriptRuntimeServices)}.{nameof(ScriptRuntimeServices.UseStandardIO)}(); }} }}"));

// Gather assembly references
// Images
var referenceAssemblyImages = new HashSet<AssemblyImage>();
foreach (var additionalReference in runOptions.AdditionalReferences)
Expand Down Expand Up @@ -58,7 +53,7 @@ public partial class ExternalProcessScriptRuntime
.Select(x => x.Path)
.ToHashSet();

// Add custom assemblies
// Add certain app assemblies needed to support script runtime services running in external process
referenceAssemblyPaths.Add(typeof(IOutputWriter<>).Assembly.Location);
// Needed to serialize output in external process to HTML
referenceAssemblyPaths.Add(typeof(HtmlConvert).Assembly.Location);
Expand Down Expand Up @@ -128,13 +123,17 @@ ParseAndCompileResult parseAndCompile(string targetCode)
return new ParseAndCompileResult(parsingResult, compilationResult);
}

// We want to try code as-is, but also try additional permutations of it if it fails to compile
// We will try different permutations of the user's code, starting with running it as-is. The idea is to account
// for, and give the ability for users to, run expressions not ending with semi-colon or .Dump() and still produce
// the "missing" pieces to run the expression.
var permutations = new List<Func<(bool shouldAttempt, string code)>>
{
// As-is
() => (true, code),

// Try adding ".Dump();" to dump the result of an expression
// There is no good way that I've found to determine if expression returns void or otherwise
// so the only way to test if the expression results in a value is to try to compile it.
() =>
{
var trimmedCode = code.Trim();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,17 @@ public void Dispose()
{
_logger.LogTrace("Dispose start");

_processHandler?.Dispose();
_externalOutputWriters.Clear();

try
{
_processHandler?.Dispose();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing process handler");
}

_logger.LogTrace("Dispose end");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,14 @@ public void RemoveInput(IInputReader<string> inputReader)
_externalInputAdapters.Remove(inputReader);
}

public void AddOutput(IOutputWriter<object> outputAdapter)
public void AddOutput(IOutputWriter<object> outputWriter)
{
_externalOutputAdapters.Add(outputAdapter);
_externalOutputAdapters.Add(outputWriter);
}

public void RemoveOutput(IOutputWriter<object> outputAdapter)
public void RemoveOutput(IOutputWriter<object> outputWriter)
{
_externalOutputAdapters.Remove(outputAdapter);
_externalOutputAdapters.Remove(outputWriter);
}

private async Task<(
Expand Down

0 comments on commit 22fbc18

Please sign in to comment.