Skip to content

Commit

Permalink
Unwrap InvocationTargetException which are ResponseErrorException
Browse files Browse the repository at this point in the history
By wrapping ResponseErrorException with another layer in
RuntimeException it means the unwrapping of the exception in
RemoteEndpoint.DEFAULT_EXCEPTION_HANDLER doesn't work as
expected and instead of getting the original ResponseError
the other end gets the fallback ResponseError of type
internal error.

Fixes #802
  • Loading branch information
jonahgraham committed Feb 14, 2024
1 parent 88868d3 commit d2925a4
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,13 @@ protected void recursiveFindRpcMethods(Object current, Set<Class<?>> visited, Se
Method method = methodInfo.method;
Object[] arguments = this.getArguments(method, arg);
return (CompletableFuture<Object>) method.invoke(current, arguments);
} catch (InvocationTargetException | IllegalAccessException e) {
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof ResponseErrorException) {
ResponseErrorException ree = (ResponseErrorException) e.getTargetException();
throw ree;
}
throw new RuntimeException(e);
} catch ( IllegalAccessException e) {
throw new RuntimeException(e);
}
};
Expand All @@ -79,7 +85,13 @@ protected void recursiveFindRpcMethods(Object current, Set<Class<?>> visited, Se
} else {
LOG.fine("A delegate object is null, jsonrpc methods of '" + method + "' are ignored");
}
} catch (InvocationTargetException | IllegalAccessException e) {
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof ResponseErrorException) {
ResponseErrorException ree = (ResponseErrorException) e.getTargetException();
throw ree;
}
throw new RuntimeException(e);
} catch ( IllegalAccessException e) {
throw new RuntimeException(e);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
import org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
import org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint;
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
Expand Down Expand Up @@ -454,6 +456,68 @@ public CompletableFuture<MyParam> askClient(MyParam param) {
Assert.assertEquals("FOO", goodFuture.get(TIMEOUT, TimeUnit.MILLISECONDS).value);
}

@Test
public void testVersatilityResponseError() throws Exception {
Logger.getLogger(RemoteEndpoint.class.getName()).setLevel(Level.OFF);
// create client side
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
PipedInputStream in2 = new PipedInputStream();
PipedOutputStream out2 = new PipedOutputStream();

// See https://github.com/eclipse-lsp4j/lsp4j/issues/510 for full details.
// Make sure that the thread that writes to the PipedOutputStream stays alive
// until the read from the PipedInputStream. Using a cached thread pool
// does not 100% guarantee that, but increases the probability that the
// selected thread will exist for the lifetime of the test.
ExecutorService executor = Executors.newCachedThreadPool();

in.connect(out2);
out.connect(in2);

MyClient client = new MyClient() {
private int tries = 0;

@Override
public CompletableFuture<MyParam> askClient(MyParam param) {
if (tries == 0) {
tries++;
throw new ResponseErrorException(new ResponseError(ResponseErrorCode.InvalidParams, "Direct Throw", "data"));
}
return CompletableFutures.computeAsync(executor, cancelToken -> {
if (tries++ == 1)
throw new ResponseErrorException(new ResponseError(ResponseErrorCode.InvalidParams, "Throw in computeAsync", "data"));
return param;
});
}
};
Launcher<MyServer> clientSideLauncher = Launcher.createLauncher(client, MyServer.class, in, out);

// create server side
MyServer server = new MyServerImpl();
Launcher<MyClient> serverSideLauncher = Launcher.createLauncher(server, MyClient.class, in2, out2);

clientSideLauncher.startListening();
serverSideLauncher.startListening();

CompletableFuture<MyParam> errorFuture1 = serverSideLauncher.getRemoteProxy().askClient(new MyParam("FOO"));
try {
System.out.println(errorFuture1.get());
Assert.fail();
} catch (ExecutionException e) {
Assert.assertEquals("Direct Throw", ((ResponseErrorException)e.getCause()).getResponseError().getMessage());
}
CompletableFuture<MyParam> errorFuture2 = serverSideLauncher.getRemoteProxy().askClient(new MyParam("FOO"));
try {
errorFuture2.get();
Assert.fail();
} catch (ExecutionException e) {
Assert.assertEquals("Throw in computeAsync", ((ResponseErrorException)e.getCause()).getResponseError().getMessage());
}
CompletableFuture<MyParam> goodFuture = serverSideLauncher.getRemoteProxy().askClient(new MyParam("FOO"));
Assert.assertEquals("FOO", goodFuture.get(TIMEOUT, TimeUnit.MILLISECONDS).value);
}

@Test
public void testUnknownMessages() throws Exception {
// intercept log messages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,18 @@
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.logging.Level;

import org.eclipse.lsp4j.jsonrpc.CompletableFutures;
import org.eclipse.lsp4j.jsonrpc.Endpoint;
import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
import org.eclipse.lsp4j.jsonrpc.RemoteEndpoint;
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.jsonrpc.messages.Message;
import org.eclipse.lsp4j.jsonrpc.messages.MessageIssue;
Expand Down Expand Up @@ -219,6 +223,38 @@ public CompletableFuture<Object> request(String method, Object parameter) {
logMessages.unregister();
}
}

@Test
public void testResponseErrorExceptionInEndpoint() {
LogMessageAccumulator logMessages = new LogMessageAccumulator();
try {
// Don't show the exception in the test execution log
logMessages.registerTo(RemoteEndpoint.class);

TestEndpoint endp = new TestEndpoint() {
@Override
public CompletableFuture<Object> request(String method, Object parameter) {
throw new ResponseErrorException(new ResponseError(ResponseErrorCode.InvalidParams, "Direct Throw", "data"));
}
};
TestMessageConsumer consumer = new TestMessageConsumer();
RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp);

endpoint.consume(init(new RequestMessage(), it -> {
it.setId("1");
it.setMethod("foo");
it.setParams("myparam");
}));

ResponseMessage response = (ResponseMessage) consumer.messages.get(0);
assertEquals("Direct Throw", response.getError().getMessage());
assertEquals(ResponseErrorCode.InvalidParams.getValue(), response.getError().getCode());
String data = (String) response.getError().getData();
assertEquals("data", data);
} finally {
logMessages.unregister();
}
}

@Test
public void testExceptionInConsumer() throws Exception {
Expand Down

0 comments on commit d2925a4

Please sign in to comment.