package org.redisson; import java.io.IOException; import java.io.NotSerializableException; import java.io.Serializable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import static org.assertj.core.api.Assertions.assertThat; import org.junit.Assert; import org.junit.Test; import static org.redisson.BaseTest.createConfig; import static org.redisson.BaseTest.createInstance; import org.redisson.api.RFuture; import org.redisson.api.RedissonClient; import org.redisson.api.RemoteInvocationOptions; import org.redisson.api.annotation.RRemoteAsync; import org.redisson.codec.FstCodec; import org.redisson.codec.SerializationCodec; import org.redisson.remote.RemoteServiceAckTimeoutException; import org.redisson.remote.RemoteServiceTimeoutException; public class RedissonRemoteServiceTest extends BaseTest { public static class Pojo { private String stringField; public Pojo() { } public Pojo(String stringField) { this.stringField = stringField; } public String getStringField() { return stringField; } public void setStringField(String stringField) { this.stringField = stringField; } } public static class SerializablePojo implements Serializable { private String stringField; public SerializablePojo() { } public SerializablePojo(String stringField) { this.stringField = stringField; } public String getStringField() { return stringField; } public void setStringField(String stringField) { this.stringField = stringField; } } @RRemoteAsync(RemoteInterface.class) public interface RemoteInterfaceAsync { RFuture<Void> cancelMethod(); RFuture<Void> voidMethod(String name, Long param); RFuture<Long> resultMethod(Long value); RFuture<Void> errorMethod(); RFuture<Void> errorMethodWithCause(); RFuture<Void> timeoutMethod(); } @RRemoteAsync(RemoteInterface.class) public interface RemoteInterfaceWrongMethodAsync { RFuture<Void> voidMethod1(String name, Long param); RFuture<Long> resultMethod(Long value); } @RRemoteAsync(RemoteInterface.class) public interface RemoteInterfaceWrongParamsAsync { RFuture<Void> voidMethod(Long param, String name); RFuture<Long> resultMethod(Long value); } public interface RemoteInterface { void cancelMethod() throws InterruptedException; void voidMethod(String name, Long param); Long resultMethod(Long value); void errorMethod() throws IOException; void errorMethodWithCause(); void timeoutMethod() throws InterruptedException; Pojo doSomethingWithPojo(Pojo pojo); SerializablePojo doSomethingWithSerializablePojo(SerializablePojo pojo); String methodOverload(); String methodOverload(String str); String methodOverload(Long lng); String methodOverload(String str, Long lng); } public static class RemoteImpl implements RemoteInterface { private AtomicInteger iterations; public RemoteImpl() { } public RemoteImpl(AtomicInteger iterations) { super(); this.iterations = iterations; } @Override public void cancelMethod() throws InterruptedException { for (long i = 0; i < Long.MAX_VALUE; i++) { iterations.incrementAndGet(); if (Thread.currentThread().isInterrupted()) { System.out.println("interrupted! " + i); return; } } } @Override public void voidMethod(String name, Long param) { System.out.println(name + " " + param); } @Override public Long resultMethod(Long value) { return value*2; } @Override public void errorMethod() throws IOException { throw new IOException("Checking error throw"); } @Override public void errorMethodWithCause() { try { int s = 2 / 0; } catch (Exception e) { throw new RuntimeException("Checking error throw", e); } } @Override public void timeoutMethod() throws InterruptedException { Thread.sleep(2000); } @Override public Pojo doSomethingWithPojo(Pojo pojo) { return pojo; } @Override public SerializablePojo doSomethingWithSerializablePojo(SerializablePojo pojo) { return pojo; } @Override public String methodOverload() { return "methodOverload()"; } @Override public String methodOverload(Long lng) { return "methodOverload(Long lng)"; } @Override public String methodOverload(String str) { return "methodOverload(String str)"; } @Override public String methodOverload(String str, Long lng) { return "methodOverload(String str, Long lng)"; } } @Test public void testCancelAsync() throws InterruptedException { RedissonClient r1 = createInstance(); AtomicInteger iterations = new AtomicInteger(); ExecutorService executor = Executors.newSingleThreadExecutor(); r1.getKeys().flushall(); r1.getRemoteService().register(RemoteInterface.class, new RemoteImpl(iterations), 1, executor); RedissonClient r2 = createInstance(); RemoteInterfaceAsync ri = r2.getRemoteService().get(RemoteInterfaceAsync.class); RFuture<Void> f = ri.cancelMethod(); Thread.sleep(500); assertThat(f.cancel(true)).isTrue(); executor.shutdown(); r1.shutdown(); r2.shutdown(); assertThat(iterations.get()).isLessThan(Integer.MAX_VALUE / 2); assertThat(executor.awaitTermination(1, TimeUnit.SECONDS)).isTrue(); } @Test(expected = IllegalArgumentException.class) public void testWrongMethodAsync() throws InterruptedException { redisson.getRemoteService().get(RemoteInterfaceWrongMethodAsync.class); } @Test(expected = IllegalArgumentException.class) public void testWrongParamsAsync() throws InterruptedException { redisson.getRemoteService().get(RemoteInterfaceWrongParamsAsync.class); } @Test public void testAsync() throws InterruptedException { RedissonClient r1 = createInstance(); r1.getRemoteService().register(RemoteInterface.class, new RemoteImpl()); RedissonClient r2 = createInstance(); RemoteInterfaceAsync ri = r2.getRemoteService().get(RemoteInterfaceAsync.class); RFuture<Void> f = ri.voidMethod("someName", 100L); f.sync(); RFuture<Long> resFuture = ri.resultMethod(100L); resFuture.sync(); assertThat(resFuture.getNow()).isEqualTo(200); r1.shutdown(); r2.shutdown(); } @Test public void testExecutorAsync() throws InterruptedException { RedissonClient r1 = createInstance(); ExecutorService executor = Executors.newSingleThreadExecutor(); r1.getRemoteService().register(RemoteInterface.class, new RemoteImpl(), 1, executor); RedissonClient r2 = createInstance(); RemoteInterfaceAsync ri = r2.getRemoteService().get(RemoteInterfaceAsync.class); RFuture<Void> f = ri.voidMethod("someName", 100L); f.sync(); RFuture<Long> resFuture = ri.resultMethod(100L); resFuture.sync(); assertThat(resFuture.getNow()).isEqualTo(200); r1.shutdown(); r2.shutdown(); executor.shutdown(); executor.awaitTermination(1, TimeUnit.MINUTES); } @Test public void testExecutorsAmountConcurrency() throws InterruptedException { // Redisson server and client final RedissonClient server = createInstance(); final RedissonClient client = createInstance(); final int serverAmount = 1; final int clientAmount = 10; // to store the current call concurrency count final AtomicInteger concurrency = new AtomicInteger(0); // a flag to indicate the the allowed concurrency was exceeded final AtomicBoolean concurrencyIsExceeded = new AtomicBoolean(false); // the server: register a service with an overrided timeoutMethod method that: // - incr the concurrency // - check if concurrency is greater than what was allowed, and if yes set the concurrencyOfOneIsExceeded flag // - wait 2s // - decr the concurrency server.getRemoteService().register(RemoteInterface.class, new RemoteImpl() { @Override public void timeoutMethod() throws InterruptedException { try { if (concurrency.incrementAndGet() > serverAmount) { concurrencyIsExceeded.compareAndSet(false, true); } super.timeoutMethod(); } finally { concurrency.decrementAndGet(); } } }, serverAmount); // a latch to force the client threads to execute simultaneously // (as far as practicable, this is hard to predict) final CountDownLatch readyLatch = new CountDownLatch(1); // the client: starts a couple of threads that will: // - await for the ready latch // - then call timeoutMethod java.util.concurrent.Future[] clientFutures = new java.util.concurrent.Future[clientAmount]; ExecutorService executor = Executors.newFixedThreadPool(clientAmount); for (int i = 0; i < clientAmount; i++) { clientFutures[i] = executor.submit(new Runnable() { @Override public void run() { try { RemoteInterface ri = client.getRemoteService().get(RemoteInterface.class, clientAmount * 3, TimeUnit.SECONDS, clientAmount * 3, TimeUnit.SECONDS); readyLatch.await(); ri.timeoutMethod(); } catch (InterruptedException e) { // ignore } } }); } // open the latch to wake the threads readyLatch.countDown(); // await for the client threads to terminate for (java.util.concurrent.Future clientFuture : clientFutures) { try { clientFuture.get(); } catch (ExecutionException e) { // ignore } } executor.shutdown(); executor.awaitTermination(clientAmount * 3, TimeUnit.SECONDS); // shutdown the server and the client server.shutdown(); client.shutdown(); // do the concurrencyIsExceeded flag was set ? // if yes, that would indicate that the server exceeded its expected concurrency assertThat(concurrencyIsExceeded.get()).isEqualTo(false); } @Test(expected = RemoteServiceTimeoutException.class) public void testTimeout() throws InterruptedException { RedissonClient r1 = createInstance(); r1.getRemoteService().register(RemoteInterface.class, new RemoteImpl()); RedissonClient r2 = createInstance(); RemoteInterface ri = r2.getRemoteService().get(RemoteInterface.class, 1, TimeUnit.SECONDS); try { ri.timeoutMethod(); } finally { r1.shutdown(); r2.shutdown(); } } @Test public void testInvocations() { RedissonClient r1 = createInstance(); r1.getRemoteService().register(RemoteInterface.class, new RemoteImpl()); RedissonClient r2 = createInstance(); RemoteInterface ri = r2.getRemoteService().get(RemoteInterface.class); ri.voidMethod("someName", 100L); assertThat(ri.resultMethod(100L)).isEqualTo(200); try { ri.errorMethod(); Assert.fail(); } catch (IOException e) { assertThat(e.getMessage()).isEqualTo("Checking error throw"); } try { ri.errorMethodWithCause(); Assert.fail(); } catch (Exception e) { assertThat(e.getCause()).isInstanceOf(ArithmeticException.class); assertThat(e.getCause().getMessage()).isEqualTo("/ by zero"); } r1.shutdown(); r2.shutdown(); } @Test public void testInvocationWithServiceName() { RedissonClient server = createInstance(); RedissonClient client = createInstance(); server.getRemoteService("MyServiceNamespace").register(RemoteInterface.class, new RemoteImpl()); RemoteInterface serviceRemoteInterface = client.getRemoteService("MyServiceNamespace").get(RemoteInterface.class); RemoteInterface otherServiceRemoteInterface = client.getRemoteService("MyOtherServiceNamespace").get(RemoteInterface.class); RemoteInterface defaultServiceRemoteInterface = client.getRemoteService().get(RemoteInterface.class); assertThat(serviceRemoteInterface.resultMethod(21L)).isEqualTo(42L); 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 public void testProxyToStringEqualsAndHashCode() { RedissonClient client = createInstance(); try { RemoteInterface service = client.getRemoteService().get(RemoteInterface.class); try { System.out.println(service.toString()); } catch (Exception e) { Assert.fail("calling toString on the client service proxy should not make a remote call"); } try { assertThat(service.hashCode() == service.hashCode()).isTrue(); } catch (Exception e) { Assert.fail("calling hashCode on the client service proxy should not make a remote call"); } try { assertThat(service.equals(service)).isTrue(); } catch (Exception e) { Assert.fail("calling equals on the client service proxy should not make a remote call"); } } finally { client.shutdown(); } } @Test public void testInvocationWithFstCodec() { RedissonClient server = Redisson.create(createConfig().setCodec(new FstCodec())); RedissonClient client = Redisson.create(createConfig().setCodec(new FstCodec())); try { server.getRemoteService().register(RemoteInterface.class, new RemoteImpl()); RemoteInterface service = client.getRemoteService().get(RemoteInterface.class); assertThat(service.resultMethod(21L)).as("Should be compatible with FstCodec").isEqualTo(42L); try { assertThat(service.doSomethingWithSerializablePojo(new SerializablePojo("test")).getStringField()).isEqualTo("test"); } catch (Exception e) { Assert.fail("Should be compatible with FstCodec"); } try { assertThat(service.doSomethingWithPojo(new Pojo("test")).getStringField()).isEqualTo("test"); Assert.fail("FstCodec should not be able to serialize a not serializable class"); } catch (Exception e) { assertThat(e.getCause()).isInstanceOf(RuntimeException.class); assertThat(e.getCause().getMessage()).contains("Pojo does not implement Serializable"); } } finally { client.shutdown(); server.shutdown(); } } @Test public void testInvocationWithSerializationCodec() { RedissonClient server = Redisson.create(createConfig().setCodec(new SerializationCodec())); RedissonClient client = Redisson.create(createConfig().setCodec(new SerializationCodec())); try { server.getRemoteService().register(RemoteInterface.class, new RemoteImpl()); RemoteInterface service = client.getRemoteService().get(RemoteInterface.class); try { assertThat(service.resultMethod(21L)).isEqualTo(42L); } catch (Exception e) { Assert.fail("Should be compatible with SerializationCodec"); } try { assertThat(service.doSomethingWithSerializablePojo(new SerializablePojo("test")).getStringField()).isEqualTo("test"); } catch (Exception e) { e.printStackTrace(); Assert.fail("Should be compatible with SerializationCodec"); } try { assertThat(service.doSomethingWithPojo(new Pojo("test")).getStringField()).isEqualTo("test"); Assert.fail("SerializationCodec should not be able to serialize a not serializable class"); } catch (Exception e) { e.printStackTrace(); assertThat(e.getCause()).isInstanceOf(NotSerializableException.class); assertThat(e.getCause().getMessage()).contains("Pojo"); } } finally { client.shutdown(); server.shutdown(); } } @Test public void testNoAckWithResultInvocations() throws InterruptedException { RedissonClient server = createInstance(); RedissonClient client = createInstance(); try { server.getRemoteService().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.getRemoteService().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 testNoAckWithResultInvocationsAsync() throws InterruptedException, ExecutionException { RedissonClient server = createInstance(); RedissonClient client = createInstance(); try { server.getRemoteService().register(RemoteInterface.class, new RemoteImpl()); // no ack but an execution timeout of 1 second RemoteInvocationOptions options = RemoteInvocationOptions.defaults().noAck().expectResultWithin(1, TimeUnit.SECONDS); RemoteInterfaceAsync service = client.getRemoteService().get(RemoteInterfaceAsync.class, options); service.voidMethod("noAck", 100L).get(); assertThat(service.resultMethod(21L).get()).isEqualTo(42); try { service.errorMethod().get(); Assert.fail(); } catch (Exception e) { assertThat(e.getCause().getMessage()).isEqualTo("Checking error throw"); } try { service.errorMethodWithCause().get(); Assert.fail(); } catch (Exception e) { assertThat(e.getCause().getCause()).isInstanceOf(ArithmeticException.class); assertThat(e.getCause().getCause().getMessage()).isEqualTo("/ by zero"); } try { service.timeoutMethod().get(); 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.getCause()).isInstanceOf(RemoteServiceTimeoutException.class); } } finally { client.shutdown(); server.shutdown(); } } @Test public void testAckWithoutResultInvocations() throws InterruptedException { RedissonClient server = createInstance(); RedissonClient client = createInstance(); try { server.getRemoteService().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.getRemoteService().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 = createInstance(); RedissonClient client = createInstance(); try { server.getRemoteService().register(RemoteInterface.class, new RemoteImpl()); // no ack fire and forget RemoteInvocationOptions options = RemoteInvocationOptions.defaults().noAck().noResult(); RemoteInterface service = client.getRemoteService().get(RemoteInterface.class, options); RemoteInterface invalidService = client.getRemoteService("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(); } } @Test public void testMethodOverload() { RedissonClient r1 = createInstance(); r1.getRemoteService().register(RemoteInterface.class, new RemoteImpl()); RedissonClient r2 = createInstance(); RemoteInterface ri = r2.getRemoteService().get(RemoteInterface.class); assertThat(ri.methodOverload()).isEqualTo("methodOverload()"); assertThat(ri.methodOverload(1l)).isEqualTo("methodOverload(Long lng)"); assertThat(ri.methodOverload("")).isEqualTo("methodOverload(String str)"); assertThat(ri.methodOverload("", 1l)).isEqualTo("methodOverload(String str, Long lng)"); r1.shutdown(); r2.shutdown(); } }