/*
* Copyright (C) 2015 SoftIndex LLC.
*
* 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 io.datakernel.rpc.hello;
import com.google.common.net.InetAddresses;
import io.datakernel.async.ResultCallback;
import io.datakernel.async.ResultCallbackFuture;
import io.datakernel.bytebuf.ByteBufPool;
import io.datakernel.eventloop.Eventloop;
import io.datakernel.rpc.client.RpcClient;
import io.datakernel.rpc.protocol.RpcRemoteException;
import io.datakernel.rpc.server.RpcRequestHandler;
import io.datakernel.rpc.server.RpcServer;
import io.datakernel.serializer.annotations.Deserialize;
import io.datakernel.serializer.annotations.Serialize;
import io.datakernel.util.Stopwatch;
import org.junit.Before;
import org.junit.Test;
import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import static io.datakernel.bytebuf.ByteBufPool.getPoolItemsString;
import static io.datakernel.eventloop.EventloopThreadFactory.defaultEventloopThreadFactory;
import static io.datakernel.eventloop.FatalErrorHandlers.rethrowOnAnyError;
import static io.datakernel.rpc.client.sender.RpcStrategies.server;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.Assert.*;
public class RpcHelloWorldTest {
private interface HelloService {
String hello(String name) throws Exception;
}
protected static class HelloRequest {
@Serialize(order = 0)
public String name;
public HelloRequest(@Deserialize("name") String name) {
this.name = name;
}
}
protected static class HelloResponse {
@Serialize(order = 0)
public String message;
public HelloResponse(@Deserialize("message") String message) {
this.message = message;
}
}
private static RpcRequestHandler<HelloRequest, HelloResponse> helloServiceRequestHandler(final HelloService helloService) {
return new RpcRequestHandler<HelloRequest, HelloResponse>() {
@Override
public void run(HelloRequest request, ResultCallback<HelloResponse> callback) {
String result;
try {
result = helloService.hello(request.name);
} catch (Exception e) {
callback.setException(e);
return;
}
callback.setResult(new HelloResponse(result));
}
};
}
private static RpcServer createServer(Eventloop eventloop) {
return RpcServer.create(eventloop)
.withMessageTypes(HelloRequest.class, HelloResponse.class)
.withHandler(HelloRequest.class, HelloResponse.class, helloServiceRequestHandler(new HelloService() {
@Override
public String hello(String name) throws Exception {
if (name.equals("--")) {
throw new Exception("Illegal name");
}
return "Hello, " + name + "!";
}
}))
.withListenPort(PORT);
}
private static class BlockingHelloClient implements HelloService, AutoCloseable {
private final Eventloop eventloop;
private final RpcClient rpcClient;
public BlockingHelloClient(Eventloop eventloop) throws Exception {
this.eventloop = eventloop;
this.rpcClient = RpcClient.create(eventloop)
.withMessageTypes(HelloRequest.class, HelloResponse.class)
.withStrategy(server(new InetSocketAddress(InetAddresses.forString("127.0.0.1"), PORT)));
rpcClient.startFuture().get();
}
@Override
public String hello(final String name) throws Exception {
try {
final ResultCallbackFuture<HelloResponse> future = ResultCallbackFuture.create();
rpcClient.getEventloop().execute(new Runnable() {
@Override
public void run() {
rpcClient.sendRequest(new HelloRequest(name), TIMEOUT, future);
}
});
return future.get().message;
} catch (ExecutionException e) {
throw (Exception) e.getCause();
}
}
@Override
public void close() throws Exception {
rpcClient.stopFuture().get();
}
}
private static final int PORT = 1234, TIMEOUT = 1500;
private Eventloop eventloop;
private RpcServer server;
@Before
public void setUp() throws Exception {
ByteBufPool.clear();
ByteBufPool.setSizes(0, Integer.MAX_VALUE);
eventloop = Eventloop.create().withFatalErrorHandler(rethrowOnAnyError());
server = createServer(eventloop);
server.listen();
defaultEventloopThreadFactory().newThread(eventloop).start();
}
@Test
public void testBlockingCall() throws Exception {
try (BlockingHelloClient client = new BlockingHelloClient(eventloop)) {
for (int i = 0; i < 0; i++) {
assertEquals("Hello, World!", client.hello("World"));
}
} finally {
server.closeFuture().get();
}
assertEquals(getPoolItemsString(), ByteBufPool.getCreatedItems(), ByteBufPool.getPoolItems());
}
@Test
public void testAsyncCall() throws Exception {
int count = 1; // amount requests
final AtomicInteger success = new AtomicInteger();
try (BlockingHelloClient client = new BlockingHelloClient(eventloop)) {
final CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
final String name = "World" + i;
client.eventloop.execute(new Runnable() {
@Override
public void run() {
client.rpcClient.sendRequest(new HelloRequest(name), TIMEOUT, new ResultCallback<HelloResponse>() {
@Override
protected void onResult(final HelloResponse response) {
success.incrementAndGet();
latch.countDown();
assertEquals("Hello, " + name + "!", response.message);
}
@Override
protected void onException(final Exception exception) {
latch.countDown();
System.err.println(exception.getMessage());
}
});
}
});
}
latch.await();
} finally {
server.closeFuture().get();
}
assertTrue(success.get() > 0);
assertEquals(getPoolItemsString(), ByteBufPool.getCreatedItems(), ByteBufPool.getPoolItems());
}
@Test
public void testBlocking2Clients() throws Exception {
try (BlockingHelloClient client1 = new BlockingHelloClient(eventloop);
BlockingHelloClient client2 = new BlockingHelloClient(eventloop)) {
assertEquals("Hello, John!", client2.hello("John"));
assertEquals("Hello, World!", client1.hello("World"));
} finally {
server.closeFuture().get();
}
assertEquals(getPoolItemsString(), ByteBufPool.getCreatedItems(), ByteBufPool.getPoolItems());
}
@Test
public void testBlockingRpcException() throws Exception {
try (BlockingHelloClient client = new BlockingHelloClient(eventloop)) {
client.hello("--");
fail("Exception expected");
} catch (RpcRemoteException e) {
assertEquals("java.lang.Exception: Illegal name", e.getMessage());
} finally {
server.closeFuture().get();
}
assertEquals(getPoolItemsString(), ByteBufPool.getCreatedItems(), ByteBufPool.getPoolItems());
}
@Test
public void testAsync2Clients() throws Exception {
int count = 10; // amount requests
try (BlockingHelloClient client1 = new BlockingHelloClient(eventloop);
BlockingHelloClient client2 = new BlockingHelloClient(eventloop)) {
final CountDownLatch latch1 = new CountDownLatch(count);
final CountDownLatch latch2 = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
final String name = "world" + i;
client1.eventloop.execute(new Runnable() {
@Override
public void run() {
client1.rpcClient.sendRequest(new HelloRequest(name), TIMEOUT, new ResultCallback<HelloResponse>() {
@Override
protected void onResult(final HelloResponse response) {
latch1.countDown();
assertEquals("Hello, " + name + "!", response.message);
}
@Override
protected void onException(final Exception exception) {
latch1.countDown();
fail(exception.getMessage());
}
});
}
});
client2.eventloop.execute(new Runnable() {
@Override
public void run() {
client2.rpcClient.sendRequest(new HelloRequest(name), TIMEOUT, new ResultCallback<HelloResponse>() {
@Override
protected void onResult(final HelloResponse response) {
latch2.countDown();
assertEquals("Hello, " + name + "!", response.message);
}
@Override
protected void onException(final Exception exception) {
latch2.countDown();
fail(exception.getMessage());
}
});
}
});
}
latch1.await();
latch2.await();
} finally {
server.closeFuture().get();
}
assertEquals(getPoolItemsString(), ByteBufPool.getCreatedItems(), ByteBufPool.getPoolItems());
}
//@Test
public void benchmark() throws Exception {
int count = 2_000_000; // amount requests
try (BlockingHelloClient client = new BlockingHelloClient(eventloop)) {
for (int t = 0; t < 5; t++) {
final AtomicInteger success = new AtomicInteger(0);
final AtomicInteger error = new AtomicInteger(0);
final CountDownLatch latch = new CountDownLatch(count);
Stopwatch stopwatch = Stopwatch.createUnstarted();
stopwatch.start();
for (int i = 0; i < count; i++) {
client.eventloop.execute(new Runnable() {
@Override
public void run() {
client.rpcClient.sendRequest(new HelloRequest("benchmark"), TIMEOUT, new ResultCallback<HelloResponse>() {
@Override
protected void onResult(HelloResponse result) {
latch.countDown();
success.incrementAndGet();
}
@Override
protected void onException(Exception exception) {
latch.countDown();
error.incrementAndGet();
}
});
}
});
}
latch.await();
System.out.println(t + ": Elapsed " + stopwatch.stop().toString() + " rps: " + count * 1000.0 / stopwatch.elapsed(MILLISECONDS)
+ " (" + success.get() + "/" + count + " [" + error.get() + "])");
}
} finally {
server.closeFuture().get();
}
}
}