Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow server the option to crash on exception thrown #74

Merged
merged 22 commits into from Jan 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
de75371
update readme, allow server to crash on exception
jepetty Dec 14, 2017
43d9c91
remove temp exception in favor of taskCanceledException
jepetty Dec 15, 2017
d5f7435
revert change in statement order
jepetty Dec 15, 2017
226db0a
resolve merge conflicts
jepetty Dec 15, 2017
0bbc033
separate overloaded rpc tests and remove unnecessary tests
jepetty Dec 18, 2017
673d727
update API to include exception as parameter
jepetty Dec 19, 2017
ef74c1a
add parameter documentation
jepetty Dec 19, 2017
8b9678c
update documentation for new functionality
jepetty Dec 20, 2017
8152cf4
improve tests and add remarks
jepetty Dec 20, 2017
3b1efda
Only pass inner exception through filter, not TargetInvocationExcepti…
jepetty Dec 20, 2017
5994ffb
update docs as per cr feedback
jepetty Dec 20, 2017
747af41
update all parts of doc to instantiate JsonRpc twice
jepetty Dec 20, 2017
5435f72
closing streams on operation canceled, other pr comments
jepetty Dec 20, 2017
97f2c56
Move IsDisposed to FullDuplexStreams and check streams disposed in Me…
jepetty Dec 20, 2017
89a602e
fix warnings in error list
jepetty Dec 20, 2017
dbde851
fix build error
jepetty Jan 4, 2018
3d04487
update documentation, only filter exceptions once
jepetty Jan 8, 2018
12c6c54
Remove obsolete comment since we don't send cancellation exceptions t…
AArnott Jan 10, 2018
22a1a68
Add a few tests around AggregateException
AArnott Jan 10, 2018
a0477dd
responding to pr feedback
jepetty Jan 11, 2018
ae643fa
merge master branch
jepetty Jan 11, 2018
984c20c
fix resx file tag, make test always async
jepetty Jan 11, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
70 changes: 51 additions & 19 deletions doc/index.md
Expand Up @@ -5,20 +5,19 @@ There are two ways to establish connection and start invoking methods remotely:

1. Use the static `Attach` method in `JsonRpc` class:
```csharp
public void ConstructRpc()
public void ConstructRpc(Stream stream)
{
var target = new LanguageServerTarget();
var rpc = JsonRpc.Attach(Console.OpenStandardOutput(), Console.OpenStandardInput(), target);
var rpc = JsonRpc.Attach(stream, target);
}
```
The `JsonRpc` object returned by `Attach` method would be used to invoke remote methods via JSON-RPC.

2. Construct a `JsonRpc` object directly:
```csharp
public void ConstructRpc()
public void ConstructRpc(Stream clientStream, Stream serverStream)
{
var streams = Nerdbank.FullDuplexStream.CreateStreams();
var rpc = new JsonRpc(streams.Item1, streams.Item2);
var clientRpc = new JsonRpc(clientStream, serverStream);
var target = new Server();
rpc.AddLocalRpcTarget(target);
rpc.StartListening();
Expand All @@ -28,42 +27,42 @@ public void ConstructRpc()
## Invoking a notification
To invoke a remote method named "foo" which takes one `string` parameter and does not return anything (i.e. send a notification remotely):
```csharp
public async Task NotifyRemote()
public async Task NotifyRemote(Stream stream)
{
var target = new Server();
var rpc = JsonRpc.Attach(Console.OpenStandardOutput(), Console.OpenStandardInput(), target);
var rpc = JsonRpc.Attach(stream, target);
await rpc.NotifyAsync("foo", "param1");
}
```
The parameter will be passed remotely as an array of one object.

To invoke a remote method named "bar" which takes one `string` parameter (but the parameter should be passed as an object instead of an array of one object):
```csharp
public async Task NotifyRemote()
public async Task NotifyRemote(Stream stream)
{
var target = new Server();
var rpc = JsonRpc.Attach(Console.OpenStandardOutput(), Console.OpenStandardInput(), target);
var rpc = JsonRpc.Attach(stream, target);
await rpc.NotifyWithParameterObjectAsync("bar", "param1");
}
```
## Invoking a request
To invoke a remote method named "foo" which takes two `string` parameters and returns an int:
```csharp
public async Task NotifyRemote()
public async Task InvokeRemote(Stream stream)
{
var target = new Server();
var rpc = JsonRpc.Attach(Console.OpenStandardOutput(), Console.OpenStandardInput(), target);
var rpc = JsonRpc.Attach(stream, target);
var myResult = await rpc.InvokeAsync<int>("foo", "param1", "param2");
}
```
The parameters will be passed remotely as an array of objects.

To invoke a remote method named "baz" which takes one `string` parameter (but the parameter should be passed as an object instead of an array of one object) and returns a string:
```csharp
public async Task NotifyRemote()
public async Task InvokeRemote(Stream stream)
{
var target = new Server();
var rpc = JsonRpc.Attach(Console.OpenStandardOutput(), Console.OpenStandardInput(), target);
var rpc = JsonRpc.Attach(stream, target);
var myResult = await rpc.InvokeWithParameterObjectAsync<string>("baz", "param1");
}
```
Expand All @@ -86,10 +85,10 @@ public class Server

public class Connection
{
public async Task NotifyRemote()
public async Task InvokeRemote(Stream stream)
{
var target = new Server();
var rpc = JsonRpc.Attach(Console.OpenStandardOutput(), Console.OpenStandardInput(), target);
var rpc = JsonRpc.Attach(stream, target);
var myResult = await rpc.InvokeWithParameterObjectAsync<string>("baz", "param1");
}
}
Expand All @@ -103,14 +102,47 @@ public class Server : BaseClass
[JsonRpcMethod("test/InvokeTestMethod")]
public string InvokeTestMethodAttribute() => "test method attribute";
}

```
With this attribute on the server method, the client can now invoke that method with a special name.
```csharp
public class Connection
{
public async Task NotifyRemote()
public async Task InvokeRemote(Stream stream)
{
var target = new Server();
var rpc = JsonRpc.Attach(Console.OpenStandardOutput(), Console.OpenStandardInput(), target);
var rpc = JsonRpc.Attach(stream);
var myResult = await rpc.InvokeWithParameterObjectAsync<string>("test/InvokeTestMethod");
}
}
```

## Close stream on fatal errors
In some cases, you may want to immediately close the streams if certain exceptions are thrown. In this case, overriding the `IsFatalException` method will give you the desired functionality. Through `IsFatalException` you can access and respond to exceptions as they are observed.
```csharp
public class Server : BaseClass
{
public void ThrowsException() => throw new Exception("Throwing an exception");
}

public class JsonRpcClosesStreamOnException : JsonRpc
{
public JsonRpcClosesStreamOnException(Stream clientStream, Stream serverStream, object target = null) : base(clientStream, serverStream, target)
{
}

protected override bool IsFatalException(Exception ex)
{
return true;
}
}

public class Connection
{
public async Task InvokeRemote(Stream clientStream, Stream serverStream)
{
var target = new Server();
var rpc = new JsonRpcClosesStreamOnException(clientStream, serverStream, target);
rpc.StartListening();
await rpc.InvokeAsync(nameof(Server.ThrowsException));
}
}
```
13 changes: 13 additions & 0 deletions src/StreamJsonRpc.Tests/DelimitedMessageHandlerTests.cs
Expand Up @@ -10,6 +10,7 @@
using System.Threading.Tasks;
using Microsoft;
using Microsoft.VisualStudio.Threading;
using Nerdbank;
using StreamJsonRpc;
using Xunit;
using Xunit.Abstractions;
Expand Down Expand Up @@ -70,6 +71,18 @@ public void IsDisposed()
Assert.True(observable.IsDisposed);
}

[Fact]
public void Dispose_StreamsAreDisposed()
{
var streams = FullDuplexStream.CreateStreams();
var handler = new DirectMessageHandler(streams.Item1, streams.Item2, Encoding.UTF8);
Assert.False(streams.Item1.IsDisposed);
Assert.False(streams.Item2.IsDisposed);
handler.Dispose();
Assert.True(streams.Item1.IsDisposed);
Assert.True(streams.Item2.IsDisposed);
}

[Fact]
public void WriteAsync_ThrowsObjectDisposedException()
{
Expand Down
10 changes: 10 additions & 0 deletions src/StreamJsonRpc.Tests/FullDuplexStream.cs
Expand Up @@ -40,6 +40,11 @@ public class FullDuplexStream : Stream
/// </summary>
private TaskCompletionSource<object> enqueuedSource = new TaskCompletionSource<object>(EnqueuedSourceOptions);

/// <summary>
/// Gets or sets a value indicating whether the stream is disposed
/// </summary>
public bool IsDisposed { get; set; }

/// <inheritdoc />
public override bool CanRead => true;

Expand Down Expand Up @@ -230,6 +235,11 @@ internal void SetOtherStream(FullDuplexStream other)
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
{
this.IsDisposed = true;
}

// Sending an empty buffer is the traditional way to signal
// that the transmitting stream has closed.
this.other.PostMessage(new Message(EmptyByteArray));
Expand Down