Skip to content

Commit 1d7cc8b

Browse files
committed
fix(jsii-dotnet-runtime): Fix EPIPE on Windows.
* 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
1 parent 007b62c commit 1d7cc8b

9 files changed

Lines changed: 91 additions & 16 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ node_modules/
22
.BUILD_COMPLETED
33
lerna-debug.log
44
.DS_Store
5-
.idea
5+
.idea
6+
dist

packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -861,7 +861,7 @@ public void NullShouldBeTreatedAsUndefined()
861861
obj.ChangeMeToUndefined = null;
862862
obj.VerifyPropertyIsUndefined();
863863
}
864-
864+
865865
[Fact(DisplayName = Prefix + nameof(JsiiAgent))]
866866
public void JsiiAgent()
867867
{

packages/jsii-dotnet-runtime/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ coverage/
2323
bin/
2424
cli/
2525
obj/
26+
*.DotSettings.user

packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime.UnitTests/Amazon.JSII.Runtime.UnitTests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>netcoreapp2.0</TargetFramework>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.IO;
2+
using Amazon.JSII.Runtime.Services;
3+
using NSubstitute;
4+
using NSubstitute.ReturnsExtensions;
5+
using Xunit;
6+
7+
namespace Amazon.JSII.Runtime.UnitTests.Client
8+
{
9+
public class RuntimeTests
10+
{
11+
private const string Prefix = "Runtime.";
12+
13+
private INodeProcess _nodeProcessMock;
14+
private TextReader _standardOutputMock;
15+
private TextReader _standardErrorMock;
16+
17+
private IRuntime _sut;
18+
19+
public RuntimeTests()
20+
{
21+
_nodeProcessMock = Substitute.For<INodeProcess>();
22+
_standardOutputMock = Substitute.For<TextReader>();
23+
_standardErrorMock = Substitute.For<TextReader>();
24+
25+
_nodeProcessMock.StandardOutput.Returns(_standardOutputMock);
26+
_nodeProcessMock.StandardError.Returns(_standardErrorMock);
27+
28+
_sut = new Services.Runtime(_nodeProcessMock);
29+
}
30+
31+
[Fact(DisplayName = Prefix + nameof(ThrowsJsiiExceptionWhenResponseNotReceived))]
32+
public void ThrowsJsiiExceptionWhenResponseNotReceived()
33+
{
34+
_nodeProcessMock.StandardOutput.ReadLine().ReturnsNull();
35+
_nodeProcessMock.StandardError.ReadToEnd().Returns("This is a test.");
36+
37+
var ex = Assert.Throws<JsiiException>(() => _sut.ReadResponse());
38+
Assert.Equal("Child process exited unexpectedly: This is a test.", ex.Message);
39+
}
40+
}
41+
}

packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/JsiiException.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
using Amazon.JSII.JsonModel.Api.Response;
2-
using System;
1+
using System;
2+
using Amazon.JSII.JsonModel.Api.Response;
33

44
namespace Amazon.JSII.Runtime
55
{
66
public class JsiiException : Exception
77
{
88
public ErrorResponse ErrorResponse { get; }
99

10+
public JsiiException(string message) : base(message)
11+
{
12+
}
13+
1014
public JsiiException(string message, Exception innerException)
1115
: base(message, innerException)
1216
{

packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/INodeProcess.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ namespace Amazon.JSII.Runtime.Services
55
{
66
public interface INodeProcess : IDisposable
77
{
8-
StreamWriter StandardInput { get; }
8+
TextWriter StandardInput { get; }
99

10-
StreamReader StandardOutput { get; }
10+
TextReader StandardOutput { get; }
11+
12+
TextReader StandardError { get; }
1113
}
12-
}
14+
}

packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/NodeProcess.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
using Microsoft.Extensions.Logging;
2-
using System;
1+
using System;
32
using System.Diagnostics;
43
using System.IO;
4+
using Microsoft.Extensions.Logging;
55

66
namespace Amazon.JSII.Runtime.Services
77
{
@@ -23,24 +23,30 @@ public NodeProcess(IJsiiRuntimeProvider jsiiRuntimeProvider, ILoggerFactory logg
2323
Arguments = "--max-old-space-size=4096 " + jsiiRuntimeProvider.JsiiRuntimePath,
2424
RedirectStandardInput = true,
2525
RedirectStandardOutput = true,
26+
RedirectStandardError = true
2627
}
2728
};
2829

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

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

3435
_process.Start();
3536
}
3637

37-
public StreamWriter StandardInput => _process.StandardInput;
38+
public TextWriter StandardInput => _process.StandardInput;
3839

39-
public StreamReader StandardOutput => _process.StandardOutput;
40+
public TextReader StandardOutput => _process.StandardOutput;
41+
42+
public TextReader StandardError => _process.StandardError;
4043

4144
void IDisposable.Dispose()
4245
{
46+
StandardInput.Dispose();
47+
StandardOutput.Dispose();
48+
StandardError.Dispose();
4349
_process.Dispose();
4450
}
4551
}
46-
}
52+
}

packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/Runtime.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Threading.Tasks;
23

34
namespace Amazon.JSII.Runtime.Services
45
{
@@ -9,11 +10,22 @@ public class Runtime : IRuntime
910
public Runtime(INodeProcess nodeProcess)
1011
{
1112
_nodeProcess = nodeProcess ?? throw new ArgumentNullException(nameof(nodeProcess));
13+
if (Environment.GetEnvironmentVariable("JSII_DEBUG") != null)
14+
{
15+
Task.Run(() => RedirectStandardError());
16+
}
1217
}
1318

1419
public string ReadResponse()
1520
{
16-
return _nodeProcess.StandardOutput.ReadLine();
21+
var response = _nodeProcess.StandardOutput.ReadLine();
22+
if (string.IsNullOrEmpty(response))
23+
{
24+
var errorMessage = _nodeProcess.StandardError.ReadToEnd();
25+
throw new JsiiException("Child process exited unexpectedly: " + errorMessage);
26+
}
27+
28+
return response;
1729
}
1830

1931
public void WriteRequest(string request)
@@ -31,5 +43,13 @@ public void WriteRequest(string request)
3143
_nodeProcess.StandardInput.WriteLine(request);
3244
_nodeProcess.StandardInput.Flush();
3345
}
46+
47+
private void RedirectStandardError()
48+
{
49+
while (true)
50+
{
51+
Console.WriteLine(_nodeProcess.StandardError.ReadLine());
52+
}
53+
}
3454
}
35-
}
55+
}

0 commit comments

Comments
 (0)