Skip to content

Commit

Permalink
fix(jsii-dotnet-runtime): Fix EPIPE on Windows.
Browse files Browse the repository at this point in the history
* The JSII runtime for DotNet applications did not properly dispose of the IO streams in use for communication between the dotnet and jsii processes. This has been fixed, which solves the EPIPE error.
* During this invesigation, it was found that the DotNet runtime did not redirect stderr, which disabled the use of JSII_DEBUG. Fixed.
* If the node process is closed unexpectedly, the application will now throw an exception with the contents of STDERR.
* Added Unit Test for node process closing unexpectedly.
* Added gitignore rule for "dist" folders, which are created by pack.

Fixes #341
  • Loading branch information
costleya committed Dec 28, 2018
1 parent 007b62c commit 1d7cc8b
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 16 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -2,4 +2,5 @@ node_modules/
.BUILD_COMPLETED
lerna-debug.log
.DS_Store
.idea
.idea
dist
Expand Up @@ -861,7 +861,7 @@ public void NullShouldBeTreatedAsUndefined()
obj.ChangeMeToUndefined = null;
obj.VerifyPropertyIsUndefined();
}

[Fact(DisplayName = Prefix + nameof(JsiiAgent))]
public void JsiiAgent()
{
Expand Down
1 change: 1 addition & 0 deletions packages/jsii-dotnet-runtime/.gitignore
Expand Up @@ -23,3 +23,4 @@ coverage/
bin/
cli/
obj/
*.DotSettings.user
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
Expand Down
@@ -0,0 +1,41 @@
using System.IO;
using Amazon.JSII.Runtime.Services;
using NSubstitute;
using NSubstitute.ReturnsExtensions;
using Xunit;

namespace Amazon.JSII.Runtime.UnitTests.Client
{
public class RuntimeTests
{
private const string Prefix = "Runtime.";

private INodeProcess _nodeProcessMock;
private TextReader _standardOutputMock;
private TextReader _standardErrorMock;

private IRuntime _sut;

public RuntimeTests()
{
_nodeProcessMock = Substitute.For<INodeProcess>();
_standardOutputMock = Substitute.For<TextReader>();
_standardErrorMock = Substitute.For<TextReader>();

_nodeProcessMock.StandardOutput.Returns(_standardOutputMock);
_nodeProcessMock.StandardError.Returns(_standardErrorMock);

_sut = new Services.Runtime(_nodeProcessMock);
}

[Fact(DisplayName = Prefix + nameof(ThrowsJsiiExceptionWhenResponseNotReceived))]
public void ThrowsJsiiExceptionWhenResponseNotReceived()
{
_nodeProcessMock.StandardOutput.ReadLine().ReturnsNull();
_nodeProcessMock.StandardError.ReadToEnd().Returns("This is a test.");

var ex = Assert.Throws<JsiiException>(() => _sut.ReadResponse());
Assert.Equal("Child process exited unexpectedly: This is a test.", ex.Message);
}
}
}
@@ -1,12 +1,16 @@
using Amazon.JSII.JsonModel.Api.Response;
using System;
using System;
using Amazon.JSII.JsonModel.Api.Response;

namespace Amazon.JSII.Runtime
{
public class JsiiException : Exception
{
public ErrorResponse ErrorResponse { get; }

public JsiiException(string message) : base(message)
{
}

public JsiiException(string message, Exception innerException)
: base(message, innerException)
{
Expand Down
Expand Up @@ -5,8 +5,10 @@ namespace Amazon.JSII.Runtime.Services
{
public interface INodeProcess : IDisposable
{
StreamWriter StandardInput { get; }
TextWriter StandardInput { get; }

StreamReader StandardOutput { get; }
TextReader StandardOutput { get; }

TextReader StandardError { get; }
}
}
}
@@ -1,7 +1,7 @@
using Microsoft.Extensions.Logging;
using System;
using System;
using System.Diagnostics;
using System.IO;
using Microsoft.Extensions.Logging;

namespace Amazon.JSII.Runtime.Services
{
Expand All @@ -23,24 +23,30 @@ public NodeProcess(IJsiiRuntimeProvider jsiiRuntimeProvider, ILoggerFactory logg
Arguments = "--max-old-space-size=4096 " + jsiiRuntimeProvider.JsiiRuntimePath,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};

_process.StartInfo.EnvironmentVariables.Add("JSII_AGENT", "DotNet/" + Environment.Version.ToString());
_process.StartInfo.EnvironmentVariables.Add("JSII_AGENT", "DotNet/" + Environment.Version);

_logger.LogDebug("Starting jsii runtime...");
_logger.LogDebug($"{_process.StartInfo.FileName} {_process.StartInfo.Arguments}");

_process.Start();
}

public StreamWriter StandardInput => _process.StandardInput;
public TextWriter StandardInput => _process.StandardInput;

public StreamReader StandardOutput => _process.StandardOutput;
public TextReader StandardOutput => _process.StandardOutput;

public TextReader StandardError => _process.StandardError;

void IDisposable.Dispose()
{
StandardInput.Dispose();
StandardOutput.Dispose();
StandardError.Dispose();
_process.Dispose();
}
}
}
}
@@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;

namespace Amazon.JSII.Runtime.Services
{
Expand All @@ -9,11 +10,22 @@ public class Runtime : IRuntime
public Runtime(INodeProcess nodeProcess)
{
_nodeProcess = nodeProcess ?? throw new ArgumentNullException(nameof(nodeProcess));
if (Environment.GetEnvironmentVariable("JSII_DEBUG") != null)
{
Task.Run(() => RedirectStandardError());
}
}

public string ReadResponse()
{
return _nodeProcess.StandardOutput.ReadLine();
var response = _nodeProcess.StandardOutput.ReadLine();
if (string.IsNullOrEmpty(response))
{
var errorMessage = _nodeProcess.StandardError.ReadToEnd();
throw new JsiiException("Child process exited unexpectedly: " + errorMessage);
}

return response;
}

public void WriteRequest(string request)
Expand All @@ -31,5 +43,13 @@ public void WriteRequest(string request)
_nodeProcess.StandardInput.WriteLine(request);
_nodeProcess.StandardInput.Flush();
}

private void RedirectStandardError()
{
while (true)
{
Console.WriteLine(_nodeProcess.StandardError.ReadLine());
}
}
}
}
}

0 comments on commit 1d7cc8b

Please sign in to comment.