proof of concept for unacknowledged and/or fire-and-forget calls in RRemoteService

pull/497/head
Pierre-David Bélanger 9 years ago
parent 7b85da1f21
commit b2e58d6a25

@ -110,7 +110,8 @@ public class RedissonRemoteService implements RRemoteService {
// subscribe(remoteInterface, requestQueue);
final RemoteServiceRequest request = future.getNow();
if (System.currentTimeMillis() - request.getDate() > request.getAckTimeout()) {
// negative ackTimeout means unacknowledged call, do not check the ack
if (request.getAckTimeout() >= 0 && System.currentTimeMillis() - request.getDate() > request.getAckTimeout()) {
log.debug("request: {} has been skipped due to ackTimeout");
// re-subscribe after a skipped ackTimeout
subscribe(remoteInterface, requestQueue);
@ -120,6 +121,8 @@ public class RedissonRemoteService implements RRemoteService {
final RemoteServiceMethod method = beans.get(new RemoteServiceKey(remoteInterface, request.getMethodName()));
final String responseName = name + ":{" + remoteInterface.getName() + "}:" + request.getRequestId();
// negative ackTimeout means unacknowledged call, do not send the ack
if (request.getAckTimeout() >= 0) {
Future<List<?>> ackClientsFuture = send(request.getAckTimeout(), responseName, new RemoteServiceAck());
ackClientsFuture.addListener(new FutureListener<List<?>>() {
@Override
@ -137,6 +140,9 @@ public class RedissonRemoteService implements RRemoteService {
invokeMethod(remoteInterface, requestQueue, request, method, responseName);
}
});
} else {
invokeMethod(remoteInterface, requestQueue, request, method, responseName);
}
}
});
@ -154,6 +160,8 @@ public class RedissonRemoteService implements RRemoteService {
log.error("Can't execute: " + request, e);
}
// negative responseTimeout means fire-and-forget call, do not send the response
if (request.getResponseTimeout() >= 0) {
Future<List<?>> clientsFuture = send(request.getResponseTimeout(), responseName, responseHolder.get());
clientsFuture.addListener(new FutureListener<List<?>>() {
@Override
@ -168,6 +176,10 @@ public class RedissonRemoteService implements RRemoteService {
subscribe(remoteInterface, requestQueue);
}
});
} else {
// re-subscribe anyways after the method invocation
subscribe(remoteInterface, requestQueue);
}
}
@Override
@ -205,11 +217,16 @@ public class RedissonRemoteService implements RRemoteService {
String responseName = name + ":{" + remoteInterface.getName() + "}:" + requestId;
RBlockingQueue<RRemoteServiceResponse> responseQueue = redisson.getBlockingQueue(responseName);
// negative ackTimeout means unacknowledged call, do not poll for the ack
if (ackTimeout >= 0) {
RemoteServiceAck ack = (RemoteServiceAck) responseQueue.poll(ackTimeout, ackTimeUnit);
if (ack == null) {
throw new RemoteServiceAckTimeoutException("No ACK response after " + ackTimeUnit.toMillis(ackTimeout) + "ms for request: " + request);
}
}
// negative executionTimeout means fire-and-forget call, do not poll for the response
if (executionTimeout >= 0) {
RemoteServiceResponse response = (RemoteServiceResponse) responseQueue.poll(executionTimeout, executionTimeUnit);
if (response == null) {
throw new RemoteServiceTimeoutException("No response after " + executionTimeUnit.toMillis(executionTimeout) + "ms for request: " + request);
@ -219,6 +236,9 @@ public class RedissonRemoteService implements RRemoteService {
}
return response.getResult();
}
return getDefaultValue(method.getReturnType());
}
};
return (T) Proxy.newProxyInstance(remoteInterface.getClassLoader(), new Class[] {remoteInterface}, handler);
}
@ -238,4 +258,32 @@ public class RedissonRemoteService implements RRemoteService {
return batch.executeAsync();
}
/**
* Horrible hack to get the default value for a Class
*
* @param type the Class
* @return the default value as
*/
private static Object getDefaultValue(Class type) {
if (!type.isPrimitive() || type.equals(void.class)) {
return null;
} else if (type.equals(boolean.class)) {
return false;
} else if (type.equals(byte.class)) {
return (byte) 0;
} else if (type.equals(char.class)) {
return (char) 0;
} else if (type.equals(short.class)) {
return (short) 0;
} else if (type.equals(int.class)) {
return (int) 0;
} else if (type.equals(long.class)) {
return (long) 0;
} else if (type.equals(float.class)) {
return (float) 0;
} else if (type.equals(double.class)) {
return (double) 0;
}
throw new IllegalArgumentException("Class " + type + " not supported");
}
}

@ -91,6 +91,8 @@ public interface RRemoteService {
* with specified invocation timeout.
* <p/>
* Ack timeout = 1000 ms by default
* <p/>
* A negative executionTimeout will make fire-and-forget calls
*
* @param remoteInterface
* @param executionTimeout - invocation timeout
@ -102,6 +104,10 @@ public interface RRemoteService {
/**
* Get remote service object for remote invocations
* with specified invocation and ack timeouts
* <p/>
* A negative executionTimeout will make fire-and-forget calls
* <p/>
* A negative ackTimeout will make unacknowledged calls
*
* @param remoteInterface
* @param executionTimeout - invocation timeout

@ -62,6 +62,8 @@ public class RedissonRemoteServiceTest extends BaseTest {
void voidMethod(String name, Long param);
int primitiveMethod();
Long resultMethod(Long value);
void errorMethod() throws IOException;
@ -83,6 +85,11 @@ public class RedissonRemoteServiceTest extends BaseTest {
System.out.println(name + " " + param);
}
@Override
public int primitiveMethod() {
return 42;
}
@Override
public Long resultMethod(Long value) {
return value*2;
@ -362,4 +369,75 @@ public class RedissonRemoteServiceTest extends BaseTest {
server.shutdown();
}
}
@Test
public void testUnacknowledgedInvocations() throws InterruptedException {
RedissonClient r1 = Redisson.create();
r1.getRemoteSerivce().register(RemoteInterface.class, new RemoteImpl());
RedissonClient r2 = Redisson.create();
RemoteInterface ri = r2.getRemoteSerivce().get(RemoteInterface.class, 30, TimeUnit.SECONDS, -1, TimeUnit.SECONDS);
ri.voidMethod("someName", 100L);
assertThat(ri.resultMethod(100L)).isEqualTo(200);
assertThat(ri.primitiveMethod()).isEqualTo(42);
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");
}
long time = System.currentTimeMillis();
ri.timeoutMethod();
time = System.currentTimeMillis() - time;
assertThat(time).describedAs("unacknowledged should still wait for the server to return a response").isGreaterThanOrEqualTo(2000);
r1.shutdown();
r2.shutdown();
}
@Test
public void testFireAndForgetInvocations() throws InterruptedException {
RedissonClient r1 = Redisson.create();
r1.getRemoteSerivce().register(RemoteInterface.class, new RemoteImpl());
RedissonClient r2 = Redisson.create();
RemoteInterface ri = r2.getRemoteSerivce().get(RemoteInterface.class, -1, TimeUnit.SECONDS);
ri.voidMethod("someName", 100L);
assertThat(ri.resultMethod(100L)).isNull();
assertThat(ri.primitiveMethod()).isEqualTo(0);
try {
ri.errorMethod();
} catch (IOException e) {
Assert.fail("fire-and-forget should not throw");
}
try {
ri.errorMethodWithCause();
} catch (Exception e) {
Assert.fail("fire-and-forget should not throw");
}
long time = System.currentTimeMillis();
ri.timeoutMethod();
time = System.currentTimeMillis() - time;
assertThat(time).describedAs("fire-and-forget should not wait for the server to return a response").isLessThan(2000);
r1.shutdown();
r2.shutdown();
}
}

Loading…
Cancel
Save