diff --git a/src/main/java/org/redisson/RedissonRemoteService.java b/src/main/java/org/redisson/RedissonRemoteService.java index 0f1df58bd..2359a57e4 100644 --- a/src/main/java/org/redisson/RedissonRemoteService.java +++ b/src/main/java/org/redisson/RedissonRemoteService.java @@ -15,6 +15,16 @@ */ package org.redisson; +import io.netty.buffer.ByteBufUtil; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.FutureListener; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.ThreadLocalRandom; +import org.redisson.core.*; +import org.redisson.remote.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; @@ -23,27 +33,6 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import org.redisson.core.RBatch; -import org.redisson.core.RBlockingQueue; -import org.redisson.core.RBlockingQueueAsync; -import org.redisson.core.RRemoteService; -import org.redisson.remote.RRemoteServiceResponse; -import org.redisson.remote.RemoteServiceAck; -import org.redisson.remote.RemoteServiceAckTimeoutException; -import org.redisson.remote.RemoteServiceKey; -import org.redisson.remote.RemoteServiceMethod; -import org.redisson.remote.RemoteServiceRequest; -import org.redisson.remote.RemoteServiceResponse; -import org.redisson.remote.RemoteServiceTimeoutException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.netty.buffer.ByteBufUtil; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.FutureListener; -import io.netty.util.internal.PlatformDependent; -import io.netty.util.internal.ThreadLocalRandom; - /** * * @author Nikita Koksharov @@ -108,9 +97,10 @@ public class RedissonRemoteService implements RRemoteService { // do not subscribe now, see https://github.com/mrniko/redisson/issues/493 // subscribe(remoteInterface, requestQueue); - + final RemoteServiceRequest request = future.getNow(); - if (System.currentTimeMillis() - request.getDate() > request.getAckTimeout()) { + // check the ack only if expected + if (request.getOptions().isAckExpected() && System.currentTimeMillis() - request.getDate() > request.getOptions().getAckTimeoutInMillis()) { log.debug("request: {} has been skipped due to ackTimeout"); // re-subscribe after a skipped ackTimeout subscribe(remoteInterface, requestQueue); @@ -119,24 +109,29 @@ public class RedissonRemoteService implements RRemoteService { final RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName())); final String responseName = name + ":{" + remoteInterface.getName() + "}:" + request.getRequestId(); - - Future> ackClientsFuture = send(request.getAckTimeout(), responseName, new RemoteServiceAck()); - ackClientsFuture.addListener(new FutureListener>() { - @Override - public void operationComplete(Future> future) throws Exception { - if (!future.isSuccess()) { - log.error("Can't send ack for request: " + request, future.cause()); - if (future.cause() instanceof RedissonShutdownException) { + + // send the ack only if expected + if (request.getOptions().isAckExpected()) { + Future> ackClientsFuture = send(request.getOptions().getAckTimeoutInMillis(), responseName, new RemoteServiceAck()); + ackClientsFuture.addListener(new FutureListener>() { + @Override + public void operationComplete(Future> future) throws Exception { + if (!future.isSuccess()) { + log.error("Can't send ack for request: " + request, future.cause()); + if (future.cause() instanceof RedissonShutdownException) { + return; + } + // re-subscribe after a failed send (ack) + subscribe(remoteInterface, requestQueue); return; } - // re-subscribe after a failed send (ack) - subscribe(remoteInterface, requestQueue); - return; - } - invokeMethod(remoteInterface, requestQueue, request, method, responseName); - } - }); + invokeMethod(remoteInterface, requestQueue, request, method, responseName); + } + }); + } else { + invokeMethod(remoteInterface, requestQueue, request, method, responseName); + } } }); @@ -153,35 +148,50 @@ public class RedissonRemoteService implements RRemoteService { responseHolder.set(response); log.error("Can't execute: " + request, e); } - - Future> clientsFuture = send(request.getResponseTimeout(), responseName, responseHolder.get()); - clientsFuture.addListener(new FutureListener>() { - @Override - public void operationComplete(Future> future) throws Exception { - if (!future.isSuccess()) { - log.error("Can't send response: " + responseHolder.get() + " for request: " + request, future.cause()); - if (future.cause() instanceof RedissonShutdownException) { - return; + + // send the response only if expected + if (request.getOptions().isResultExpected()) { + Future> clientsFuture = send(request.getOptions().getExecutionTimeoutInMillis(), responseName, responseHolder.get()); + clientsFuture.addListener(new FutureListener>() { + @Override + public void operationComplete(Future> future) throws Exception { + if (!future.isSuccess()) { + log.error("Can't send response: " + responseHolder.get() + " for request: " + request, future.cause()); + if (future.cause() instanceof RedissonShutdownException) { + return; + } } + // re-subscribe anyways (fail or success) after the send (response) + subscribe(remoteInterface, requestQueue); } - // re-subscribe anyways (fail or success) after the send (response) - subscribe(remoteInterface, requestQueue); - } - }); + }); + } else { + // re-subscribe anyways after the method invocation + subscribe(remoteInterface, requestQueue); + } } - + @Override public T get(Class remoteInterface) { - return get(remoteInterface, 30, TimeUnit.SECONDS); + return get(remoteInterface, RemoteInvocationOptions.defaults()); } @Override public T get(final Class remoteInterface, final long executionTimeout, final TimeUnit executionTimeUnit) { - return get(remoteInterface, executionTimeout, executionTimeUnit, 1, TimeUnit.SECONDS); + return get(remoteInterface, RemoteInvocationOptions.defaults() + .expectResultWithin(executionTimeout, executionTimeUnit)); } - - public T get(final Class remoteInterface, final long executionTimeout, final TimeUnit executionTimeUnit, - final long ackTimeout, final TimeUnit ackTimeUnit) { + + public T get(final Class remoteInterface, final long executionTimeout, final TimeUnit executionTimeUnit, + final long ackTimeout, final TimeUnit ackTimeUnit) { + return get(remoteInterface, RemoteInvocationOptions.defaults() + .expectAckWithin(ackTimeout, ackTimeUnit) + .expectResultWithin(executionTimeout, executionTimeUnit)); + } + + public T get(final Class remoteInterface, final RemoteInvocationOptions options) { + // local copy of the options, to prevent mutation + final RemoteInvocationOptions optionsCopy = new RemoteInvocationOptions(options); final String toString = getClass().getSimpleName() + "-" + remoteInterface.getSimpleName() + "-proxy-" + generateRequestId(); InvocationHandler handler = new InvocationHandler() { @Override @@ -194,33 +204,47 @@ public class RedissonRemoteService implements RRemoteService { return toString.hashCode(); } + if (!optionsCopy.isResultExpected() && !(method.getReturnType().equals(Void.class) || method.getReturnType().equals(Void.TYPE))) + throw new IllegalArgumentException("The noResult option only supports void return value"); + String requestId = generateRequestId(); - + String requestQueueName = name + ":{" + remoteInterface.getName() + "}"; RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName); - RemoteServiceRequest request = new RemoteServiceRequest(requestId, method.getName(), args, - ackTimeUnit.toMillis(ackTimeout), executionTimeUnit.toMillis(executionTimeout), System.currentTimeMillis()); + RemoteServiceRequest request = new RemoteServiceRequest(requestId, + method.getName(), args, optionsCopy, System.currentTimeMillis()); requestQueue.add(request); - - String responseName = name + ":{" + remoteInterface.getName() + "}:" + requestId; - RBlockingQueue responseQueue = redisson.getBlockingQueue(responseName); - - RemoteServiceAck ack = (RemoteServiceAck) responseQueue.poll(ackTimeout, ackTimeUnit); - if (ack == null) { - throw new RemoteServiceAckTimeoutException("No ACK response after " + ackTimeUnit.toMillis(ackTimeout) + "ms for request: " + request); + + RBlockingQueue responseQueue = null; + if (optionsCopy.isAckExpected() || optionsCopy.isResultExpected()) { + String responseName = name + ":{" + remoteInterface.getName() + "}:" + requestId; + responseQueue = redisson.getBlockingQueue(responseName); } - - RemoteServiceResponse response = (RemoteServiceResponse) responseQueue.poll(executionTimeout, executionTimeUnit); - if (response == null) { - throw new RemoteServiceTimeoutException("No response after " + executionTimeUnit.toMillis(executionTimeout) + "ms for request: " + request); + + // poll for the ack only if expected + if (optionsCopy.isAckExpected()) { + RemoteServiceAck ack = (RemoteServiceAck) responseQueue.poll(optionsCopy.getAckTimeoutInMillis(), TimeUnit.MILLISECONDS); + if (ack == null) { + throw new RemoteServiceAckTimeoutException("No ACK response after " + optionsCopy.getAckTimeoutInMillis() + "ms for request: " + request); + } } - if (response.getError() != null) { - throw response.getError(); + + // poll for the response only if expected + if (optionsCopy.isResultExpected()) { + RemoteServiceResponse response = (RemoteServiceResponse) responseQueue.poll(optionsCopy.getExecutionTimeoutInMillis(), TimeUnit.MILLISECONDS); + if (response == null) { + throw new RemoteServiceTimeoutException("No response after " + optionsCopy.getExecutionTimeoutInMillis() + "ms for request: " + request); + } + if (response.getError() != null) { + throw response.getError(); + } + return response.getResult(); } - return response.getResult(); + + return null; } }; - return (T) Proxy.newProxyInstance(remoteInterface.getClassLoader(), new Class[] {remoteInterface}, handler); + return (T) Proxy.newProxyInstance(remoteInterface.getClassLoader(), new Class[]{remoteInterface}, handler); } private String generateRequestId() { @@ -237,5 +261,4 @@ public class RedissonRemoteService implements RRemoteService { queue.expireAsync(timeout, TimeUnit.MILLISECONDS); return batch.executeAsync(); } - } diff --git a/src/main/java/org/redisson/core/RRemoteService.java b/src/main/java/org/redisson/core/RRemoteService.java index f5449b664..1980cbfff 100644 --- a/src/main/java/org/redisson/core/RRemoteService.java +++ b/src/main/java/org/redisson/core/RRemoteService.java @@ -77,21 +77,32 @@ public interface RRemoteService { /** * Get remote service object for remote invocations. *

- * Ack timeout = 1000 ms by default - *

- * Execution timeout = 30 sec by default - * + * This method is a shortcut for + *

+     *     get(remoteInterface, RemoteInvocationOptions.defaults())
+     * 
+ * + * @see RemoteInvocationOptions#defaults() + * @see #get(Class, RemoteInvocationOptions) + * * @param remoteInterface * @return */ T get(Class remoteInterface); - + /** * Get remote service object for remote invocations * with specified invocation timeout. - *

- * Ack timeout = 1000 ms by default - * + *

+ * This method is a shortcut for + *

+     *     get(remoteInterface, RemoteInvocationOptions.defaults()
+     *      .expectResultWithin(executionTimeout, executionTimeUnit))
+     * 
+ * + * @see RemoteInvocationOptions#defaults() + * @see #get(Class, RemoteInvocationOptions) + * * @param remoteInterface * @param executionTimeout - invocation timeout * @param executionTimeUnit @@ -102,7 +113,17 @@ public interface RRemoteService { /** * Get remote service object for remote invocations * with specified invocation and ack timeouts - * + *

+ * This method is a shortcut for + *

+     *     get(remoteInterface, RemoteInvocationOptions.defaults()
+     *      .expectAckWithin(ackTimeout, ackTimeUnit)
+     *      .expectResultWithin(executionTimeout, executionTimeUnit))
+     * 
+ * + * @see RemoteInvocationOptions + * @see #get(Class, RemoteInvocationOptions) + * * @param remoteInterface * @param executionTimeout - invocation timeout * @param executionTimeUnit @@ -111,5 +132,17 @@ public interface RRemoteService { * @return */ T get(Class remoteInterface, long executionTimeout, TimeUnit executionTimeUnit, long ackTimeout, TimeUnit ackTimeUnit); - + + /** + * Get remote service object for remote invocations + * with the specified options + *

+ * Note that when using the noResult() option, + * it is expected that the invoked method returns void, + * or else IllegalArgumentException will be thrown. + * + * @see RemoteInvocationOptions + */ + T get(Class remoteInterface, RemoteInvocationOptions options); + } diff --git a/src/main/java/org/redisson/core/RemoteInvocationOptions.java b/src/main/java/org/redisson/core/RemoteInvocationOptions.java new file mode 100644 index 000000000..265ea81a6 --- /dev/null +++ b/src/main/java/org/redisson/core/RemoteInvocationOptions.java @@ -0,0 +1,141 @@ +/** + * Copyright 2014 Nikita Koksharov, Nickolay Borbit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.core; + +import java.io.Serializable; +import java.util.concurrent.TimeUnit; + +/** + * RRemoteService invocation options. + * + * Used to tune how RRemoteService will behave + * in regard to the remote invocations acknowledgement + * and execution timeout. + *

+ * Examples: + *

+ *     // 1 second ack timeout and 30 seconds execution timeout
+ *     RemoteInvocationOptions options =
+ *          RemoteInvocationOptions.defaults();
+ *
+ *     // no ack but 30 seconds execution timeout
+ *     RemoteInvocationOptions options =
+ *          RemoteInvocationOptions.defaults()
+ *              .noAck();
+ *
+ *     // 1 second ack timeout then forget the result
+ *     RemoteInvocationOptions options =
+ *          RemoteInvocationOptions.defaults()
+ *              .noResult();
+ *
+ *     // 1 minute ack timeout then forget about the result
+ *     RemoteInvocationOptions options =
+ *          RemoteInvocationOptions.defaults()
+ *              .expectAckWithin(1, TimeUnit.MINUTES)
+ *              .noResult();
+ *
+ *     // no ack and forget about the result (fire and forget)
+ *     RemoteInvocationOptions options =
+ *          RemoteInvocationOptions.defaults()
+ *              .noAck()
+ *              .noResult();
+ * 
+ * + * @see RRemoteService#get(Class, RemoteInvocationOptions) + */ +public class RemoteInvocationOptions implements Serializable { + + private Long ackTimeoutInMillis; + private Long executionTimeoutInMillis; + + private RemoteInvocationOptions() { + } + + public RemoteInvocationOptions(RemoteInvocationOptions copy) { + this.ackTimeoutInMillis = copy.ackTimeoutInMillis; + this.executionTimeoutInMillis = copy.executionTimeoutInMillis; + } + + /** + * Creates a new instance of RemoteInvocationOptions with opinionated defaults. + *

+ * This is equivalent to: + *

+     *     new RemoteInvocationOptions()
+     *      .expectAckWithin(1, TimeUnit.SECONDS)
+     *      .expectResultWithin(30, TimeUnit.SECONDS)
+     * 
+ */ + public static RemoteInvocationOptions defaults() { + return new RemoteInvocationOptions() + .expectAckWithin(1, TimeUnit.SECONDS) + .expectResultWithin(20, TimeUnit.SECONDS); + } + + public Long getAckTimeoutInMillis() { + return ackTimeoutInMillis; + } + + public Long getExecutionTimeoutInMillis() { + return executionTimeoutInMillis; + } + + public boolean isAckExpected() { + return ackTimeoutInMillis != null; + } + + public boolean isResultExpected() { + return executionTimeoutInMillis != null; + } + + public RemoteInvocationOptions expectAckWithin(long ackTimeoutInMillis) { + this.ackTimeoutInMillis = ackTimeoutInMillis; + return this; + } + + public RemoteInvocationOptions expectAckWithin(long ackTimeout, TimeUnit timeUnit) { + this.ackTimeoutInMillis = timeUnit.toMillis(ackTimeout); + return this; + } + + public RemoteInvocationOptions noAck() { + ackTimeoutInMillis = null; + return this; + } + + public RemoteInvocationOptions expectResultWithin(long executionTimeoutInMillis) { + this.executionTimeoutInMillis = executionTimeoutInMillis; + return this; + } + + public RemoteInvocationOptions expectResultWithin(long executionTimeout, TimeUnit timeUnit) { + this.executionTimeoutInMillis = timeUnit.toMillis(executionTimeout); + return this; + } + + public RemoteInvocationOptions noResult() { + executionTimeoutInMillis = null; + return this; + } + + @Override + public String toString() { + return "RemoteInvocationOptions[" + + "ackTimeoutInMillis=" + ackTimeoutInMillis + + ", executionTimeoutInMillis=" + executionTimeoutInMillis + + ']'; + } +} diff --git a/src/main/java/org/redisson/remote/RemoteServiceRequest.java b/src/main/java/org/redisson/remote/RemoteServiceRequest.java index 3b65c85fb..adc4267f8 100644 --- a/src/main/java/org/redisson/remote/RemoteServiceRequest.java +++ b/src/main/java/org/redisson/remote/RemoteServiceRequest.java @@ -15,6 +15,8 @@ */ package org.redisson.remote; +import org.redisson.core.RemoteInvocationOptions; + import java.io.Serializable; import java.util.Arrays; @@ -23,36 +25,26 @@ public class RemoteServiceRequest implements Serializable { private String requestId; private String methodName; private Object[] args; - private long ackTimeout; - private long responseTimeout; + private RemoteInvocationOptions options; private long date; public RemoteServiceRequest() { } - public RemoteServiceRequest(String requestId, String methodName, Object[] args, long ackTimeout, long responseTimeout, long date) { + public RemoteServiceRequest(String requestId, String methodName, Object[] args, RemoteInvocationOptions options, long date) { super(); this.requestId = requestId; this.methodName = methodName; this.args = args; - this.ackTimeout = ackTimeout; - this.responseTimeout = responseTimeout; + this.options = options; this.date = date; } - public long getResponseTimeout() { - return responseTimeout; - } - public long getDate() { return date; } - public long getAckTimeout() { - return ackTimeout; - } - public String getRequestId() { return requestId; } @@ -60,7 +52,11 @@ public class RemoteServiceRequest implements Serializable { public Object[] getArgs() { return args; } - + + public RemoteInvocationOptions getOptions() { + return options; + } + public String getMethodName() { return methodName; } @@ -68,7 +64,7 @@ public class RemoteServiceRequest implements Serializable { @Override public String toString() { return "RemoteServiceRequest [requestId=" + requestId + ", methodName=" + methodName + ", args=" - + Arrays.toString(args) + ", ackTimeout=" + ackTimeout + ", date=" + date + "]"; + + Arrays.toString(args) + ", options=" + options + ", date=" + date + "]"; } } diff --git a/src/test/java/org/redisson/RedissonRemoteServiceTest.java b/src/test/java/org/redisson/RedissonRemoteServiceTest.java index 1b6d6aa3d..85e7f015f 100644 --- a/src/test/java/org/redisson/RedissonRemoteServiceTest.java +++ b/src/test/java/org/redisson/RedissonRemoteServiceTest.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import org.redisson.codec.FstCodec; import org.redisson.codec.SerializationCodec; +import org.redisson.core.RemoteInvocationOptions; +import org.redisson.remote.RemoteServiceAckTimeoutException; import org.redisson.remote.RemoteServiceTimeoutException; import java.io.IOException; @@ -61,7 +63,7 @@ public class RedissonRemoteServiceTest extends BaseTest { public interface RemoteInterface { void voidMethod(String name, Long param); - + Long resultMethod(Long value); void errorMethod() throws IOException; @@ -82,7 +84,7 @@ public class RedissonRemoteServiceTest extends BaseTest { public void voidMethod(String name, Long param) { System.out.println(name + " " + param); } - + @Override public Long resultMethod(Long value) { return value*2; @@ -248,19 +250,33 @@ public class RedissonRemoteServiceTest extends BaseTest { @Test public void testInvocationWithServiceName() { - String name = "MyServiceName"; + RedissonClient server = Redisson.create(); + RedissonClient client = Redisson.create(); - RedissonClient r1 = Redisson.create(); - r1.getRemoteSerivce(name).register(RemoteInterface.class, new RemoteImpl()); + server.getRemoteSerivce("MyServiceNamespace").register(RemoteInterface.class, new RemoteImpl()); - RedissonClient r2 = Redisson.create(); - RemoteInterface ri = r2.getRemoteSerivce(name).get(RemoteInterface.class); + RemoteInterface serviceRemoteInterface = client.getRemoteSerivce("MyServiceNamespace").get(RemoteInterface.class); + RemoteInterface otherServiceRemoteInterface = client.getRemoteSerivce("MyOtherServiceNamespace").get(RemoteInterface.class); + RemoteInterface defaultServiceRemoteInterface = client.getRemoteSerivce().get(RemoteInterface.class); - ri.voidMethod("someName", 100L); - assertThat(ri.resultMethod(100L)).isEqualTo(200); + assertThat(serviceRemoteInterface.resultMethod(21L)).isEqualTo(42L); - r1.shutdown(); - r2.shutdown(); + try { + otherServiceRemoteInterface.resultMethod(21L); + Assert.fail("Invoking a service in an unregistered custom services namespace should throw"); + } catch (Exception e) { + assertThat(e).isInstanceOf(RemoteServiceAckTimeoutException.class); + } + + try { + defaultServiceRemoteInterface.resultMethod(21L); + Assert.fail("Invoking a service in the unregistered default services namespace should throw"); + } catch (Exception e) { + assertThat(e).isInstanceOf(RemoteServiceAckTimeoutException.class); + } + + client.shutdown(); + server.shutdown(); } @Test @@ -362,4 +378,143 @@ public class RedissonRemoteServiceTest extends BaseTest { server.shutdown(); } } + + @Test + public void testNoAckWithResultInvocations() throws InterruptedException { + RedissonClient server = Redisson.create(); + RedissonClient client = Redisson.create(); + try { + server.getRemoteSerivce().register(RemoteInterface.class, new RemoteImpl()); + + // no ack but an execution timeout of 1 second + RemoteInvocationOptions options = RemoteInvocationOptions.defaults().noAck().expectResultWithin(1, TimeUnit.SECONDS); + RemoteInterface service = client.getRemoteSerivce().get(RemoteInterface.class, options); + + service.voidMethod("noAck", 100L); + assertThat(service.resultMethod(21L)).isEqualTo(42); + + try { + service.errorMethod(); + Assert.fail(); + } catch (IOException e) { + assertThat(e.getMessage()).isEqualTo("Checking error throw"); + } + + try { + service.errorMethodWithCause(); + Assert.fail(); + } catch (Exception e) { + assertThat(e.getCause()).isInstanceOf(ArithmeticException.class); + assertThat(e.getCause().getMessage()).isEqualTo("/ by zero"); + } + + try { + service.timeoutMethod(); + Assert.fail("noAck option should still wait for the server to return a response and throw if the execution timeout is exceeded"); + } catch (Exception e) { + assertThat(e).isInstanceOf(RemoteServiceTimeoutException.class); + } + } finally { + client.shutdown(); + server.shutdown(); + } + } + + @Test + public void testAckWithoutResultInvocations() throws InterruptedException { + RedissonClient server = Redisson.create(); + RedissonClient client = Redisson.create(); + try { + server.getRemoteSerivce().register(RemoteInterface.class, new RemoteImpl()); + + // fire and forget with an ack timeout of 1 sec + RemoteInvocationOptions options = RemoteInvocationOptions.defaults().expectAckWithin(1, TimeUnit.SECONDS).noResult(); + RemoteInterface service = client.getRemoteSerivce().get(RemoteInterface.class, options); + + service.voidMethod("noResult", 100L); + + try { + service.resultMethod(100L); + Assert.fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(IllegalArgumentException.class); + } + + try { + service.errorMethod(); + } catch (IOException e) { + Assert.fail("noResult option should not throw server side exception"); + } + + try { + service.errorMethodWithCause(); + } catch (Exception e) { + Assert.fail("noResult option should not throw server side exception"); + } + + long time = System.currentTimeMillis(); + service.timeoutMethod(); + time = System.currentTimeMillis() - time; + assertThat(time).describedAs("noResult option should not wait for the server to return a response").isLessThan(2000); + + try { + service.timeoutMethod(); + Assert.fail("noResult option should still wait for the server to ack the request and throw if the ack timeout is exceeded"); + } catch (Exception e) { + assertThat(e).isInstanceOf(RemoteServiceAckTimeoutException.class); + } + } finally { + client.shutdown(); + server.shutdown(); + } + } + + @Test + public void testNoAckWithoutResultInvocations() throws InterruptedException { + RedissonClient server = Redisson.create(); + RedissonClient client = Redisson.create(); + try { + server.getRemoteSerivce().register(RemoteInterface.class, new RemoteImpl()); + + // no ack fire and forget + RemoteInvocationOptions options = RemoteInvocationOptions.defaults().noAck().noResult(); + RemoteInterface service = client.getRemoteSerivce().get(RemoteInterface.class, options); + RemoteInterface invalidService = client.getRemoteSerivce("Invalid").get(RemoteInterface.class, options); + + service.voidMethod("noAck/noResult", 100L); + + try { + service.resultMethod(100L); + Assert.fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(IllegalArgumentException.class); + } + + try { + service.errorMethod(); + } catch (IOException e) { + Assert.fail("noAck with noResult options should not throw server side exception"); + } + + try { + service.errorMethodWithCause(); + } catch (Exception e) { + Assert.fail("noAck with noResult options should not throw server side exception"); + } + + long time = System.currentTimeMillis(); + service.timeoutMethod(); + time = System.currentTimeMillis() - time; + assertThat(time).describedAs("noAck with noResult options should not wait for the server to return a response").isLessThan(2000); + + try { + invalidService.voidMethod("noAck/noResult", 21L); + } catch (Exception e) { + Assert.fail("noAck with noResult options should not throw any exception even while invoking a service in an unregistered services namespace"); + } + } finally { + client.shutdown(); + server.shutdown(); + } + } }