From b424720c7cf31acccc654cc1edef31b039867b5d Mon Sep 17 00:00:00 2001 From: dponomarev Date: Mon, 4 Feb 2019 12:16:04 +0300 Subject: [PATCH 01/30] refactor --- .../RoundRobinSocketProviderImpl.java | 10 +- .../org/tarantool/TarantoolClusterClient.java | 9 +- .../TarantoolClusterClientConfig.java | 11 ++ .../tarantool/server/BinaryProtoUtils.java | 110 ++++++++++++++ .../server/TarantoolBinaryPackage.java | 26 ++++ .../org/tarantool/server/TarantoolServer.java | 135 ++++++++++++++++++ .../tarantool/ClientReconnectClusterIT.java | 3 +- 7 files changed, 295 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/tarantool/server/BinaryProtoUtils.java create mode 100644 src/main/java/org/tarantool/server/TarantoolBinaryPackage.java create mode 100644 src/main/java/org/tarantool/server/TarantoolServer.java diff --git a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java index d16c6bf4..38d4f3c8 100644 --- a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java +++ b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java @@ -24,13 +24,13 @@ public class RoundRobinSocketProviderImpl implements SocketChannelProvider { /** * Constructs an instance. * - * @param addrs Array of addresses in a form of [host]:[port]. + * @param slaveHosts Array of addresses in a form of [host]:[port]. */ - public RoundRobinSocketProviderImpl(String... addrs) { - if (addrs == null || addrs.length == 0) - throw new IllegalArgumentException("addrs is null or empty."); + public RoundRobinSocketProviderImpl(String[] slaveHosts) { + if (slaveHosts == null || slaveHosts.length == 0) + throw new IllegalArgumentException("slaveHosts is null or empty."); - this.addrs = Arrays.copyOf(addrs, addrs.length); + this.addrs = Arrays.copyOf(slaveHosts, slaveHosts.length); sockAddrs = new InetSocketAddress[this.addrs.length]; diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index 3a6c243a..78cec643 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -25,10 +25,9 @@ public class TarantoolClusterClient extends TarantoolClientImpl { /** * @param config Configuration. - * @param addrs Array of addresses in the form of [host]:[port]. */ - public TarantoolClusterClient(TarantoolClusterClientConfig config, String... addrs) { - this(config, new RoundRobinSocketProviderImpl(addrs).setTimeout(config.operationExpiryTimeMillis)); + public TarantoolClusterClient(TarantoolClusterClientConfig config) { + this(config, new RoundRobinSocketProviderImpl(config.slaveHosts).setTimeout(config.operationExpiryTimeMillis)); } /** @@ -42,6 +41,10 @@ public TarantoolClusterClient(TarantoolClusterClientConfig config, SocketChannel Executors.newSingleThreadExecutor() : config.executor; } + private void refreshServerList() { + + } + @Override protected boolean isDead(CompletableFuture q) { if ((state.getState() & CLOSED) != 0) { diff --git a/src/main/java/org/tarantool/TarantoolClusterClientConfig.java b/src/main/java/org/tarantool/TarantoolClusterClientConfig.java index 423896b3..d859a4aa 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClientConfig.java +++ b/src/main/java/org/tarantool/TarantoolClusterClientConfig.java @@ -11,4 +11,15 @@ public class TarantoolClusterClientConfig extends TarantoolClientConfig { /* Executor service that will be used as a thread of execution to retry writes. */ public Executor executor = null; + + /** + * Array of addresses in the form of [host]:[port]. + */ + public String[] slaveHosts; + + /** + * Array of addresses of tarantool instances that can act as providers of slaveHostList + */ + public String[] masterHosts; + } diff --git a/src/main/java/org/tarantool/server/BinaryProtoUtils.java b/src/main/java/org/tarantool/server/BinaryProtoUtils.java new file mode 100644 index 00000000..dd904055 --- /dev/null +++ b/src/main/java/org/tarantool/server/BinaryProtoUtils.java @@ -0,0 +1,110 @@ +package org.tarantool.server; + +import org.tarantool.Base64; +import org.tarantool.Code; +import org.tarantool.CountInputStreamImpl; +import org.tarantool.Key; +import org.tarantool.MsgPackLite; +import org.tarantool.server.TarantoolServer.CountingInputStream; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +public abstract class BinaryProtoUtils { + + private final static int DEFAULT_INITIAL_REQUEST_SIZE = 4096; + + + private TarantoolBinaryPackage readPacket(SocketChannel channel) throws IOException { + + int size = channel.socket().getInputStream().read(); + ByteBuffer msgBuffer = ByteBuffer.allocate(size - 1); + channel.read(msgBuffer); + + CountInputStreamImpl msgStream = new CountInputStreamImpl(new ByteArrayInputStream(msgBuffer.array())); + Map headers = (Map) getMsgPackLite().unpack(msgStream); + + + Map body = null; + if (msgStream.getBytesRead() < size) { + body = (Map) getMsgPackLite().unpack(msgStream); + } + + return new TarantoolBinaryPackage(headers, body); + } + + + protected static ByteBuffer createAuthPacket(String username, final String password, String salt) throws IOException { + final MessageDigest sha1; + try { + sha1 = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + List auth = new ArrayList(2); + auth.add("chap-sha1"); + + byte[] p = sha1.digest(password.getBytes()); + + sha1.reset(); + byte[] p2 = sha1.digest(p); + + sha1.reset(); + sha1.update(Base64.decode(salt), 0, 20); + sha1.update(p2); + byte[] scramble = sha1.digest(); + for (int i = 0, e = 20; i < e; i++) { + p[i] ^= scramble[i]; + } + auth.add(p); + + // this was the default implementation +// return createPacket(initialRequestSize, Code.AUTH, 0L, null, Key.USER_NAME, username, Key.TUPLE, auth); + return createPacket(DEFAULT_INITIAL_REQUEST_SIZE, Code.AUTH, 0L, null, Key.USER_NAME, username, Key.TUPLE, auth); + } + + protected static ByteBuffer createPacket(Code code, Long syncId, Long schemaId, Object... args) throws IOException { + return createPacket(DEFAULT_INITIAL_REQUEST_SIZE, code, syncId, schemaId, args); + } + + protected static ByteBuffer createPacket(int initialRequestSize, Code code, Long syncId, Long schemaId, Object... args) throws IOException { +// TarantoolClientImpl.ByteArrayOutputStream bos = new TarantoolClientImpl.ByteArrayOutputStream(initialRequestSize); + ByteArrayOutputStream bos = new ByteArrayOutputStream(initialRequestSize); + bos.write(new byte[5]); + DataOutputStream ds = new DataOutputStream(bos); + Map header = new EnumMap(Key.class); + Map body = new EnumMap(Key.class); + header.put(Key.CODE, code); + header.put(Key.SYNC, syncId); + if (schemaId != null) { + header.put(Key.SCHEMA_ID, schemaId); + } + if (args != null) { + for (int i = 0, e = args.length; i < e; i += 2) { + Object value = args[i + 1]; + body.put((Key) args[i], value); + } + } + getMsgPackLite().pack(header, ds); + getMsgPackLite().pack(body, ds); + ds.flush(); + ByteBuffer buffer = ByteBuffer.wrap(bos.toByteArray(), 0, bos.size()); + buffer.put(0, (byte) 0xce); + buffer.putInt(1, bos.size() - 5); + return buffer; + } + + public MsgPackLite getMsgPackLite() { + return MsgPackLite.INSTANCE; + } +} diff --git a/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java b/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java new file mode 100644 index 00000000..35dbaa3b --- /dev/null +++ b/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java @@ -0,0 +1,26 @@ +package org.tarantool.server; + +import java.util.Map; + +public class TarantoolBinaryPackage { + private final Map headers; + private final Map body; + + public TarantoolBinaryPackage(Map headers, Map body) { + this.headers = headers; + this.body = body; + } + + public TarantoolBinaryPackage(Map headers) { + this.headers = headers; + body = null; + } + + public Map getHeaders() { + return headers; + } + + public Map getBody() { + return body; + } +} diff --git a/src/main/java/org/tarantool/server/TarantoolServer.java b/src/main/java/org/tarantool/server/TarantoolServer.java new file mode 100644 index 00000000..04d1923d --- /dev/null +++ b/src/main/java/org/tarantool/server/TarantoolServer.java @@ -0,0 +1,135 @@ +package org.tarantool.server; + +import org.tarantool.ByteBufferInputStream; +import org.tarantool.CommunicationException; +import org.tarantool.CountInputStream; +import org.tarantool.Key; +import org.tarantool.MsgPackLite; +import org.tarantool.SocketChannelProvider; +import org.tarantool.TarantoolClientConfig; +import org.tarantool.TarantoolException; + +import java.io.DataInputStream; +import java.io.IOException; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.util.Map; + +public class TarantoolServer { + + /** + * External + */ + private SocketChannelProvider socketProvider; + private TarantoolClientConfig config; + + private CountingInputStream dis; + protected SocketChannel channel; + + private MsgPackLite msgPackLite = MsgPackLite.INSTANCE; + + /** + * Connection state + */ + protected String salt; + + public TarantoolServer(SocketChannelProvider socketProvider, TarantoolClientConfig config) { + this.socketProvider = socketProvider; + + this.config = config; + } + + public void init() throws Exception { + connect(); + } + + protected void connect() throws Exception { + connect(socketProvider.get(0)); + } + + public static final class CountingInputStream extends DataInputStream { + + private final ByteBufferInputStream in; + + public CountingInputStream(ByteBufferInputStream byteBufferInputStream) { + super(byteBufferInputStream); + this.in = byteBufferInputStream; + } + + private long getBytesRead() { + return in.getBytesRead(); + } + } + + protected void connect(final SocketChannel channel) throws Exception { + ByteBufferInputStream bufferInputStream = null; + try { + ; + CountingInputStream dis = new CountingInputStream((bufferInputStream = new ByteBufferInputStream(channel))); + byte[] bytes = new byte[64]; + dis.readFully(bytes); + String firstLine = new String(bytes); + if (!firstLine.startsWith("Tarantool")) { + CommunicationException e = new CommunicationException("Welcome message should starts with tarantool " + + "but starts with '" + firstLine + "'", new IllegalStateException("Invalid welcome packet")); + + throw e; + } + dis.readFully(bytes); + String salt = new String(bytes); + if (config.username != null && config.password != null) { + writeFully(channel, BinaryProtoUtils.createAuthPacket(config.username, config.password, salt)); + TarantoolBinaryPackage biPack = readPacket(); + Long code = (Long) biPack.getHeaders().get(Key.CODE.getId()); + if (code != 0) { + throw serverError(code, biPack.getBody().get(Key.ERROR.getId())); + } + } + + this.dis = dis; + } catch (IOException e) { + try { + if (bufferInputStream != null) { + bufferInputStream.close(); + } + } catch (IOException ignored) { + + } + throw new CommunicationException("Couldn't connect to tarantool", e); + } + channel.configureBlocking(false); + } + + protected TarantoolException serverError(long code, Object error) { + return new TarantoolException(code, error instanceof String ? (String) error : new String((byte[]) error)); + } + + public TarantoolBinaryPackage readPacket() throws IOException { + return readPacket(dis); + } + + + public void writeFully(ByteBuffer buffer) throws IOException { + writeFully(channel, buffer); + } + + private void writeFully(SocketChannel channel, ByteBuffer buffer) throws IOException { + long code = 0; + while (buffer.remaining() > 0 && (code = channel.write(buffer)) > -1) { + } + if (code < 0) { + throw new SocketException("write failed code: " + code); + } + } + + public void closeConnection() { + if (channel != null) { + try { + channel.close(); + } catch (IOException ignored) { + + } + } + } +} diff --git a/src/test/java/org/tarantool/ClientReconnectClusterIT.java b/src/test/java/org/tarantool/ClientReconnectClusterIT.java index 35737a32..49f05db6 100644 --- a/src/test/java/org/tarantool/ClientReconnectClusterIT.java +++ b/src/test/java/org/tarantool/ClientReconnectClusterIT.java @@ -112,6 +112,7 @@ public void execute() throws Throwable { private TarantoolClientImpl makeClient(String...addrs) { TarantoolClusterClientConfig config = makeClusterClientConfig(); - return new TarantoolClusterClient(config, addrs); + config.slaveHosts = addrs; + return new TarantoolClusterClient(config); } } From e5d2015f6ae02f03eceda4be15b0e2263302d56b Mon Sep 17 00:00:00 2001 From: dponomarev Date: Tue, 5 Feb 2019 20:59:54 +0300 Subject: [PATCH 02/30] Method refreshServerList "created" --- .../org/tarantool/TarantoolClusterClient.java | 26 ++++++++++++++++++- .../tarantool/server/BinaryProtoUtils.java | 10 +++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index 78cec643..2d6a976c 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -1,5 +1,9 @@ package org.tarantool; +import org.tarantool.server.*; + +import java.io.*; +import java.nio.channels.*; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.CompletableFuture; @@ -23,11 +27,21 @@ public class TarantoolClusterClient extends TarantoolClientImpl { /* Collection of operations to be retried. */ private ConcurrentHashMap> retries = new ConcurrentHashMap>(); + private final String[] slaveHosts; + private final String[] masterHosts; + + private final SocketChannelProvider masterHostsSocketProvider; + /** * @param config Configuration. */ public TarantoolClusterClient(TarantoolClusterClientConfig config) { this(config, new RoundRobinSocketProviderImpl(config.slaveHosts).setTimeout(config.operationExpiryTimeMillis)); + + masterHostsSocketProvider = new RoundRobinSocketProviderImpl(config.masterHosts); + + slaveHosts = config.slaveHosts; + masterHosts = config.masterHosts; } /** @@ -41,7 +55,17 @@ public TarantoolClusterClient(TarantoolClusterClientConfig config, SocketChannel Executors.newSingleThreadExecutor() : config.executor; } - private void refreshServerList() { + /** + * + * @param lastError + * @throws CommunicationException incase of communication exception + */ + private void refreshServerList(Exception lastError) { + + SocketChannel socketChannel = masterHostsSocketProvider.get(0, lastError); + + BinaryProtoUtils.readPacket(socketChannel); + } diff --git a/src/main/java/org/tarantool/server/BinaryProtoUtils.java b/src/main/java/org/tarantool/server/BinaryProtoUtils.java index dd904055..56a711f4 100644 --- a/src/main/java/org/tarantool/server/BinaryProtoUtils.java +++ b/src/main/java/org/tarantool/server/BinaryProtoUtils.java @@ -25,7 +25,7 @@ public abstract class BinaryProtoUtils { private final static int DEFAULT_INITIAL_REQUEST_SIZE = 4096; - private TarantoolBinaryPackage readPacket(SocketChannel channel) throws IOException { + public static TarantoolBinaryPackage readPacket(SocketChannel channel) throws IOException { int size = channel.socket().getInputStream().read(); ByteBuffer msgBuffer = ByteBuffer.allocate(size - 1); @@ -44,7 +44,7 @@ private TarantoolBinaryPackage readPacket(SocketChannel channel) throws IOExcept } - protected static ByteBuffer createAuthPacket(String username, final String password, String salt) throws IOException { + public static ByteBuffer createAuthPacket(String username, final String password, String salt) throws IOException { final MessageDigest sha1; try { sha1 = MessageDigest.getInstance("SHA-1"); @@ -73,11 +73,11 @@ protected static ByteBuffer createAuthPacket(String username, final String passw return createPacket(DEFAULT_INITIAL_REQUEST_SIZE, Code.AUTH, 0L, null, Key.USER_NAME, username, Key.TUPLE, auth); } - protected static ByteBuffer createPacket(Code code, Long syncId, Long schemaId, Object... args) throws IOException { + public static ByteBuffer createPacket(Code code, Long syncId, Long schemaId, Object... args) throws IOException { return createPacket(DEFAULT_INITIAL_REQUEST_SIZE, code, syncId, schemaId, args); } - protected static ByteBuffer createPacket(int initialRequestSize, Code code, Long syncId, Long schemaId, Object... args) throws IOException { + public static ByteBuffer createPacket(int initialRequestSize, Code code, Long syncId, Long schemaId, Object... args) throws IOException { // TarantoolClientImpl.ByteArrayOutputStream bos = new TarantoolClientImpl.ByteArrayOutputStream(initialRequestSize); ByteArrayOutputStream bos = new ByteArrayOutputStream(initialRequestSize); bos.write(new byte[5]); @@ -104,7 +104,7 @@ protected static ByteBuffer createPacket(int initialRequestSize, Code code, Long return buffer; } - public MsgPackLite getMsgPackLite() { + private static MsgPackLite getMsgPackLite() { return MsgPackLite.INSTANCE; } } From 397c090cf37bd189d3a5a22d3b4e4a7ebbaf3b50 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Fri, 8 Feb 2019 21:39:15 +0300 Subject: [PATCH 03/30] Create a TarantoolNode that describes a node --- .../RoundRobinSocketProviderImpl.java | 50 ++++++------- .../org/tarantool/TarantoolClusterClient.java | 17 +++-- .../TarantoolClusterClientConfig.java | 4 +- .../org/tarantool/server/TarantoolNode.java | 71 +++++++++++++++++++ 4 files changed, 110 insertions(+), 32 deletions(-) create mode 100644 src/main/java/org/tarantool/server/TarantoolNode.java diff --git a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java index 38d4f3c8..eb031e38 100644 --- a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java +++ b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java @@ -1,24 +1,26 @@ package org.tarantool; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.nio.channels.SocketChannel; -import java.util.Arrays; +import org.tarantool.server.*; + +import java.io.*; +import java.net.*; +import java.nio.channels.*; /** * Basic reconnection strategy that changes addresses in a round-robin fashion. * To be used with {@link TarantoolClientImpl}. */ public class RoundRobinSocketProviderImpl implements SocketChannelProvider { + /** Timeout to establish socket connection with an individual server. */ private int timeout; // 0 is infinite. + /** Limit of retries. */ private int retriesLimit = -1; // No-limit. - /** Server addresses as configured. */ - private final String[] addrs; - /** Socket addresses. */ - private final InetSocketAddress[] sockAddrs; - /** Current position within {@link #sockAddrs} array. */ + + private final TarantoolNode[] nodes; + + /** Current position within {@link #nodes} array. */ private int pos; /** @@ -27,23 +29,22 @@ public class RoundRobinSocketProviderImpl implements SocketChannelProvider { * @param slaveHosts Array of addresses in a form of [host]:[port]. */ public RoundRobinSocketProviderImpl(String[] slaveHosts) { - if (slaveHosts == null || slaveHosts.length == 0) - throw new IllegalArgumentException("slaveHosts is null or empty."); - - this.addrs = Arrays.copyOf(slaveHosts, slaveHosts.length); - - sockAddrs = new InetSocketAddress[this.addrs.length]; + if (slaveHosts == null || slaveHosts.length < 1) { + throw new IllegalArgumentException("slave hosts is null ot empty"); + } - for (int i = 0; i < this.addrs.length; i++) { - sockAddrs[i] = parseAddress(this.addrs[i]); + nodes = new TarantoolNode[slaveHosts.length]; + for (int i = 0; i < slaveHosts.length; i++) { + String slaveHostAddress = slaveHosts[i]; + nodes[i] = TarantoolNode.create(slaveHostAddress); } } /** - * @return Configured addresses in a form of [host]:[port]. + * @return Non-empty list of round-robined nodes */ - public String[] getAddresses() { - return this.addrs; + public TarantoolNode[] getNodes() { + return nodes; } /** @@ -107,8 +108,9 @@ public SocketChannel get(int retryNumber, Throwable lastError) { while (!Thread.currentThread().isInterrupted()) { SocketChannel channel = null; try { - channel = SocketChannel.open(); InetSocketAddress addr = getNextSocketAddress(); + + channel = SocketChannel.open(); channel.socket().connect(addr, timeout); return channel; } catch (IOException e) { @@ -141,15 +143,15 @@ public SocketChannel get(int retryNumber, Throwable lastError) { * @return Number of configured addresses. */ protected int getAddressCount() { - return sockAddrs.length; + return nodes.length; } /** * @return Socket address to use for the next reconnection attempt. */ protected InetSocketAddress getNextSocketAddress() { - InetSocketAddress res = sockAddrs[pos]; - pos = (pos + 1) % sockAddrs.length; + InetSocketAddress res = nodes[pos].getSocketAddress(); + pos = (pos + 1) % nodes.length; return res; } diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index 2d6a976c..f9f61226 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -4,8 +4,7 @@ import java.io.*; import java.nio.channels.*; -import java.util.ArrayList; -import java.util.Collection; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; @@ -28,9 +27,7 @@ public class TarantoolClusterClient extends TarantoolClientImpl { private ConcurrentHashMap> retries = new ConcurrentHashMap>(); private final String[] slaveHosts; - private final String[] masterHosts; - - private final SocketChannelProvider masterHostsSocketProvider; + private final String masterHosts; /** * @param config Configuration. @@ -38,7 +35,6 @@ public class TarantoolClusterClient extends TarantoolClientImpl { public TarantoolClusterClient(TarantoolClusterClientConfig config) { this(config, new RoundRobinSocketProviderImpl(config.slaveHosts).setTimeout(config.operationExpiryTimeMillis)); - masterHostsSocketProvider = new RoundRobinSocketProviderImpl(config.masterHosts); slaveHosts = config.slaveHosts; masterHosts = config.masterHosts; @@ -69,6 +65,15 @@ private void refreshServerList(Exception lastError) { } + /** + * Получает список хостов + * @param serverChannel + * @return + */ + private List hostnames(SocketChannel serverChannel) { + //todo + } + @Override protected boolean isDead(CompletableFuture q) { if ((state.getState() & CLOSED) != 0) { diff --git a/src/main/java/org/tarantool/TarantoolClusterClientConfig.java b/src/main/java/org/tarantool/TarantoolClusterClientConfig.java index d859a4aa..74c9a1a2 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClientConfig.java +++ b/src/main/java/org/tarantool/TarantoolClusterClientConfig.java @@ -18,8 +18,8 @@ public class TarantoolClusterClientConfig extends TarantoolClientConfig { public String[] slaveHosts; /** - * Array of addresses of tarantool instances that can act as providers of slaveHostList + * Addresses of tarantool instances that can act as providers of slaveHostList */ - public String[] masterHosts; + public String masterHosts; } diff --git a/src/main/java/org/tarantool/server/TarantoolNode.java b/src/main/java/org/tarantool/server/TarantoolNode.java new file mode 100644 index 00000000..b0d63e72 --- /dev/null +++ b/src/main/java/org/tarantool/server/TarantoolNode.java @@ -0,0 +1,71 @@ +package org.tarantool.server; + +import java.net.*; + +/** + * Holds info about a tarantool node. + */ +public class TarantoolNode { + + private final InetSocketAddress socketAddress; + + private TarantoolNode(InetSocketAddress socketAddress) { + this.socketAddress = socketAddress; + } + + /** + * @return A socket address that the client can be connected to + */ + public InetSocketAddress getSocketAddress() { + return socketAddress; + } + + /** + * + * @param socketAddress Nonnull socket address + * @return Instance of {@link TarantoolNode} + */ + public static TarantoolNode create(InetSocketAddress socketAddress) { + if (socketAddress == null) { + throw new IllegalArgumentException("A socket address can not be null."); + } + + return new TarantoolNode(socketAddress); + } + + + /** + * @param address hostname addess as String + * + * @throws IllegalArgumentException if the port parameter is outside the range + * of valid port values, or if the hostname parameter is null. + * @throws SecurityException if a security manager is present and + * permission to resolve the host name is + * denied. + */ + public static TarantoolNode create(String address) { + if (address == null) { + throw new IllegalArgumentException("A hostname address can not be null."); + } + + return new TarantoolNode(parseAddress(address)); + } + + /** + * Parse a string address in the form of [host]:[port] + * and builds a socket address. + * + * @param addr Server address as string. + * @throws IllegalArgumentException if the port parameter is outside the range + * of valid port values, or if the hostname parameter is null. + * @throws SecurityException if a security manager is present and + * permission to resolve the host name is + * denied. + */ + private static InetSocketAddress parseAddress(String addr) { + int idx = addr.indexOf(':'); + String host = (idx < 0) ? addr : addr.substring(0, idx); + int port = (idx < 0) ? 3301 : Integer.parseInt(addr.substring(idx + 1)); + return new InetSocketAddress(host, port); + } +} From 653d4de506dc95616acaa9814482eca6573e38d5 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Mon, 11 Feb 2019 01:35:34 +0300 Subject: [PATCH 04/30] Implementing ClusterTopologyDiscoverer.java & co --- pom.xml | 6 ++ .../RoundRobinSocketProviderImpl.java | 42 +++++++---- .../SimpleSocketChannelProvider.java | 29 ++++++++ .../org/tarantool/TarantoolClientImpl.java | 9 +++ .../org/tarantool/TarantoolClusterClient.java | 61 ++++++++++------ .../TarantoolClusterClientConfig.java | 9 ++- .../cluster/ClusterTopologyDiscoverer.java | 9 +++ ...lusterTopologyFromShardDiscovererImpl.java | 71 ++++++++++++++++++ .../org/tarantool/server/TarantoolNode.java | 72 ++++++++++++++++++- .../org/tarantool/server/TarantoolServer.java | 21 +++++- ...erTopologyFromShardDiscovererImplTest.java | 26 +++++++ .../tarantool/server/TarantoolNodeTest.java | 23 ++++++ 12 files changed, 340 insertions(+), 38 deletions(-) create mode 100644 src/main/java/org/tarantool/SimpleSocketChannelProvider.java create mode 100644 src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java create mode 100644 src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java create mode 100644 src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java create mode 100644 src/test/java/org/tarantool/server/TarantoolNodeTest.java diff --git a/pom.xml b/pom.xml index 76d7974e..874cb214 100644 --- a/pom.xml +++ b/pom.xml @@ -134,6 +134,12 @@ ${junit.jupiter.version} test + + org.junit.jupiter + junit-jupiter-params + ${junit.jupiter.version} + test + org.mockito mockito-all diff --git a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java index eb031e38..0d009b5d 100644 --- a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java +++ b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java @@ -5,6 +5,12 @@ import java.io.*; import java.net.*; import java.nio.channels.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; /** * Basic reconnection strategy that changes addresses in a round-robin fashion. @@ -18,7 +24,7 @@ public class RoundRobinSocketProviderImpl implements SocketChannelProvider { /** Limit of retries. */ private int retriesLimit = -1; // No-limit. - private final TarantoolNode[] nodes; + private TarantoolNode[] nodes; /** Current position within {@link #nodes} array. */ private int pos; @@ -33,13 +39,33 @@ public RoundRobinSocketProviderImpl(String[] slaveHosts) { throw new IllegalArgumentException("slave hosts is null ot empty"); } + updateNodes(slaveHosts); + } + + private void updateNodes(String[] slaveHosts) { + //todo add read-write lock nodes = new TarantoolNode[slaveHosts.length]; for (int i = 0; i < slaveHosts.length; i++) { String slaveHostAddress = slaveHosts[i]; nodes[i] = TarantoolNode.create(slaveHostAddress); } + + pos = 0; } + + public void updateNodes(List slaveHosts) { + if (slaveHosts == null) { + throw new IllegalArgumentException("slaveHosts can not be null"); + } + //todo add read-write lock + + this.nodes = (TarantoolNode[]) slaveHosts.toArray(); + + pos = 0; + } + + /** * @return Non-empty list of round-robined nodes */ @@ -155,18 +181,8 @@ protected InetSocketAddress getNextSocketAddress() { return res; } - /** - * Parse a string address in the form of [host]:[port] - * and builds a socket address. - * - * @param addr Server address. - * @return Socket address. - */ - protected InetSocketAddress parseAddress(String addr) { - int idx = addr.indexOf(':'); - String host = (idx < 0) ? addr : addr.substring(0, idx); - int port = (idx < 0) ? 3301 : Integer.parseInt(addr.substring(idx + 1)); - return new InetSocketAddress(host, port); + protected TarantoolNode getCurrentNode() { + return nodes[pos]; } /** diff --git a/src/main/java/org/tarantool/SimpleSocketChannelProvider.java b/src/main/java/org/tarantool/SimpleSocketChannelProvider.java new file mode 100644 index 00000000..eee3e0d2 --- /dev/null +++ b/src/main/java/org/tarantool/SimpleSocketChannelProvider.java @@ -0,0 +1,29 @@ +package org.tarantool; + +import org.tarantool.server.TarantoolNode; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.channels.SocketChannel; + +public class SimpleSocketChannelProvider implements SocketChannelProvider{ + + private final TarantoolNode tarantoolNode; + + public SimpleSocketChannelProvider(InetSocketAddress socketAddress) { + this.tarantoolNode = TarantoolNode.create(socketAddress); + } + + public SimpleSocketChannelProvider(String address) { + this.tarantoolNode = TarantoolNode.create(address); + } + + @Override + public SocketChannel get(int retryNumber, Throwable lastError) { + try { + return SocketChannel.open(tarantoolNode.getSocketAddress()); + } catch (IOException e) { + throw new CommunicationException("Exception occurred while connecting to node " + tarantoolNode, e); + } + } +} diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index 0c510287..b21abb46 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -2,6 +2,7 @@ import java.io.DataInputStream; import java.io.IOException; +import java.net.InetSocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; @@ -73,6 +74,14 @@ public void run() { } }); + public TarantoolClientImpl(InetSocketAddress socketAddress, TarantoolClientConfig config) { + this(new SimpleSocketChannelProvider(socketAddress), config); + } + + public TarantoolClientImpl(String address, TarantoolClientConfig config) { + this(new SimpleSocketChannelProvider(address), config); + } + public TarantoolClientImpl(SocketChannelProvider socketProvider, TarantoolClientConfig config) { super(); this.thumbstone = NOT_INIT_EXCEPTION; diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index f9f61226..985ab07d 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -1,9 +1,9 @@ package org.tarantool; +import org.tarantool.cluster.ClusterTopologyDiscoverer; +import org.tarantool.cluster.ClusterTopologyFromShardDiscovererImpl; import org.tarantool.server.*; -import java.io.*; -import java.nio.channels.*; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -26,18 +26,17 @@ public class TarantoolClusterClient extends TarantoolClientImpl { /* Collection of operations to be retried. */ private ConcurrentHashMap> retries = new ConcurrentHashMap>(); - private final String[] slaveHosts; - private final String masterHosts; + private final Collection slaveHosts; + private final TarantoolNode infoHost; + private final Integer infoHostConnectionTimeout; + private final ClusterTopologyDiscoverer topologyDiscoverer; /** * @param config Configuration. */ public TarantoolClusterClient(TarantoolClusterClientConfig config) { this(config, new RoundRobinSocketProviderImpl(config.slaveHosts).setTimeout(config.operationExpiryTimeMillis)); - - slaveHosts = config.slaveHosts; - masterHosts = config.masterHosts; } /** @@ -49,29 +48,49 @@ public TarantoolClusterClient(TarantoolClusterClientConfig config, SocketChannel this.executor = config.executor == null ? Executors.newSingleThreadExecutor() : config.executor; + this.infoHost = TarantoolNode.create(config.infoHost); + + this.infoHostConnectionTimeout = config.infoHostConnectionTimeout; + this.topologyDiscoverer = new ClusterTopologyFromShardDiscovererImpl(config); + + slaveHosts = topologyDiscoverer.discoverTarantoolNodes(this.infoHost, infoHostConnectionTimeout); } /** - * - * @param lastError - * @throws CommunicationException incase of communication exception + * @param infoNode a node from which a topology of the cluster is discovered. + * @throws CommunicationException in case of communication with {@code infoNode} exception + * @throws IllegalArgumentException in case when the info node returned invalid address */ - private void refreshServerList(Exception lastError) { + private Collection refreshServerList(TarantoolNode infoNode) { + List newServerList = topologyDiscoverer + .discoverTarantoolNodes(infoNode, infoHostConnectionTimeout); - SocketChannel socketChannel = masterHostsSocketProvider.get(0, lastError); + writeLock.lock(); +// todo add a read lock + try { - BinaryProtoUtils.readPacket(socketChannel); + RoundRobinSocketProviderImpl rSocketProvider = (RoundRobinSocketProviderImpl) this.socketProvider; + TarantoolNode currentNode = rSocketProvider.getCurrentNode(); - } + int sameNodeIndex = newServerList.indexOf(currentNode); + if (sameNodeIndex != -1) { + Collections.swap(newServerList, 0, sameNodeIndex); + rSocketProvider.updateNodes(newServerList); + } else { + rSocketProvider.updateNodes(newServerList); + die("The server list have been changed.", null); + //todo + } - /** - * Получает список хостов - * @param serverChannel - * @return - */ - private List hostnames(SocketChannel serverChannel) { - //todo + rSocketProvider.updateNodes(newServerList); + + + } finally { + writeLock.unlock(); + } + + return newServerList; } @Override diff --git a/src/main/java/org/tarantool/TarantoolClusterClientConfig.java b/src/main/java/org/tarantool/TarantoolClusterClientConfig.java index 74c9a1a2..c44b9c9f 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClientConfig.java +++ b/src/main/java/org/tarantool/TarantoolClusterClientConfig.java @@ -18,8 +18,13 @@ public class TarantoolClusterClientConfig extends TarantoolClientConfig { public String[] slaveHosts; /** - * Addresses of tarantool instances that can act as providers of slaveHostList + * Address of a tarantool instance that can act as provider of host list */ - public String masterHosts; + public String infoHost; + + /** + * timeout of connecting to a info host + */ + public int infoHostConnectionTimeout = 500; } diff --git a/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java b/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java new file mode 100644 index 00000000..460694cd --- /dev/null +++ b/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java @@ -0,0 +1,9 @@ +package org.tarantool.cluster; + +import org.tarantool.server.TarantoolNode; + +import java.util.List; + +public interface ClusterTopologyDiscoverer { + List discoverTarantoolNodes(TarantoolNode infoNode, Integer infoHostConnectionTimeout); +} diff --git a/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java b/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java new file mode 100644 index 00000000..ad70bc8c --- /dev/null +++ b/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java @@ -0,0 +1,71 @@ +package org.tarantool.cluster; + +import org.tarantool.CommunicationException; +import org.tarantool.TarantoolClientConfig; +import org.tarantool.TarantoolClientImpl; +import org.tarantool.TarantoolClusterClient; +import org.tarantool.TarantoolClusterClientConfig; +import org.tarantool.server.BinaryProtoUtils; +import org.tarantool.server.TarantoolNode; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public class ClusterTopologyFromShardDiscovererImpl implements ClusterTopologyDiscoverer { + + private final TarantoolClusterClientConfig clientConfig; + + private static final String DISCOVER_TOPOLOGY_TARANTOOL_SIDE_FUNCTION_NAME = "get_shard_cfg"; + + public ClusterTopologyFromShardDiscovererImpl(TarantoolClusterClientConfig clientConfig) { + this.clientConfig = clientConfig; + } + + @Override + public List discoverTarantoolNodes(TarantoolNode infoNode, + Integer infoHostConnectionTimeout) { + + List list = new TarantoolClientImpl(infoNode.getSocketAddress(), clientConfig) + .syncOps() + .call(DISCOVER_TOPOLOGY_TARANTOOL_SIDE_FUNCTION_NAME); + + Map funcResult = (Map) ((List) list.get(0)).get(0); + + Map shardHash2DescriptionMap = (Map) getValue(funcResult, "sharding"); + + List result = new ArrayList<>(); + + + for (Object shardHash2Description : shardHash2DescriptionMap.entrySet()) { + + Map replicas = (Map) getValue(((Map.Entry) shardHash2Description).getValue(), "replicas"); + + for (Object replica : replicas.entrySet()) { + Object replicaUri = getValue(((Map.Entry) replica).getValue(), "uri"); + result.add(TarantoolNode.create(parseReplicaUri(replicaUri.toString()))); + } + } + + return result; + } + + private String parseReplicaUri(String uri) { + String[] split = uri.split("@"); + if (split.length == 2) { + return split[1]; + } else { + return split[0]; + } + } + + private Object getValue(Object map, String key) { + if (!(map instanceof Map)) { + throw new IllegalArgumentException("Argument 'map' is not instance of Map but " + map.getClass()); + } + + return ((Map) map).get(key); + } +} diff --git a/src/main/java/org/tarantool/server/TarantoolNode.java b/src/main/java/org/tarantool/server/TarantoolNode.java index b0d63e72..73d03270 100644 --- a/src/main/java/org/tarantool/server/TarantoolNode.java +++ b/src/main/java/org/tarantool/server/TarantoolNode.java @@ -1,6 +1,11 @@ package org.tarantool.server; +import org.tarantool.CommunicationException; + +import java.io.IOException; import java.net.*; +import java.nio.channels.SocketChannel; +import java.util.Objects; /** * Holds info about a tarantool node. @@ -20,6 +25,57 @@ public InetSocketAddress getSocketAddress() { return socketAddress; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TarantoolNode node = (TarantoolNode) o; + return socketAddress.equals(node.socketAddress); + } + + @Override + public int hashCode() { + return Objects.hash(socketAddress); + } + + @Deprecated + private SocketChannel socketChannel; + /** + * Opens a socket connection channel to the tarantool node or returns an opened one + * @return + */ + @Deprecated //it's bad to open socket at level of this class + public SocketChannel getSocketChannel(Integer timeout) { + if (socketChannel == null) { + socketChannel = openSocketChannel(timeout); + } + + return socketChannel; + } + + @Deprecated //it's bad to open socket at level of this class + private SocketChannel openSocketChannel(Integer timeout) { + SocketChannel result = null; + try { + result = SocketChannel.open(); + + if (timeout != null) { + result.socket().connect(socketAddress, timeout); + } else { + result.connect(socketAddress); + } + return result; + } catch (Exception e) { + if (result != null) { + try { + result.close(); + } catch (IOException ignored) { + } + } + throw new CommunicationException("Failed to connect to node " + this.toString(), e); + } + } + /** * * @param socketAddress Nonnull socket address @@ -65,7 +121,21 @@ public static TarantoolNode create(String address) { private static InetSocketAddress parseAddress(String addr) { int idx = addr.indexOf(':'); String host = (idx < 0) ? addr : addr.substring(0, idx); - int port = (idx < 0) ? 3301 : Integer.parseInt(addr.substring(idx + 1)); + + int port; + try { + port = (idx < 0) ? 3301 : Integer.parseInt(addr.substring(idx + 1)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Exception while parsing port in address '" + addr + "'", e); + } + return new InetSocketAddress(host, port); } + + @Override + public String toString() { + return "TarantoolNode{" + + "socketAddress=" + socketAddress.getHostString() + ":" + socketAddress.getPort() + + '}'; + } } diff --git a/src/main/java/org/tarantool/server/TarantoolServer.java b/src/main/java/org/tarantool/server/TarantoolServer.java index 04d1923d..071f8c6d 100644 --- a/src/main/java/org/tarantool/server/TarantoolServer.java +++ b/src/main/java/org/tarantool/server/TarantoolServer.java @@ -45,7 +45,7 @@ public void init() throws Exception { } protected void connect() throws Exception { - connect(socketProvider.get(0)); + connect(socketProvider.get(0, null)); } public static final class CountingInputStream extends DataInputStream { @@ -109,6 +109,25 @@ public TarantoolBinaryPackage readPacket() throws IOException { return readPacket(dis); } + @Deprecated // use org.tarantool.server.BinaryProtoUtils.readPacket + private TarantoolBinaryPackage readPacket(CountingInputStream countingInputStream) throws IOException { + int size = ((Number) getMsgPackLite().unpack(countingInputStream)).intValue(); + + long mark = countingInputStream.getBytesRead(); + Map headers = (Map) getMsgPackLite().unpack(countingInputStream); + + Map body = null; + if (countingInputStream.getBytesRead() - mark < size) { + body = (Map) getMsgPackLite().unpack(countingInputStream); + } + countingInputStream.skipBytes((int) (countingInputStream.getBytesRead() - mark - size)); + + return new TarantoolBinaryPackage(headers, body); + } + + private static MsgPackLite getMsgPackLite() { + return MsgPackLite.INSTANCE; + } public void writeFully(ByteBuffer buffer) throws IOException { writeFully(channel, buffer); diff --git a/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java b/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java new file mode 100644 index 00000000..04545a23 --- /dev/null +++ b/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java @@ -0,0 +1,26 @@ +package org.tarantool.cluster; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.tarantool.TarantoolClusterClientConfig; +import org.tarantool.server.TarantoolNode; + +import java.util.Collection; + +class ClusterTopologyFromShardDiscovererImplTest { + + @DisplayName("Test that a list which describes the topology is fetched correctly") + @Test + void testListFetching() { + TarantoolNode tarantoolNode = TarantoolNode.create("localhost:3301"); + TarantoolClusterClientConfig clientConfig = new TarantoolClusterClientConfig(); + + clientConfig.username = "storage"; + clientConfig.password = "storage"; + + Collection tarantoolNodes = + new ClusterTopologyFromShardDiscovererImpl(clientConfig) + .discoverTarantoolNodes(tarantoolNode, 5000); + int i = 0; + } +} \ No newline at end of file diff --git a/src/test/java/org/tarantool/server/TarantoolNodeTest.java b/src/test/java/org/tarantool/server/TarantoolNodeTest.java new file mode 100644 index 00000000..2ee988a0 --- /dev/null +++ b/src/test/java/org/tarantool/server/TarantoolNodeTest.java @@ -0,0 +1,23 @@ +package org.tarantool.server; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +class TarantoolNodeTest { + + @DisplayName("Test that a TarantoolNode throws an illegal argument exception" + + "in case when it's being created with wrong address string") + @ParameterizedTest + @ValueSource(strings = { + "hostname: 333", + "127.0.0.1:333333" + }) + void testThrowsExceptionInCaseOfInvalidStringAddress(String address) { + assertThrows(IllegalArgumentException.class, + () -> TarantoolNode.create(address), + "We expect the code under test to throw an IllegalArgumentException, but it didn't"); + } +} \ No newline at end of file From 3bde207319c45aebdb8ed9ffc49c3e3515df9f62 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Tue, 12 Feb 2019 02:09:09 +0300 Subject: [PATCH 05/30] Add test benchmark --- pom.xml | 67 ++++++++++++++++++- src/main/assembly/perf-tests.xml | 28 ++++++++ .../java-templates/org/tarantool/Version.java | 4 +- ...erTopologyFromShardDiscovererImplTest.java | 6 +- src/test/perf/org/tarantool/MyBenchmark.java | 27 ++++++++ 5 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 src/main/assembly/perf-tests.xml create mode 100644 src/test/perf/org/tarantool/MyBenchmark.java diff --git a/pom.xml b/pom.xml index 874cb214..621c2e60 100644 --- a/pom.xml +++ b/pom.xml @@ -5,10 +5,15 @@ connector 1.9.1-SNAPSHOT jar + UTF-8 5.3.1 + + 1.21 + 2.6 + Tarantool Connector for Java https://github.com/tarantool/tarantool-java Tarantool client for java @@ -40,11 +45,28 @@ org.apache.maven.plugins maven-compiler-plugin - 3.2 + 3.6.1 1.8 1.8 + + + + testCompile + + + + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + + + + + @@ -124,6 +158,31 @@ + + + maven-assembly-plugin + ${maven-assembly-plugin.version} + + src/main/assembly/perf-tests.xml + + + + make-assembly + package + + single + + + true + + + org.openjdk.jmh.Main + + + + + + @@ -152,6 +211,12 @@ 1.23 test + + org.openjdk.jmh + jmh-core + ${jmh.version} + test + diff --git a/src/main/assembly/perf-tests.xml b/src/main/assembly/perf-tests.xml new file mode 100644 index 00000000..57d9eba6 --- /dev/null +++ b/src/main/assembly/perf-tests.xml @@ -0,0 +1,28 @@ + + perf-tests + + jar + + false + + + / + true + true + test + + + + + ${project.build.directory}/test-classes + / + + **/* + + true + + + \ No newline at end of file diff --git a/src/main/java-templates/org/tarantool/Version.java b/src/main/java-templates/org/tarantool/Version.java index 82dcfc6c..cdfa14ac 100644 --- a/src/main/java-templates/org/tarantool/Version.java +++ b/src/main/java-templates/org/tarantool/Version.java @@ -2,6 +2,6 @@ public final class Version { public static final String version = "${project.version}"; - public static final int majorVersion = ${parsedVersion.majorVersion}; - public static final int minorVersion = ${parsedVersion.minorVersion}; + public static final int majorVersion = Integer.parseInt("${parsedVersion.majorVersion}"); + public static final int minorVersion = Integer.parseInt("${parsedVersion.minorVersion}"); } diff --git a/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java b/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java index 04545a23..ecf5283a 100644 --- a/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java +++ b/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java @@ -18,9 +18,9 @@ void testListFetching() { clientConfig.username = "storage"; clientConfig.password = "storage"; - Collection tarantoolNodes = - new ClusterTopologyFromShardDiscovererImpl(clientConfig) - .discoverTarantoolNodes(tarantoolNode, 5000); +// Collection tarantoolNodes = +// new ClusterTopologyFromShardDiscovererImpl(clientConfig) +// .discoverTarantoolNodes(tarantoolNode, 5000); int i = 0; } } \ No newline at end of file diff --git a/src/test/perf/org/tarantool/MyBenchmark.java b/src/test/perf/org/tarantool/MyBenchmark.java new file mode 100644 index 00000000..d588144e --- /dev/null +++ b/src/test/perf/org/tarantool/MyBenchmark.java @@ -0,0 +1,27 @@ +package org.tarantool; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; + +import java.util.concurrent.TimeUnit; + +public class MyBenchmark { + + @State(Scope.Thread) + public static class MyState { + public int a = 1; + public int b = 2; + public int sum ; + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MINUTES) + public void testMethod(MyState state) { + state.sum = state.a + state.b; + } + +} From c38304bfaa57dc6b44859eb69e0f7ca128462ebc Mon Sep 17 00:00:00 2001 From: dponomarev Date: Tue, 12 Feb 2019 22:24:42 +0300 Subject: [PATCH 06/30] Implement non-working tarantool client impl read-write measurment --- .../org/tarantool/DodgeSocketChannel.java | 131 ++++++++++++++++++ .../org/tarantool/DodgeSocketChannelTest.java | 31 +++++ src/test/perf/org/tarantool/MyBenchmark.java | 40 +++++- 3 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 src/test/perf/org/tarantool/DodgeSocketChannel.java create mode 100644 src/test/perf/org/tarantool/DodgeSocketChannelTest.java diff --git a/src/test/perf/org/tarantool/DodgeSocketChannel.java b/src/test/perf/org/tarantool/DodgeSocketChannel.java new file mode 100644 index 00000000..9309d991 --- /dev/null +++ b/src/test/perf/org/tarantool/DodgeSocketChannel.java @@ -0,0 +1,131 @@ +package org.tarantool; + +import java.io.*; +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.nio.channels.spi.*; +import java.util.*; +import java.util.concurrent.*; + +public class DodgeSocketChannel extends SocketChannel { + + private ArrayBlockingQueue blockingQueue; + + public DodgeSocketChannel(Integer queueSize) { + super(null); + this.blockingQueue = new ArrayBlockingQueue<>(queueSize); + } + + /** + * Initializes a new instance of this class. + * + * @param provider The provider that created this channel + */ + protected DodgeSocketChannel(SelectorProvider provider) { + super(provider); + } + + @Override + public SocketChannel bind(SocketAddress local) throws IOException { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public SocketChannel setOption(SocketOption name, T value) throws IOException { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public T getOption(SocketOption name) throws IOException { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public Set> supportedOptions() { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public SocketChannel shutdownInput() throws IOException { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public SocketChannel shutdownOutput() throws IOException { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public Socket socket() { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public boolean isConnected() { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public boolean isConnectionPending() { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public boolean connect(SocketAddress remote) throws IOException { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public boolean finishConnect() throws IOException { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public SocketAddress getRemoteAddress() throws IOException { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public int read(ByteBuffer dst) throws IOException { + try { + byte[] bytes = blockingQueue.take(); + dst.put(bytes); + return bytes.length; + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + + @Override + public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public int write(ByteBuffer src) throws IOException { + blockingQueue.add(src.array()); + return src.array().length; +// throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + public SocketAddress getLocalAddress() throws IOException { + throw new UnsupportedOperationException("This operation is not implemented"); + } + + @Override + protected void implCloseSelectableChannel() throws IOException { + + } + + @Override + protected void implConfigureBlocking(boolean block) throws IOException { + + } +} diff --git a/src/test/perf/org/tarantool/DodgeSocketChannelTest.java b/src/test/perf/org/tarantool/DodgeSocketChannelTest.java new file mode 100644 index 00000000..ad89645f --- /dev/null +++ b/src/test/perf/org/tarantool/DodgeSocketChannelTest.java @@ -0,0 +1,31 @@ +package org.tarantool; + +import org.junit.jupiter.api.*; + +import java.io.*; +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.nio.channels.spi.*; +import java.util.*; +import java.util.concurrent.*; + +public class DodgeSocketChannelTest { + + @Test + void testCorrectDodge() throws IOException { + DodgeSocketChannel dodgeSocketChannel = new DodgeSocketChannel(10); + + + dodgeSocketChannel.write(ByteBuffer.wrap("one".getBytes())); + dodgeSocketChannel.write(ByteBuffer.wrap("two".getBytes())); + + ByteBuffer oneBuffer = ByteBuffer.allocate("one".getBytes().length); + ByteBuffer twoBuffer = ByteBuffer.allocate("two".getBytes().length); + dodgeSocketChannel.read(oneBuffer); + dodgeSocketChannel.read(twoBuffer); + + Assertions.assertEquals("one", new String(oneBuffer.array())); + Assertions.assertEquals("two", new String(twoBuffer.array())); + } +} diff --git a/src/test/perf/org/tarantool/MyBenchmark.java b/src/test/perf/org/tarantool/MyBenchmark.java index d588144e..05d76607 100644 --- a/src/test/perf/org/tarantool/MyBenchmark.java +++ b/src/test/perf/org/tarantool/MyBenchmark.java @@ -7,10 +7,47 @@ import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; -import java.util.concurrent.TimeUnit; +import java.nio.channels.*; +import java.util.*; +import java.util.concurrent.*; public class MyBenchmark { + private static class DodgeSocketChannelProvider implements SocketChannelProvider { + + private final Integer defaultSocketQueueSize; + + private DodgeSocketChannelProvider(Integer defaultSocketQueueSize) { + this.defaultSocketQueueSize = defaultSocketQueueSize; + } + + @Override + public SocketChannel get(int retryNumber, Throwable lastError) { + return new DodgeSocketChannel(defaultSocketQueueSize); + } + } + + @State(Scope.Benchmark) + public static class SpeedOfWriteAndReadState { + + public final TarantoolClientImpl tarantoolClient; + + public SpeedOfWriteAndReadState() { + TarantoolClientConfig config = new TarantoolClientConfig(); + this.tarantoolClient = new TarantoolClientImpl(new DodgeSocketChannelProvider(10), config); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MINUTES) + public void measureSpeedOfWriteAndReadViaSharedBuffer(SpeedOfWriteAndReadState state) { + try { + state.tarantoolClient.asyncOps().insert(1, Arrays.asList("a", "b")).get(); + } catch (Exception e) { + throw new IllegalArgumentException("Exception occurred while benchmarking", e); + } + } + @State(Scope.Thread) public static class MyState { public int a = 1; @@ -22,6 +59,7 @@ public static class MyState { @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MINUTES) public void testMethod(MyState state) { state.sum = state.a + state.b; + System.out.println("Run from: " + Thread.currentThread().getName()); } } From c0839e806fab099d36ddbb573734d9408f675d8f Mon Sep 17 00:00:00 2001 From: dponomarev Date: Wed, 13 Feb 2019 22:04:04 +0300 Subject: [PATCH 07/30] Attempt to refactor --- .../java/org/tarantool/TarantoolBase.java | 11 ++++++++-- .../org/tarantool/TarantoolConnection.java | 9 +++++++-- .../tarantool/server/BinaryProtoUtils.java | 20 +++++++++++++++---- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/tarantool/TarantoolBase.java b/src/main/java/org/tarantool/TarantoolBase.java index 3fb1ce40..ff7c7ed7 100644 --- a/src/main/java/org/tarantool/TarantoolBase.java +++ b/src/main/java/org/tarantool/TarantoolBase.java @@ -1,10 +1,12 @@ package org.tarantool; +import org.tarantool.server.*; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.net.Socket; +import java.net.*; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.security.MessageDigest; @@ -37,9 +39,10 @@ public abstract class TarantoolBase extends AbstractTarantoolOps> implements Taran protected InputStream in; protected OutputStream out; protected Socket socket; + protected SocketChannel channel; public TarantoolConnection(String username, String password, Socket socket) throws IOException { + + } + + public TarantoolConnection(String username, String password, InetSocketAddress address) throws IOException { super(username, password, socket); this.socket = socket; this.out = socket.getOutputStream(); diff --git a/src/main/java/org/tarantool/server/BinaryProtoUtils.java b/src/main/java/org/tarantool/server/BinaryProtoUtils.java index 56a711f4..935e02e5 100644 --- a/src/main/java/org/tarantool/server/BinaryProtoUtils.java +++ b/src/main/java/org/tarantool/server/BinaryProtoUtils.java @@ -7,10 +7,7 @@ import org.tarantool.MsgPackLite; import org.tarantool.server.TarantoolServer.CountingInputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; +import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.security.MessageDigest; @@ -25,6 +22,21 @@ public abstract class BinaryProtoUtils { private final static int DEFAULT_INITIAL_REQUEST_SIZE = 4096; + public static TarantoolBinaryPackage readPacket(InputStream inputStream) throws IOException { + int size = inputStream.read(); + + CountInputStreamImpl msgStream = new CountInputStreamImpl(inputStream); + Map headers = (Map) getMsgPackLite().unpack(msgStream); + + + Map body = null; + if (msgStream.getBytesRead() < size) { + body = (Map) getMsgPackLite().unpack(msgStream); + } + + return new TarantoolBinaryPackage(headers, body); + } + public static TarantoolBinaryPackage readPacket(SocketChannel channel) throws IOException { int size = channel.socket().getInputStream().read(); From bd9276b10c0bf14726ecdcb21783f105c9ce592c Mon Sep 17 00:00:00 2001 From: dponomarev Date: Thu, 14 Feb 2019 22:22:51 +0300 Subject: [PATCH 08/30] Yet another attempt to force the benchmark to work --- .../java/org/tarantool/TarantoolBase.java | 5 ++-- .../org/tarantool/TarantoolClientImpl.java | 11 ++++++--- .../org/tarantool/TarantoolConnection.java | 15 +++++++----- src/test/perf/org/tarantool/MyBenchmark.java | 23 +++++++++++-------- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/tarantool/TarantoolBase.java b/src/main/java/org/tarantool/TarantoolBase.java index ff7c7ed7..22833f6c 100644 --- a/src/main/java/org/tarantool/TarantoolBase.java +++ b/src/main/java/org/tarantool/TarantoolBase.java @@ -39,10 +39,11 @@ public abstract class TarantoolBase extends AbstractTarantoolOps T syncGet(Future r) { } } + protected void writeFully(OutputStream stream, ByteBuffer buffer) throws IOException { +// stream.write(bu.ar); //todo? + } + protected void writeFully(SocketChannel channel, ByteBuffer buffer) throws IOException { long code = 0; while (buffer.remaining() > 0 && (code = channel.write(buffer)) > -1) { diff --git a/src/main/java/org/tarantool/TarantoolConnection.java b/src/main/java/org/tarantool/TarantoolConnection.java index d89eab78..c64e686d 100644 --- a/src/main/java/org/tarantool/TarantoolConnection.java +++ b/src/main/java/org/tarantool/TarantoolConnection.java @@ -1,5 +1,7 @@ package org.tarantool; +import org.tarantool.server.*; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -17,10 +19,6 @@ public class TarantoolConnection extends TarantoolBase> implements Taran public TarantoolConnection(String username, String password, Socket socket) throws IOException { - - } - - public TarantoolConnection(String username, String password, InetSocketAddress address) throws IOException { super(username, password, socket); this.socket = socket; this.out = socket.getOutputStream(); @@ -30,10 +28,15 @@ public TarantoolConnection(String username, String password, InetSocketAddress a @Override protected List exec(Code code, Object... args) { try { - ByteBuffer packet = createPacket(code, syncId.incrementAndGet(), null, args); + ByteBuffer packet = BinaryProtoUtils.createPacket(code, syncId.incrementAndGet(), null, args); + out.write(packet.array(), 0, packet.remaining()); out.flush(); - readPacket(is); + + TarantoolBinaryPackage responsePackage = BinaryProtoUtils.readPacket(is); + + Map headers = responsePackage.getHeaders(); + Map body = responsePackage.getBody(); Long c = (Long) headers.get(Key.CODE.getId()); if (c == 0) { return (List) body.get(Key.DATA.getId()); diff --git a/src/test/perf/org/tarantool/MyBenchmark.java b/src/test/perf/org/tarantool/MyBenchmark.java index 05d76607..16a12a7c 100644 --- a/src/test/perf/org/tarantool/MyBenchmark.java +++ b/src/test/perf/org/tarantool/MyBenchmark.java @@ -1,11 +1,6 @@ package org.tarantool; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.*; import java.nio.channels.*; import java.util.*; @@ -30,18 +25,28 @@ public SocketChannel get(int retryNumber, Throwable lastError) { @State(Scope.Benchmark) public static class SpeedOfWriteAndReadState { - public final TarantoolClientImpl tarantoolClient; + public TarantoolClientImpl tarantoolClient; - public SpeedOfWriteAndReadState() { + @Setup(Level.Invocation) + public void init() { + System.out.println("INIT BENCHMARK"); TarantoolClientConfig config = new TarantoolClientConfig(); this.tarantoolClient = new TarantoolClientImpl(new DodgeSocketChannelProvider(10), config); } + + @TearDown(Level.Invocation) + public void tearDown() { + System.out.println("TEARDOWN BENCHMARK"); + tarantoolClient.close(); + } } @Benchmark - @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MINUTES) + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.MINUTES) public void measureSpeedOfWriteAndReadViaSharedBuffer(SpeedOfWriteAndReadState state) { try { + System.out.println("RUN BENCHMARK. state is " + state); state.tarantoolClient.asyncOps().insert(1, Arrays.asList("a", "b")).get(); } catch (Exception e) { throw new IllegalArgumentException("Exception occurred while benchmarking", e); From de2e9eff7e9cf2e7e558dd35d40460dd4e742990 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Mon, 18 Feb 2019 02:40:58 +0300 Subject: [PATCH 09/30] Yet another refactor iteration --- .../org/tarantool/TestTarantoolClient.java | 18 +- src/main/java/org/tarantool/JDBCBridge.java | 15 +- .../java/org/tarantool/SqlProtoUtils.java | 51 ++++++ .../java/org/tarantool/TarantoolBase.java | 166 ++---------------- .../org/tarantool/TarantoolClientImpl.java | 92 ++-------- .../org/tarantool/TarantoolConnection.java | 29 +-- .../tarantool/server/BinaryProtoUtils.java | 133 +++++++++++++- .../server/TarantoolBinaryPackage.java | 15 ++ .../tarantool/server/TarantoolNodeInfo.java | 20 +++ .../jdbc/JdbcExceptionHandlingTest.java | 5 +- .../org/tarantool/DodgeSocketChannel.java | 25 ++- .../org/tarantool/DodgeSocketChannelTest.java | 22 ++- 12 files changed, 315 insertions(+), 276 deletions(-) create mode 100644 src/main/java/org/tarantool/SqlProtoUtils.java create mode 100644 src/main/java/org/tarantool/server/TarantoolNodeInfo.java diff --git a/src/it/java/org/tarantool/TestTarantoolClient.java b/src/it/java/org/tarantool/TestTarantoolClient.java index 24ec0e39..a27e7403 100644 --- a/src/it/java/org/tarantool/TestTarantoolClient.java +++ b/src/it/java/org/tarantool/TestTarantoolClient.java @@ -1,5 +1,7 @@ package org.tarantool; +import org.tarantool.server.TarantoolBinaryPackage; + import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; @@ -55,16 +57,6 @@ public void run() { t.start(); } - @Override - protected void writeFully(SocketChannel channel, ByteBuffer buffer) throws IOException { - try { - Thread.sleep(1L); - } catch (InterruptedException e) { - e.printStackTrace(); - } - super.writeFully(channel, buffer); - } - @Override protected void configureThreads(String threadName) { super.configureThreads(threadName); @@ -81,14 +73,14 @@ protected void reconnect(int retry, Throwable lastError) { } @Override - protected void complete(long code, CompletableFuture q) { - super.complete(code, q); + protected void complete(TarantoolBinaryPackage pack, CompletableFuture q) { + super.complete(pack, q); + Long code = pack.getCode(); if (code != 0) { System.out.println(code); } s.release(); } - } public static void main(String[] args) throws IOException, InterruptedException, ExecutionException, SQLException { diff --git a/src/main/java/org/tarantool/JDBCBridge.java b/src/main/java/org/tarantool/JDBCBridge.java index b31af64d..e650c43e 100644 --- a/src/main/java/org/tarantool/JDBCBridge.java +++ b/src/main/java/org/tarantool/JDBCBridge.java @@ -8,6 +8,7 @@ import java.util.Map; import org.tarantool.jdbc.SQLResultSet; +import org.tarantool.server.TarantoolBinaryPackage; public class JDBCBridge { public static final JDBCBridge EMPTY = new JDBCBridge(Collections.emptyList(), Collections.>emptyList()); @@ -16,8 +17,8 @@ public class JDBCBridge { final Map columnsByName; final List> rows; - protected JDBCBridge(TarantoolConnection connection) { - this(connection.getSQLMetadata(),connection.getSQLData()); + protected JDBCBridge(TarantoolBinaryPackage pack) { + this(SqlProtoUtils.getSQLMetadata(pack), SqlProtoUtils.getSQLData(pack)); } protected JDBCBridge(List sqlMetadata, List> rows) { @@ -30,8 +31,8 @@ protected JDBCBridge(List sqlMetadata, List fields, List> values) { } public static Object execute(TarantoolConnection connection, String sql, Object ... params) { - connection.sql(sql, params); - Long rowCount = connection.getSqlRowCount(); + TarantoolBinaryPackage pack = connection.sql(sql, params); + Long rowCount = SqlProtoUtils.getSqlRowCount(pack); if(rowCount == null) { - return new SQLResultSet(new JDBCBridge(connection)); + return new SQLResultSet(new JDBCBridge(pack)); } return rowCount.intValue(); } diff --git a/src/main/java/org/tarantool/SqlProtoUtils.java b/src/main/java/org/tarantool/SqlProtoUtils.java new file mode 100644 index 00000000..69fdf572 --- /dev/null +++ b/src/main/java/org/tarantool/SqlProtoUtils.java @@ -0,0 +1,51 @@ +package org.tarantool; + +import org.tarantool.server.TarantoolBinaryPackage; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public abstract class SqlProtoUtils { + + + public static List> readSqlResult(TarantoolBinaryPackage pack) { + List> data = (List>) pack.getBody().get(Key.DATA.getId()); + + List> values = new ArrayList>(data.size()); + List metaData = getSQLMetadata(pack); + LinkedHashMap value = new LinkedHashMap(); + for (List row : data) { + for (int i = 0; i < row.size(); i++) { + value.put(metaData.get(i).getName(), row.get(i)); + } + values.add(value); + } + return values; + } + + public static List> getSQLData(TarantoolBinaryPackage pack) { + return (List>) pack.getBody().get(Key.DATA.getId()); + } + + + public static List getSQLMetadata(TarantoolBinaryPackage pack) { + List> meta = (List>) pack.getBody().get(Key.SQL_METADATA.getId()); + List values = new ArrayList(meta.size()); + for (Map c : meta) { + values.add(new TarantoolBase.SQLMetaData((String) c.get(Key.SQL_FIELD_NAME.getId()))); + } + return values; + } + + public static Long getSqlRowCount(TarantoolBinaryPackage pack) { + Map info = (Map) pack.getBody().get(Key.SQL_INFO.getId()); + Number rowCount; + if (info != null && (rowCount = ((Number) info.get(Key.SQL_ROW_COUNT.getId()))) != null) { + return rowCount.longValue(); + } + return null; + } +} diff --git a/src/main/java/org/tarantool/TarantoolBase.java b/src/main/java/org/tarantool/TarantoolBase.java index 22833f6c..91fb4048 100644 --- a/src/main/java/org/tarantool/TarantoolBase.java +++ b/src/main/java/org/tarantool/TarantoolBase.java @@ -1,25 +1,18 @@ package org.tarantool; -import org.tarantool.server.*; +import org.tarantool.server.BinaryProtoUtils; +import org.tarantool.server.TarantoolNodeInfo; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.IOException; -import java.io.OutputStream; -import java.net.*; +import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; public abstract class TarantoolBase extends AbstractTarantoolOps, Object, Result> { - protected static final String WELCOME = "Tarantool "; + protected String serverVersion; /** * Connection state @@ -28,13 +21,6 @@ public abstract class TarantoolBase extends AbstractTarantoolOps headers; - protected Map body; public TarantoolBase() { } @@ -42,112 +28,17 @@ public TarantoolBase() { public TarantoolBase(String username, String password, Socket socket) { super(); try { -// Socket socket = Socket. - - this.is = new DataInputStream(cis = new CountInputStreamImpl(socket.getInputStream())); - byte[] bytes = new byte[64]; - is.readFully(bytes); - String firstLine = new String(bytes); - if (!firstLine.startsWith(WELCOME)) { - close(); - throw new CommunicationException("Welcome message should starts with tarantool but starts with '" + firstLine + "'", new IllegalStateException("Invalid welcome packet")); - } - serverVersion = firstLine.substring(WELCOME.length()); - is.readFully(bytes); - this.salt = new String(bytes); - if (username != null && password != null) { - ByteBuffer authPacket = createAuthPacket(username, password); - OutputStream os = socket.getOutputStream(); - os.write(authPacket.array(), 0, authPacket.remaining()); - os.flush(); - readPacket(is); - Long code = (Long) headers.get(Key.CODE.getId()); - if (code != 0) { - throw serverError(code, body.get(Key.ERROR.getId())); - } - } + TarantoolNodeInfo info = BinaryProtoUtils.connect(socket, username, password); + this.serverVersion = info.getServerVersion(); + this.salt = info.getSalt(); + } catch (CommunicationException e) { + close(); + throw e; } catch (IOException e) { - try { - is.close(); - } catch (IOException ignored) { - - } - try { - cis.close(); - } catch (IOException ignored) { - - } throw new CommunicationException("Couldn't connect to tarantool", e); } } - - protected ByteBuffer createAuthPacket(String username, final String password) throws IOException { - final MessageDigest sha1; - try { - sha1 = MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(e); - } - List auth = new ArrayList(2); - auth.add("chap-sha1"); - - byte[] p = sha1.digest(password.getBytes()); - - sha1.reset(); - byte[] p2 = sha1.digest(p); - - sha1.reset(); - sha1.update(Base64.decode(salt), 0, 20); - sha1.update(p2); - byte[] scramble = sha1.digest(); - for (int i = 0, e = 20; i < e; i++) { - p[i] ^= scramble[i]; - } - auth.add(p); - return createPacket(Code.AUTH, 0L, null, Key.USER_NAME, username, Key.TUPLE, auth); - } - - protected ByteBuffer createPacket(Code code, Long syncId, Long schemaId, Object... args) throws IOException { - TarantoolClientImpl.ByteArrayOutputStream bos = new TarantoolClientImpl.ByteArrayOutputStream(initialRequestSize); - bos.write(new byte[5]); - DataOutputStream ds = new DataOutputStream(bos); - Map header = new EnumMap(Key.class); - Map body = new EnumMap(Key.class); - header.put(Key.CODE, code); - header.put(Key.SYNC, syncId); - if (schemaId != null) { - header.put(Key.SCHEMA_ID, schemaId); - } - if (args != null) { - for (int i = 0, e = args.length; i < e; i += 2) { - Object value = args[i + 1]; - body.put((Key) args[i], value); - } - } - msgPackLite.pack(header, ds); - msgPackLite.pack(body, ds); - ds.flush(); - ByteBuffer buffer = bos.toByteBuffer(); - buffer.put(0, (byte) 0xce); - buffer.putInt(1, bos.size() - 5); - return buffer; - } - - protected TarantoolBinaryPackage readPacket(SocketChannel channel) throws IOException { - return BinaryProtoUtils.readPacket(channel); - } - - protected void readPacket(DataInputStream is) throws IOException { - int size = ((Number) msgPackLite.unpack(is)).intValue(); - long mark = cis.getBytesRead(); - headers = (Map) msgPackLite.unpack(is); - if (cis.getBytesRead() - mark < size) { - body = (Map) msgPackLite.unpack(is); - } - is.skipBytes((int) (cis.getBytesRead() - mark - size)); - } - protected static class SQLMetaData { protected String name; @@ -167,43 +58,6 @@ public String toString() { } } - protected List getSQLMetadata() { - List> meta = (List>) body.get(Key.SQL_METADATA.getId()); - List values = new ArrayList(meta.size()); - for(Map c:meta ) { - values.add(new SQLMetaData((String) c.get(Key.SQL_FIELD_NAME.getId()))); - } - return values; - } - - protected List> getSQLData() { - return (List>) body.get(Key.DATA.getId()); - } - - protected List> readSqlResult(List> data) { - List> values = new ArrayList>(data.size()); - List metaData = getSQLMetadata(); - LinkedHashMap value = new LinkedHashMap(); - for (List row : data) { - for (int i = 0; i < row.size(); i++) { - value.put(metaData.get(i).getName(), row.get(i)); - } - values.add(value); - } - return values; - } - - - protected Long getSqlRowCount() { - Map info = (Map) body.get(Key.SQL_INFO.getId()); - Number rowCount; - if (info != null && (rowCount = ((Number) info.get(Key.SQL_ROW_COUNT.getId()))) != null) { - return rowCount.longValue(); - } - return null; - } - - protected TarantoolException serverError(long code, Object error) { return new TarantoolException(code, error instanceof String ? (String) error : new String((byte[]) error)); } diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index 7848d0f0..785ad4fe 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -4,7 +4,6 @@ import java.io.*; import java.net.InetSocketAddress; -import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.Iterator; @@ -143,39 +142,10 @@ protected void reconnect(int retry, Throwable lastError) { protected void connect(final SocketChannel channel) throws Exception { try { - DataInputStream is = new DataInputStream(cis = new ByteBufferInputStream(channel)); - byte[] bytes = new byte[64]; - is.readFully(bytes); - String firstLine = new String(bytes); - if (!firstLine.startsWith("Tarantool")) { - CommunicationException e = new CommunicationException("Welcome message should starts with tarantool " + - "but starts with '" + firstLine + "'", new IllegalStateException("Invalid welcome packet")); - - close(e); - throw e; - } - is.readFully(bytes); - this.salt = new String(bytes); - if (config.username != null && config.password != null) { - writeFully(channel, createAuthPacket(config.username, config.password)); - readPacket(is); - Long code = (Long) headers.get(Key.CODE.getId()); - if (code != 0) { - throw serverError(code, body.get(Key.ERROR.getId())); - } - } - this.is = is; + TarantoolNodeInfo nodeInfo = BinaryProtoUtils.connect(channel, config.username, config.password); + this.salt = nodeInfo.getSalt(); + this.serverVersion = nodeInfo.getServerVersion(); } catch (IOException e) { - try { - is.close(); - } catch (IOException ignored) { - - } - try { - cis.close(); - } catch (IOException ignored) { - - } throw new CommunicationException("Couldn't connect to tarantool", e); } channel.configureBlocking(false); @@ -348,7 +318,7 @@ private boolean directWrite(ByteBuffer buffer) throws InterruptedException, IOEx if (rem > initialRequestSize) { stats.directPacketSizeGrowth++; } - writeFully(channel, buffer); + BinaryProtoUtils.writeFully(channel, buffer); stats.directWrite++; wait.incrementAndGet(); } finally { @@ -367,14 +337,16 @@ protected void readThread() { try { while (!Thread.currentThread().isInterrupted()) { try { - long code; - readPacket(is); - code = (Long) headers.get(Key.CODE.getId()); + TarantoolBinaryPackage pack = BinaryProtoUtils.readPacket(channel); + + Map headers = pack.getHeaders(); + Long syncId = (Long) headers.get(Key.SYNC.getId()); CompletableFuture future = futures.remove(syncId); + stats.received++; wait.decrementAndGet(); - complete(code, future); + complete(pack, future); } catch (Exception e) { die("Cant read answer", e); return; @@ -404,7 +376,7 @@ protected void writeThread() { writerBuffer.flip(); writeLock.lock(); try { - writeFully(channel, writerBuffer); + BinaryProtoUtils.writeFully(channel, writerBuffer); } finally { writeLock.unlock(); } @@ -421,29 +393,30 @@ protected void fail(CompletableFuture q, Exception e) { q.completeExceptionally(e); } - protected void complete(long code, CompletableFuture q) { + protected void complete(TarantoolBinaryPackage pack, CompletableFuture q) { if (q != null) { + long code = pack.getCode(); if (code == 0) { - List data = (List) body.get(Key.DATA.getId()); + List data = (List) pack.getBody().get(Key.DATA.getId()); if (code == Code.EXECUTE.getId()) { - completeSql(q, (List>) data); + completeSql(q, pack); } else { ((CompletableFuture) q).complete(data); } } else { - Object error = body.get(Key.ERROR.getId()); + Object error = pack.getBody().get(Key.ERROR.getId()); fail(q, serverError(code, error)); } } } - protected void completeSql(CompletableFuture q, List> data) { - Long rowCount = getSqlRowCount(); + protected void completeSql(CompletableFuture q, TarantoolBinaryPackage pack) { + Long rowCount = SqlProtoUtils.getSqlRowCount(pack); if (rowCount!=null) { ((CompletableFuture) q).complete(rowCount); } else { - List> values = readSqlResult(data); + List> values = SqlProtoUtils.readSqlResult(pack); ((CompletableFuture) q).complete(values); } } @@ -464,19 +437,6 @@ protected T syncGet(Future r) { } } - protected void writeFully(OutputStream stream, ByteBuffer buffer) throws IOException { -// stream.write(bu.ar); //todo? - } - - protected void writeFully(SocketChannel channel, ByteBuffer buffer) throws IOException { - long code = 0; - while (buffer.remaining() > 0 && (code = channel.write(buffer)) > -1) { - } - if (code < 0) { - throw new SocketException("write failed code: " + code); - } - } - @Override public void close() { close(new Exception("Connection is closed.")); @@ -502,20 +462,6 @@ protected void stopIO() { if (writer != null) { writer.interrupt(); } - if (is != null) { - try { - is.close(); - } catch (IOException ignored) { - - } - } - if (cis != null) { - try { - cis.close(); - } catch (IOException ignored) { - - } - } closeChannel(channel); } diff --git a/src/main/java/org/tarantool/TarantoolConnection.java b/src/main/java/org/tarantool/TarantoolConnection.java index c64e686d..b3f2ca99 100644 --- a/src/main/java/org/tarantool/TarantoolConnection.java +++ b/src/main/java/org/tarantool/TarantoolConnection.java @@ -7,7 +7,6 @@ import java.io.OutputStream; import java.net.*; import java.nio.ByteBuffer; -import java.nio.channels.*; import java.util.List; import java.util.Map; @@ -15,7 +14,6 @@ public class TarantoolConnection extends TarantoolBase> implements Taran protected InputStream in; protected OutputStream out; protected Socket socket; - protected SocketChannel channel; public TarantoolConnection(String username, String password, Socket socket) throws IOException { @@ -27,22 +25,28 @@ public TarantoolConnection(String username, String password, Socket socket) thro @Override protected List exec(Code code, Object... args) { + TarantoolBinaryPackage responsePackage = writeAndRead(code, args); + return (List) responsePackage.getBody().get(Key.DATA.getId()); + } + + protected TarantoolBinaryPackage writeAndRead(Code code, Object... args) { try { ByteBuffer packet = BinaryProtoUtils.createPacket(code, syncId.incrementAndGet(), null, args); out.write(packet.array(), 0, packet.remaining()); out.flush(); - TarantoolBinaryPackage responsePackage = BinaryProtoUtils.readPacket(is); + TarantoolBinaryPackage responsePackage = BinaryProtoUtils.readPacket(in); Map headers = responsePackage.getHeaders(); Map body = responsePackage.getBody(); Long c = (Long) headers.get(Key.CODE.getId()); - if (c == 0) { - return (List) body.get(Key.DATA.getId()); - } else { + + if (c != 0) { throw serverError(c, body.get(Key.ERROR.getId())); } + + return responsePackage; } catch (IOException e) { close(); throw new CommunicationException("Couldn't execute query", e); @@ -69,21 +73,20 @@ public void close() { } } - @Override public Long update(String sql, Object... bind) { - sql(sql, bind); - return getSqlRowCount(); + TarantoolBinaryPackage pack = sql(sql, bind); + return SqlProtoUtils.getSqlRowCount(pack); } @Override public List> query(String sql, Object... bind) { - sql(sql, bind); - return readSqlResult((List>) body.get(Key.DATA)); + TarantoolBinaryPackage pack = sql(sql, bind); + return SqlProtoUtils.readSqlResult(pack); } - protected void sql(String sql, Object[] bind) { - exec(Code.EXECUTE, Key.SQL_TEXT, sql, Key.SQL_BIND, bind); + protected TarantoolBinaryPackage sql(String sql, Object[] bind) { + return writeAndRead(Code.EXECUTE, Key.SQL_TEXT, sql, Key.SQL_BIND, bind); } public boolean isClosed() { diff --git a/src/main/java/org/tarantool/server/BinaryProtoUtils.java b/src/main/java/org/tarantool/server/BinaryProtoUtils.java index 935e02e5..729aa5d6 100644 --- a/src/main/java/org/tarantool/server/BinaryProtoUtils.java +++ b/src/main/java/org/tarantool/server/BinaryProtoUtils.java @@ -1,13 +1,17 @@ package org.tarantool.server; import org.tarantool.Base64; +import org.tarantool.ByteBufferInputStream; import org.tarantool.Code; +import org.tarantool.CommunicationException; import org.tarantool.CountInputStreamImpl; import org.tarantool.Key; import org.tarantool.MsgPackLite; -import org.tarantool.server.TarantoolServer.CountingInputStream; +import org.tarantool.TarantoolException; import java.io.*; +import java.net.Socket; +import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.security.MessageDigest; @@ -20,7 +24,7 @@ public abstract class BinaryProtoUtils { private final static int DEFAULT_INITIAL_REQUEST_SIZE = 4096; - + public static final String WELCOME = "Tarantool "; public static TarantoolBinaryPackage readPacket(InputStream inputStream) throws IOException { int size = inputStream.read(); @@ -37,19 +41,138 @@ public static TarantoolBinaryPackage readPacket(InputStream inputStream) throws return new TarantoolBinaryPackage(headers, body); } + /** + * + * Connects to a tarantool node described by {@code socket}. Performs an authentication if required + * + * @param socket a socket channel to tarantool node + * @param username auth username + * @param password auth password + * @return object with information about a connection/ + * @throws IOException in case of any IO fails + * @throws CommunicationException when welcome string is invalid + * @throws TarantoolException in case of failed authentication + */ + public static TarantoolNodeInfo connect(Socket socket, String username, String password) throws IOException { + byte[] inputBytes = new byte[64]; + + InputStream inputStream = socket.getInputStream(); + inputStream.read(inputBytes); + + String firstLine = new String(inputBytes); + if (!firstLine.startsWith(WELCOME)) { + String errMsg = "Failed to connect to node " + socket.getRemoteSocketAddress().toString() + ":" + + " Welcome message should starts with tarantool but starts with '" + firstLine + "'"; + throw new CommunicationException(errMsg, new IllegalStateException("Invalid welcome packet")); + } + + String serverVersion = firstLine.substring(WELCOME.length()); + + inputStream.read(inputBytes); + String salt = new String(inputBytes); + if (username != null && password != null) { + ByteBuffer authPacket = createAuthPacket(username, password, salt); + + OutputStream os = socket.getOutputStream(); + os.write(authPacket.array(), 0, authPacket.remaining()); + os.flush(); + + TarantoolBinaryPackage responsePackage = readPacket(socket.getInputStream()); + Long code = (Long) responsePackage.getHeaders().get(Key.CODE.getId()); + if (code != 0) { + Object error = responsePackage.getBody().get(Key.ERROR.getId()); + throw new TarantoolException(code, error instanceof String ? (String) error : new String((byte[]) error)); + } + } + + return new TarantoolNodeInfo(salt, serverVersion); + } + + /** + * + * Connects to a tarantool node described by {@code socketChannel}. Performs an authentication if required + * + * @param channel a socket channel to tarantool node + * @param username auth username + * @param password auth password + * @return object with information about a connection/ + * @throws IOException in case of any IO fails + * @throws CommunicationException when welcome string is invalid + * @throws TarantoolException in case of failed authentication + */ + public static TarantoolNodeInfo connect(SocketChannel channel, String username, String password) throws IOException { + ByteBuffer welcomeBytes = ByteBuffer.wrap(new byte[64]); + channel.read(welcomeBytes); + + String firstLine = new String(welcomeBytes.array()); + if (!firstLine.startsWith(WELCOME)) { + String errMsg = "Failed to connect to node " + channel.getRemoteAddress().toString() + ":" + + " Welcome message should starts with tarantool but starts with '" + firstLine + "'"; + throw new CommunicationException(errMsg, new IllegalStateException("Invalid welcome packet")); + } + String serverVersion = firstLine.substring(WELCOME.length()); + + welcomeBytes.clear(); + channel.read(welcomeBytes); + String salt = new String(welcomeBytes.array()); + if (username != null && password != null) { + ByteBuffer authPacket = createAuthPacket(username, password, salt); + writeFully(channel, authPacket); + TarantoolBinaryPackage authResponse = readPacket(channel); + Long code = (Long) authResponse.getHeaders().get(Key.CODE.getId()); + if (code != 0) { + Object error = authResponse.getBody().get(Key.ERROR.getId()); + throw new TarantoolException(code, error instanceof String ? (String) error : new String((byte[]) error)); + } + } + + return new TarantoolNodeInfo(salt, serverVersion); + } + + public static void writeFully(OutputStream stream, ByteBuffer buffer) throws IOException { + stream.write(buffer.array()); + stream.flush(); + } + + public static void writeFully(SocketChannel channel, ByteBuffer buffer) throws IOException { + long code = 0; + while (buffer.remaining() > 0 && (code = channel.write(buffer)) > -1) { + } + if (code < 0) { + throw new SocketException("write failed code: " + code); + } + } + public static TarantoolBinaryPackage readPacket(SocketChannel channel) throws IOException { - int size = channel.socket().getInputStream().read(); +// int size = channel.socket().getInputStream().read(); + InputStream inputStream = channel.socket().getInputStream(); + + int size = ((Number) getMsgPackLite().unpack(inputStream)).intValue(); ByteBuffer msgBuffer = ByteBuffer.allocate(size - 1); channel.read(msgBuffer); CountInputStreamImpl msgStream = new CountInputStreamImpl(new ByteArrayInputStream(msgBuffer.array())); - Map headers = (Map) getMsgPackLite().unpack(msgStream); + Object unpackedHeaders = getMsgPackLite().unpack(msgStream); + if (!(unpackedHeaders instanceof Map)) { + //noinspection ConstantConditions + throw new CommunicationException("Error while unpacking headers of tarantool response: " + + "expected type Map but was " + unpackedHeaders != null ? unpackedHeaders.getClass().toString() : "null"); + } + //noinspection unchecked (checked above) + Map headers = (Map) unpackedHeaders; Map body = null; if (msgStream.getBytesRead() < size) { - body = (Map) getMsgPackLite().unpack(msgStream); + Object unpackedBody = getMsgPackLite().unpack(msgStream); + if (!(unpackedBody instanceof Map)) { + //noinspection ConstantConditions + throw new CommunicationException("Error while unpacking body of tarantool response: " + + "expected type Map but was " + unpackedBody != null ? unpackedBody.getClass().toString() : "null"); + } + //noinspection unchecked (checked above) + body = (Map) unpackedBody; } return new TarantoolBinaryPackage(headers, body); diff --git a/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java b/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java index 35dbaa3b..adb161d0 100644 --- a/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java +++ b/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java @@ -1,5 +1,7 @@ package org.tarantool.server; +import org.tarantool.Key; + import java.util.Map; public class TarantoolBinaryPackage { @@ -16,6 +18,19 @@ public TarantoolBinaryPackage(Map headers) { body = null; } + public Long getCode() { + Object potenticalCode = headers.get(Key.CODE.getId()); + + if (!(potenticalCode instanceof Long)) { + //noinspection ConstantConditions + throw new IllegalStateException("A value contained in the header by key '" + Key.CODE.name() + "'" + + " is not instance of Long class: " + + potenticalCode != null ? potenticalCode.getClass().toString() : "null"); + } + + return (Long) potenticalCode; + } + public Map getHeaders() { return headers; } diff --git a/src/main/java/org/tarantool/server/TarantoolNodeInfo.java b/src/main/java/org/tarantool/server/TarantoolNodeInfo.java new file mode 100644 index 00000000..7e39a488 --- /dev/null +++ b/src/main/java/org/tarantool/server/TarantoolNodeInfo.java @@ -0,0 +1,20 @@ +package org.tarantool.server; + +public class TarantoolNodeInfo { + + private final String salt; + private final String serverVersion; + + public TarantoolNodeInfo(String salt, String serverVersion) { + this.salt = salt; + this.serverVersion = serverVersion; + } + + public String getSalt() { + return salt; + } + + public String getServerVersion() { + return serverVersion; + } +} diff --git a/src/test/java/org/tarantool/jdbc/JdbcExceptionHandlingTest.java b/src/test/java/org/tarantool/jdbc/JdbcExceptionHandlingTest.java index e02c0e03..b680ce0f 100644 --- a/src/test/java/org/tarantool/jdbc/JdbcExceptionHandlingTest.java +++ b/src/test/java/org/tarantool/jdbc/JdbcExceptionHandlingTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.function.ThrowingConsumer; import org.tarantool.CommunicationException; import org.tarantool.TarantoolConnection; +import org.tarantool.server.TarantoolBinaryPackage; import java.io.IOException; import java.net.InetSocketAddress; @@ -297,8 +298,8 @@ class TestTarantoolConnection extends TarantoolConnection { super(null, null, mock(Socket.class)); } @Override - protected void sql(String sql, Object[] bind) { - super.sql(sql, bind); + protected TarantoolBinaryPackage sql(String sql, Object[] bind) { + return super.sql(sql, bind); } } } diff --git a/src/test/perf/org/tarantool/DodgeSocketChannel.java b/src/test/perf/org/tarantool/DodgeSocketChannel.java index 9309d991..a8dd86f8 100644 --- a/src/test/perf/org/tarantool/DodgeSocketChannel.java +++ b/src/test/perf/org/tarantool/DodgeSocketChannel.java @@ -12,6 +12,10 @@ public class DodgeSocketChannel extends SocketChannel { private ArrayBlockingQueue blockingQueue; + public static String FAKE_WELCOME_STRING = "Tarantool 1.10.2 (Binary) 53b5547d-0560-4383-b303-4861572d4517\n" + + "1bsTc5Ibljs94bsexVze+ZngV1vBJcstoYDxSTa9h8k="; + private boolean isWellcomePerformed = false; + public DodgeSocketChannel(Integer queueSize) { super(null); this.blockingQueue = new ArrayBlockingQueue<>(queueSize); @@ -88,6 +92,19 @@ public SocketAddress getRemoteAddress() throws IOException { @Override public int read(ByteBuffer dst) throws IOException { + synchronized (this) { + if (isWellcomePerformed) { + return dodgeRead(dst); + } else { + byte[] bytes = FAKE_WELCOME_STRING.getBytes(); + dst.put(bytes); + isWellcomePerformed = true; + return bytes.length; + } + } + } + + private int dodgeRead(ByteBuffer dst) { try { byte[] bytes = blockingQueue.take(); dst.put(bytes); @@ -104,8 +121,12 @@ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { @Override public int write(ByteBuffer src) throws IOException { - blockingQueue.add(src.array()); - return src.array().length; + synchronized (this) { + if (isWellcomePerformed) { + blockingQueue.add(src.array()); + } + return src.array().length; + } // throw new UnsupportedOperationException("This operation is not implemented"); } diff --git a/src/test/perf/org/tarantool/DodgeSocketChannelTest.java b/src/test/perf/org/tarantool/DodgeSocketChannelTest.java index ad89645f..76e9dcfe 100644 --- a/src/test/perf/org/tarantool/DodgeSocketChannelTest.java +++ b/src/test/perf/org/tarantool/DodgeSocketChannelTest.java @@ -3,19 +3,31 @@ import org.junit.jupiter.api.*; import java.io.*; -import java.net.*; import java.nio.*; -import java.nio.channels.*; -import java.nio.channels.spi.*; -import java.util.*; -import java.util.concurrent.*; public class DodgeSocketChannelTest { + + @Test + void testReadWellcome() throws IOException { + DodgeSocketChannel dodgeSocketChannel = new DodgeSocketChannel(10); + + ByteBuffer buffer = readWellcome(dodgeSocketChannel); + + Assertions.assertEquals(DodgeSocketChannel.FAKE_WELCOME_STRING, new String(buffer.array())); + } + + private ByteBuffer readWellcome(DodgeSocketChannel dodgeSocketChannel) throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(new byte[DodgeSocketChannel.FAKE_WELCOME_STRING.length()]); + dodgeSocketChannel.read(buffer); + return buffer; + } + @Test void testCorrectDodge() throws IOException { DodgeSocketChannel dodgeSocketChannel = new DodgeSocketChannel(10); + readWellcome(dodgeSocketChannel); dodgeSocketChannel.write(ByteBuffer.wrap("one".getBytes())); dodgeSocketChannel.write(ByteBuffer.wrap("two".getBytes())); From 848fcf2014075fe361d376b20f05b9ddae706706 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Sun, 24 Feb 2019 22:13:30 +0300 Subject: [PATCH 10/30] Refactor reading from channel --- .../java/org/tarantool/CountInputStream.java | 2 +- .../org/tarantool/TarantoolClientImpl.java | 2 +- .../tarantool/server/BinaryProtoUtils.java | 73 +++++++++++++++---- .../server/ByteBufferBackedInputStream.java | 45 ++++++++++++ .../server/SelectorChannelReadHelper.java | 33 +++++++++ .../server/TarantoolBinaryPackage.java | 5 ++ .../org/tarantool/server/TarantoolNode.java | 2 +- .../org/tarantool/server/TarantoolServer.java | 16 +++- .../AbstractTarantoolConnectorIT.java | 2 +- 9 files changed, 158 insertions(+), 22 deletions(-) create mode 100644 src/main/java/org/tarantool/server/ByteBufferBackedInputStream.java create mode 100644 src/main/java/org/tarantool/server/SelectorChannelReadHelper.java diff --git a/src/main/java/org/tarantool/CountInputStream.java b/src/main/java/org/tarantool/CountInputStream.java index afef4f29..b0f627c1 100644 --- a/src/main/java/org/tarantool/CountInputStream.java +++ b/src/main/java/org/tarantool/CountInputStream.java @@ -3,5 +3,5 @@ import java.io.InputStream; public abstract class CountInputStream extends InputStream { - abstract long getBytesRead(); + public abstract long getBytesRead(); } diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index 785ad4fe..4b9800af 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -397,11 +397,11 @@ protected void complete(TarantoolBinaryPackage pack, CompletableFuture q) { if (q != null) { long code = pack.getCode(); if (code == 0) { - List data = (List) pack.getBody().get(Key.DATA.getId()); if (code == Code.EXECUTE.getId()) { completeSql(q, pack); } else { + List data = (List) pack.getBody().get(Key.DATA.getId()); ((CompletableFuture) q).complete(data); } } else { diff --git a/src/main/java/org/tarantool/server/BinaryProtoUtils.java b/src/main/java/org/tarantool/server/BinaryProtoUtils.java index 729aa5d6..72fa17a8 100644 --- a/src/main/java/org/tarantool/server/BinaryProtoUtils.java +++ b/src/main/java/org/tarantool/server/BinaryProtoUtils.java @@ -4,6 +4,7 @@ import org.tarantool.ByteBufferInputStream; import org.tarantool.Code; import org.tarantool.CommunicationException; +import org.tarantool.CountInputStream; import org.tarantool.CountInputStreamImpl; import org.tarantool.Key; import org.tarantool.MsgPackLite; @@ -42,16 +43,15 @@ public static TarantoolBinaryPackage readPacket(InputStream inputStream) throws } /** - * * Connects to a tarantool node described by {@code socket}. Performs an authentication if required * - * @param socket a socket channel to tarantool node + * @param socket a socket channel to tarantool node * @param username auth username * @param password auth password * @return object with information about a connection/ - * @throws IOException in case of any IO fails + * @throws IOException in case of any IO fails * @throws CommunicationException when welcome string is invalid - * @throws TarantoolException in case of failed authentication + * @throws TarantoolException in case of failed authentication */ public static TarantoolNodeInfo connect(Socket socket, String username, String password) throws IOException { byte[] inputBytes = new byte[64]; @@ -89,16 +89,15 @@ public static TarantoolNodeInfo connect(Socket socket, String username, String p } /** - * * Connects to a tarantool node described by {@code socketChannel}. Performs an authentication if required * - * @param channel a socket channel to tarantool node + * @param channel a socket channel to tarantool node * @param username auth username * @param password auth password * @return object with information about a connection/ - * @throws IOException in case of any IO fails + * @throws IOException in case of any IO fails * @throws CommunicationException when welcome string is invalid - * @throws TarantoolException in case of failed authentication + * @throws TarantoolException in case of failed authentication */ public static TarantoolNodeInfo connect(SocketChannel channel, String username, String password) throws IOException { ByteBuffer welcomeBytes = ByteBuffer.wrap(new byte[64]); @@ -143,18 +142,60 @@ public static void writeFully(SocketChannel channel, ByteBuffer buffer) throws I } } + public static final int LENGTH_OF_SIZE_MESSAGE = 5; public static TarantoolBinaryPackage readPacket(SocketChannel channel) throws IOException { -// int size = channel.socket().getInputStream().read(); - InputStream inputStream = channel.socket().getInputStream(); + channel.configureBlocking(false); + SelectorChannelReadHelper bufferReader = new SelectorChannelReadHelper(channel); + + ByteBuffer buffer = ByteBuffer.allocate(LENGTH_OF_SIZE_MESSAGE); + bufferReader.readFully(buffer); + + buffer.flip(); + int size = ((Number) getMsgPackLite().unpack(new ByteBufferBackedInputStream(buffer))).intValue(); + buffer = ByteBuffer.allocate(size); + bufferReader.readFully(buffer); + + buffer.flip(); + ByteBufferBackedInputStream msgBytesStream = new ByteBufferBackedInputStream(buffer); + Object unpackedHeaders = getMsgPackLite().unpack(msgBytesStream); + if (!(unpackedHeaders instanceof Map)) { + //noinspection ConstantConditions + throw new CommunicationException("Error while unpacking headers of tarantool response: " + + "expected type Map but was " + unpackedHeaders != null ? unpackedHeaders.getClass().toString() : "null"); + } + //noinspection unchecked (checked above) + Map headers = (Map) unpackedHeaders; + + Map body = null; + if (msgBytesStream.hasAvailable()) { + Object unpackedBody = getMsgPackLite().unpack(msgBytesStream); + if (!(unpackedBody instanceof Map)) { + //noinspection ConstantConditions + throw new CommunicationException("Error while unpacking body of tarantool response: " + + "expected type Map but was " + unpackedBody != null ? unpackedBody.getClass().toString() : "null"); + } + //noinspection unchecked (checked above) + body = (Map) unpackedBody; + } + + return new TarantoolBinaryPackage(headers, body); + } + + @Deprecated + public static TarantoolBinaryPackage readPacketOld(SocketChannel channel) throws IOException { + CountInputStream inputStream = new ByteBufferInputStream(channel); + return readPacket(inputStream); + } + + @Deprecated + private static TarantoolBinaryPackage readPacket(CountInputStream inputStream) throws IOException { int size = ((Number) getMsgPackLite().unpack(inputStream)).intValue(); - ByteBuffer msgBuffer = ByteBuffer.allocate(size - 1); - channel.read(msgBuffer); - CountInputStreamImpl msgStream = new CountInputStreamImpl(new ByteArrayInputStream(msgBuffer.array())); + long mark = inputStream.getBytesRead(); - Object unpackedHeaders = getMsgPackLite().unpack(msgStream); + Object unpackedHeaders = getMsgPackLite().unpack(inputStream); if (!(unpackedHeaders instanceof Map)) { //noinspection ConstantConditions throw new CommunicationException("Error while unpacking headers of tarantool response: " + @@ -164,8 +205,8 @@ public static TarantoolBinaryPackage readPacket(SocketChannel channel) throws IO Map headers = (Map) unpackedHeaders; Map body = null; - if (msgStream.getBytesRead() < size) { - Object unpackedBody = getMsgPackLite().unpack(msgStream); + if (inputStream.getBytesRead() - mark < size) { + Object unpackedBody = getMsgPackLite().unpack(inputStream); if (!(unpackedBody instanceof Map)) { //noinspection ConstantConditions throw new CommunicationException("Error while unpacking body of tarantool response: " + diff --git a/src/main/java/org/tarantool/server/ByteBufferBackedInputStream.java b/src/main/java/org/tarantool/server/ByteBufferBackedInputStream.java new file mode 100644 index 00000000..c2a7ebb9 --- /dev/null +++ b/src/main/java/org/tarantool/server/ByteBufferBackedInputStream.java @@ -0,0 +1,45 @@ +package org.tarantool.server; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +class ByteBufferBackedInputStream extends InputStream { + + private final ByteBuffer buf; + + /** + * //todo add a comment + * @param buf a buffer have to be ready fo read (flipped) + */ + public ByteBufferBackedInputStream(ByteBuffer buf) { + this.buf = buf; + } + + public int read() throws IOException { + if (!buf.hasRemaining()) { + return -1; + } + return buf.get() & 0xFF; + } + + public int read(byte[] bytes, int off, int len) + throws IOException { + if (!buf.hasRemaining()) { + return -1; + } + + len = Math.min(len, buf.remaining()); + buf.get(bytes, off, len); + return len; + } + + @Override + public int available() { + return buf.remaining(); + } + + public boolean hasAvailable() { + return available() > 0; + } +} \ No newline at end of file diff --git a/src/main/java/org/tarantool/server/SelectorChannelReadHelper.java b/src/main/java/org/tarantool/server/SelectorChannelReadHelper.java new file mode 100644 index 00000000..af2428e3 --- /dev/null +++ b/src/main/java/org/tarantool/server/SelectorChannelReadHelper.java @@ -0,0 +1,33 @@ +package org.tarantool.server; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.nio.channels.spi.SelectorProvider; + +class SelectorChannelReadHelper { + private final SocketChannel channel; + private final Selector selector; + + public SelectorChannelReadHelper(SocketChannel channel) throws IOException { + if (channel.isBlocking()) { + throw new IllegalArgumentException("Channel have to be blocking"); + } + + this.channel = channel; + selector = SelectorProvider.provider().openSelector(); + channel.register(selector, SelectionKey.OP_READ); + } + + public void readFully(ByteBuffer buffer) throws IOException { + channel.read(buffer); + + while (buffer.remaining() > 0) { + selector.select();//todo think about read timeout + channel.read(buffer); + } + } + +} diff --git a/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java b/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java index adb161d0..8fbaf263 100644 --- a/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java +++ b/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java @@ -2,6 +2,7 @@ import org.tarantool.Key; +import java.util.Collections; import java.util.Map; public class TarantoolBinaryPackage { @@ -38,4 +39,8 @@ public Map getHeaders() { public Map getBody() { return body; } + + public boolean hasBody() { + return body != null && body.size() > 0; + } } diff --git a/src/main/java/org/tarantool/server/TarantoolNode.java b/src/main/java/org/tarantool/server/TarantoolNode.java index 73d03270..2e96b2ed 100644 --- a/src/main/java/org/tarantool/server/TarantoolNode.java +++ b/src/main/java/org/tarantool/server/TarantoolNode.java @@ -91,7 +91,7 @@ public static TarantoolNode create(InetSocketAddress socketAddress) { /** - * @param address hostname addess as String + * @param address hostname address as String * * @throws IllegalArgumentException if the port parameter is outside the range * of valid port values, or if the hostname parameter is null. diff --git a/src/main/java/org/tarantool/server/TarantoolServer.java b/src/main/java/org/tarantool/server/TarantoolServer.java index 071f8c6d..cba6069f 100644 --- a/src/main/java/org/tarantool/server/TarantoolServer.java +++ b/src/main/java/org/tarantool/server/TarantoolServer.java @@ -2,7 +2,6 @@ import org.tarantool.ByteBufferInputStream; import org.tarantool.CommunicationException; -import org.tarantool.CountInputStream; import org.tarantool.Key; import org.tarantool.MsgPackLite; import org.tarantool.SocketChannelProvider; @@ -29,12 +28,25 @@ public class TarantoolServer { private MsgPackLite msgPackLite = MsgPackLite.INSTANCE; + public static void connect(TarantoolNode node, String username, String password) { + SocketChannel channel; + try { + channel = SocketChannel.open(node.getSocketAddress()); + } catch (IOException e) { + throw new CommunicationException("Exception occurred while connecting to node " + node, e); + } +//todo +// TarantoolNodeInfo nodeInfo = BinaryProtoUtils.connect(channel, username, password); +// this.salt = nodeInfo.getSalt(); +// this.serverVersion = nodeInfo.getServerVersion(); + } + /** * Connection state */ protected String salt; - public TarantoolServer(SocketChannelProvider socketProvider, TarantoolClientConfig config) { + private TarantoolServer(SocketChannelProvider socketProvider, TarantoolClientConfig config) { this.socketProvider = socketProvider; this.config = config; diff --git a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java index 4c494f50..2e12e955 100644 --- a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java +++ b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java @@ -35,7 +35,7 @@ public abstract class AbstractTarantoolConnectorIT { protected static final int LISTEN = 3301; protected static final int ADMIN = 3313; protected static final int TIMEOUT = 500; - protected static final int RESTART_TIMEOUT = 2000; + protected static final int RESTART_TIMEOUT = 200000;//todo protected static final SocketChannelProvider socketChannelProvider = new TestSocketChannelProvider(host, port, RESTART_TIMEOUT); From 2239322e057e0c6478a5941da57908a110678392 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Sun, 24 Feb 2019 22:40:43 +0300 Subject: [PATCH 11/30] AbstractTarantoolConnectorIT restore RESTART_TIMEOUT value --- src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java index 2e12e955..4c494f50 100644 --- a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java +++ b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java @@ -35,7 +35,7 @@ public abstract class AbstractTarantoolConnectorIT { protected static final int LISTEN = 3301; protected static final int ADMIN = 3313; protected static final int TIMEOUT = 500; - protected static final int RESTART_TIMEOUT = 200000;//todo + protected static final int RESTART_TIMEOUT = 2000; protected static final SocketChannelProvider socketChannelProvider = new TestSocketChannelProvider(host, port, RESTART_TIMEOUT); From 10854fc69f9eb2eac92a2f2fbb848e26555d3525 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Mon, 25 Feb 2019 02:14:57 +0300 Subject: [PATCH 12/30] SelectorChannelReadHelper fix + add some todo --- .../org/tarantool/TarantoolClientImpl.java | 33 ++++++++++--------- .../tarantool/server/BinaryProtoUtils.java | 1 + .../server/SelectorChannelReadHelper.java | 12 +++++-- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index 4b9800af..8652fe14 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -42,9 +42,9 @@ public class TarantoolClientImpl extends TarantoolBase> implements Tar protected SocketChannel channel; protected ByteBuffer sharedBuffer; protected ByteBuffer writerBuffer; - protected ReentrantLock bufferLock = new ReentrantLock(false); - protected Condition bufferNotEmpty = bufferLock.newCondition(); - protected Condition bufferEmpty = bufferLock.newCondition(); + protected ReentrantLock writerBufferLock = new ReentrantLock(false); + protected Condition writerBufferNotEmpty = writerBufferLock.newCondition(); + protected Condition writerBufferEmpty = writerBufferLock.newCondition(); protected ReentrantLock writeLock = new ReentrantLock(true); /** @@ -149,12 +149,13 @@ protected void connect(final SocketChannel channel) throws Exception { throw new CommunicationException("Couldn't connect to tarantool", e); } channel.configureBlocking(false); + this.channel = channel; - bufferLock.lock(); + writerBufferLock.lock(); try { sharedBuffer.clear(); } finally { - bufferLock.unlock(); + writerBufferLock.unlock(); } this.thumbstone = null; startThreads(channel.socket().getRemoteSocketAddress().toString()); @@ -250,12 +251,12 @@ protected synchronized void die(String message, Exception cause) { } } - bufferLock.lock(); + writerBufferLock.lock(); try { sharedBuffer.clear(); - bufferEmpty.signalAll(); + writerBufferEmpty.signalAll(); } finally { - bufferLock.unlock(); + writerBufferLock.unlock(); } stopIO(); } @@ -277,7 +278,7 @@ protected void write(Code code, Long syncId, Long schemaId, Object... args) protected void sharedWrite(ByteBuffer buffer) throws InterruptedException, TimeoutException { long start = System.currentTimeMillis(); - if (bufferLock.tryLock(config.writeTimeoutMillis, TimeUnit.MILLISECONDS)) { + if (writerBufferLock.tryLock(config.writeTimeoutMillis, TimeUnit.MILLISECONDS)) { try { int rem = buffer.remaining(); stats.sharedMaxPacketSize = Math.max(stats.sharedMaxPacketSize, rem); @@ -288,7 +289,7 @@ protected void sharedWrite(ByteBuffer buffer) throws InterruptedException, Timeo stats.sharedEmptyAwait++; long remaining = config.writeTimeoutMillis - (System.currentTimeMillis() - start); try { - if (remaining < 1 || !bufferEmpty.await(remaining, TimeUnit.MILLISECONDS)) { + if (remaining < 1 || !writerBufferEmpty.await(remaining, TimeUnit.MILLISECONDS)) { stats.sharedEmptyAwaitTimeouts++; throw new TimeoutException(config.writeTimeoutMillis + "ms is exceeded while waiting for empty buffer you could configure write timeout it in TarantoolConfig"); } @@ -298,10 +299,10 @@ protected void sharedWrite(ByteBuffer buffer) throws InterruptedException, Timeo } sharedBuffer.put(buffer); wait.incrementAndGet(); - bufferNotEmpty.signalAll(); + writerBufferNotEmpty.signalAll(); stats.buffered++; } finally { - bufferLock.unlock(); + writerBufferLock.unlock(); } } else { stats.sharedWriteLockTimeouts++; @@ -361,17 +362,17 @@ protected void writeThread() { writerBuffer.clear(); while (!Thread.currentThread().isInterrupted()) { try { - bufferLock.lock(); + writerBufferLock.lock(); try { while (sharedBuffer.position() == 0) { - bufferNotEmpty.await(); + writerBufferNotEmpty.await(); } sharedBuffer.flip(); writerBuffer.put(sharedBuffer); sharedBuffer.clear(); - bufferEmpty.signalAll(); + writerBufferEmpty.signalAll(); } finally { - bufferLock.unlock(); + writerBufferLock.unlock(); } writerBuffer.flip(); writeLock.lock(); diff --git a/src/main/java/org/tarantool/server/BinaryProtoUtils.java b/src/main/java/org/tarantool/server/BinaryProtoUtils.java index 72fa17a8..32bea42d 100644 --- a/src/main/java/org/tarantool/server/BinaryProtoUtils.java +++ b/src/main/java/org/tarantool/server/BinaryProtoUtils.java @@ -146,6 +146,7 @@ public static void writeFully(SocketChannel channel, ByteBuffer buffer) throws I public static TarantoolBinaryPackage readPacket(SocketChannel channel) throws IOException { channel.configureBlocking(false); + //todo get rid of this because SelectorProvider.provider().openSelector() creates two pipes and socket in the /proc/fd SelectorChannelReadHelper bufferReader = new SelectorChannelReadHelper(channel); ByteBuffer buffer = ByteBuffer.allocate(LENGTH_OF_SIZE_MESSAGE); diff --git a/src/main/java/org/tarantool/server/SelectorChannelReadHelper.java b/src/main/java/org/tarantool/server/SelectorChannelReadHelper.java index af2428e3..4de5581b 100644 --- a/src/main/java/org/tarantool/server/SelectorChannelReadHelper.java +++ b/src/main/java/org/tarantool/server/SelectorChannelReadHelper.java @@ -1,5 +1,7 @@ package org.tarantool.server; +import org.tarantool.CommunicationException; + import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; @@ -22,11 +24,17 @@ public SelectorChannelReadHelper(SocketChannel channel) throws IOException { } public void readFully(ByteBuffer buffer) throws IOException { - channel.read(buffer); + int n = channel.read(buffer); + if (n < 0) { + throw new CommunicationException("Channel read failed " + n); + } while (buffer.remaining() > 0) { selector.select();//todo think about read timeout - channel.read(buffer); + n = channel.read(buffer); + if (n < 0) { + throw new CommunicationException("Channel read failed " + n); + } } } From 2b0286261bca2ea72338dff9c1260f9e652b9f22 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Mon, 25 Feb 2019 23:03:54 +0300 Subject: [PATCH 13/30] Move connection logic to channel provider. Part1 --- .../org/tarantool/TestTarantoolClient.java | 2 +- .../RoundRobinSocketProviderImpl.java | 38 +--- .../SimpleSocketChannelProvider.java | 33 +++- .../org/tarantool/SocketChannelProvider.java | 9 +- .../java/org/tarantool/TarantoolBase.java | 15 +- .../org/tarantool/TarantoolClientImpl.java | 44 ++++- .../org/tarantool/TarantoolClusterClient.java | 12 +- .../cluster/ClusterTopologyDiscoverer.java | 4 +- ...lusterTopologyFromShardDiscovererImpl.java | 16 +- .../tarantool/server/BinaryProtoUtils.java | 10 +- .../org/tarantool/server/TarantoolNode.java | 141 --------------- .../server/TarantoolNodeConnection.java | 59 +++++++ .../server/TarantoolNodeConnectionMeta.java | 20 +++ .../tarantool/server/TarantoolNodeInfo.java | 112 +++++++++++- .../org/tarantool/server/TarantoolServer.java | 166 ------------------ .../java/org/tarantool/ClientReconnectIT.java | 4 +- .../java/org/tarantool/TarantoolConsole.java | 2 +- .../tarantool/TestSocketChannelProvider.java | 2 +- ...erTopologyFromShardDiscovererImplTest.java | 10 +- ...deTest.java => TarantoolNodeInfoTest.java} | 6 +- src/test/perf/org/tarantool/MyBenchmark.java | 2 +- 21 files changed, 295 insertions(+), 412 deletions(-) delete mode 100644 src/main/java/org/tarantool/server/TarantoolNode.java create mode 100644 src/main/java/org/tarantool/server/TarantoolNodeConnection.java create mode 100644 src/main/java/org/tarantool/server/TarantoolNodeConnectionMeta.java delete mode 100644 src/main/java/org/tarantool/server/TarantoolServer.java rename src/test/java/org/tarantool/server/{TarantoolNodeTest.java => TarantoolNodeInfoTest.java} (79%) diff --git a/src/it/java/org/tarantool/TestTarantoolClient.java b/src/it/java/org/tarantool/TestTarantoolClient.java index a27e7403..a8a0e108 100644 --- a/src/it/java/org/tarantool/TestTarantoolClient.java +++ b/src/it/java/org/tarantool/TestTarantoolClient.java @@ -96,7 +96,7 @@ public static void main(String[] args) throws IOException, InterruptedException, //config.sharedBufferSize = 0; SocketChannelProvider socketChannelProvider = new SocketChannelProvider() { @Override - public SocketChannel get(int retryNumber, Throwable lastError) { + public SocketChannel getNext(int retryNumber, Throwable lastError) { if (lastError != null) { lastError.printStackTrace(System.out); } diff --git a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java index 0d009b5d..99627653 100644 --- a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java +++ b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java @@ -5,12 +5,7 @@ import java.io.*; import java.net.*; import java.nio.channels.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; /** * Basic reconnection strategy that changes addresses in a round-robin fashion. @@ -24,7 +19,7 @@ public class RoundRobinSocketProviderImpl implements SocketChannelProvider { /** Limit of retries. */ private int retriesLimit = -1; // No-limit. - private TarantoolNode[] nodes; + private TarantoolNodeInfo[] nodes; /** Current position within {@link #nodes} array. */ private int pos; @@ -44,23 +39,23 @@ public RoundRobinSocketProviderImpl(String[] slaveHosts) { private void updateNodes(String[] slaveHosts) { //todo add read-write lock - nodes = new TarantoolNode[slaveHosts.length]; + nodes = new TarantoolNodeInfo[slaveHosts.length]; for (int i = 0; i < slaveHosts.length; i++) { String slaveHostAddress = slaveHosts[i]; - nodes[i] = TarantoolNode.create(slaveHostAddress); + nodes[i] = TarantoolNodeInfo.create(slaveHostAddress); } pos = 0; } - public void updateNodes(List slaveHosts) { + public void updateNodes(List slaveHosts) { if (slaveHosts == null) { throw new IllegalArgumentException("slaveHosts can not be null"); } //todo add read-write lock - this.nodes = (TarantoolNode[]) slaveHosts.toArray(); + this.nodes = (TarantoolNodeInfo[]) slaveHosts.toArray(); pos = 0; } @@ -69,7 +64,7 @@ public void updateNodes(List slaveHosts) { /** * @return Non-empty list of round-robined nodes */ - public TarantoolNode[] getNodes() { + public TarantoolNodeInfo[] getNodes() { return nodes; } @@ -102,7 +97,7 @@ public int getTimeout() { /** * Sets maximum amount of reconnect attempts to be made before an exception is raised. - * The retry count is maintained by a {@link #get(int, Throwable)} caller + * The retry count is maintained by a {@link #getNext(int, Throwable)} caller * when a socket level connection was established. * * Negative value means unlimited. @@ -125,10 +120,7 @@ public int getRetriesLimit() { /** {@inheritDoc} */ @Override - public SocketChannel get(int retryNumber, Throwable lastError) { - if (areRetriesExhausted(retryNumber)) { - throw new CommunicationException("Connection retries exceeded.", lastError); - } + public SocketChannel getNext() { int attempts = getAddressCount(); long deadline = System.currentTimeMillis() + timeout * attempts; while (!Thread.currentThread().isInterrupted()) { @@ -181,20 +173,8 @@ protected InetSocketAddress getNextSocketAddress() { return res; } - protected TarantoolNode getCurrentNode() { + protected TarantoolNodeInfo getCurrentNode() { return nodes[pos]; } - /** - * Provides a decision on whether retries limit is hit. - * - * @param retries Current count of retries. - * @return {@code true} if retries are exhausted. - */ - private boolean areRetriesExhausted(int retries) { - int limit = getRetriesLimit(); - if (limit < 0) - return false; - return retries >= limit; - } } diff --git a/src/main/java/org/tarantool/SimpleSocketChannelProvider.java b/src/main/java/org/tarantool/SimpleSocketChannelProvider.java index eee3e0d2..2bc2c126 100644 --- a/src/main/java/org/tarantool/SimpleSocketChannelProvider.java +++ b/src/main/java/org/tarantool/SimpleSocketChannelProvider.java @@ -1,6 +1,6 @@ package org.tarantool; -import org.tarantool.server.TarantoolNode; +import org.tarantool.server.*; import java.io.IOException; import java.net.InetSocketAddress; @@ -8,22 +8,37 @@ public class SimpleSocketChannelProvider implements SocketChannelProvider{ - private final TarantoolNode tarantoolNode; + private final TarantoolNodeInfo tarantoolNodeInfo; - public SimpleSocketChannelProvider(InetSocketAddress socketAddress) { - this.tarantoolNode = TarantoolNode.create(socketAddress); + private TarantoolNodeConnection nodeConnection; + + public SimpleSocketChannelProvider(InetSocketAddress socketAddress, String username, String password) { + this.tarantoolNodeInfo = TarantoolNodeInfo.create(socketAddress, username, password); + } + + public SimpleSocketChannelProvider(String address, String username, String password) { + this.tarantoolNodeInfo = TarantoolNodeInfo.create(address, username, password); } - public SimpleSocketChannelProvider(String address) { - this.tarantoolNode = TarantoolNode.create(address); + @Override + public void connect() { + nodeConnection = TarantoolNodeConnection.connect(tarantoolNodeInfo); } @Override - public SocketChannel get(int retryNumber, Throwable lastError) { + public SocketChannel getNext() { try { - return SocketChannel.open(tarantoolNode.getSocketAddress()); + return SocketChannel.open(tarantoolNodeInfo.getSocketAddress()); } catch (IOException e) { - throw new CommunicationException("Exception occurred while connecting to node " + tarantoolNode, e); + throw new CommunicationException("Exception occurred while connecting to node " + tarantoolNodeInfo, e); + } + } + + @Override + public SocketChannel getChannel() { + if (nodeConnection == null) { + throw new IllegalStateException("Not initialized"); } + return null; } } diff --git a/src/main/java/org/tarantool/SocketChannelProvider.java b/src/main/java/org/tarantool/SocketChannelProvider.java index 09112dec..5d4700fe 100644 --- a/src/main/java/org/tarantool/SocketChannelProvider.java +++ b/src/main/java/org/tarantool/SocketChannelProvider.java @@ -4,12 +4,15 @@ import java.nio.channels.SocketChannel; public interface SocketChannelProvider { + + void connect(); + /** * Provides socket channel to init restore connection. * You could change hosts on fail and sleep between retries in this method - * @param retryNumber number of current retry. Reset after successful connect. - * @param lastError the last error occurs when reconnecting * @return the result of SocketChannel open(SocketAddress remote) call */ - SocketChannel get(int retryNumber, Throwable lastError); + SocketChannel getNext(); + + SocketChannel getChannel(); } diff --git a/src/main/java/org/tarantool/TarantoolBase.java b/src/main/java/org/tarantool/TarantoolBase.java index 91fb4048..e225c0d8 100644 --- a/src/main/java/org/tarantool/TarantoolBase.java +++ b/src/main/java/org/tarantool/TarantoolBase.java @@ -1,23 +1,21 @@ package org.tarantool; import org.tarantool.server.BinaryProtoUtils; -import org.tarantool.server.TarantoolNodeInfo; +import org.tarantool.server.TarantoolNodeConnectionMeta; import java.io.IOException; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.List; -import java.util.Map; import java.util.concurrent.atomic.AtomicLong; public abstract class TarantoolBase extends AbstractTarantoolOps, Object, Result> { - protected String serverVersion; /** * Connection state */ - protected String salt; + TarantoolNodeConnectionMeta currentNodeInfo; protected MsgPackLite msgPackLite = MsgPackLite.INSTANCE; protected AtomicLong syncId = new AtomicLong(); protected int initialRequestSize = 4096; @@ -28,9 +26,7 @@ public TarantoolBase() { public TarantoolBase(String username, String password, Socket socket) { super(); try { - TarantoolNodeInfo info = BinaryProtoUtils.connect(socket, username, password); - this.serverVersion = info.getServerVersion(); - this.salt = info.getSalt(); + this.currentNodeInfo = BinaryProtoUtils.connect(socket, username, password); } catch (CommunicationException e) { close(); throw e; @@ -97,6 +93,9 @@ public void setInitialRequestSize(int initialRequestSize) { } public String getServerVersion() { - return serverVersion; + if (currentNodeInfo == null) { + throw new IllegalStateException("Tarantool base is not initialized"); + } + return currentNodeInfo.getServerVersion(); } } diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index 8652fe14..f16aca2f 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -32,6 +32,11 @@ public class TarantoolClientImpl extends TarantoolBase> implements Tar * External */ protected SocketChannelProvider socketProvider; + + /** + * Max amount of one by one reconnect attempts + */ + protected int reconnectRetriesLimit = -1; // No-limit. protected volatile Exception thumbstone; protected Map> futures; @@ -75,7 +80,7 @@ public void run() { }); public TarantoolClientImpl(InetSocketAddress socketAddress, TarantoolClientConfig config) { - this(new SimpleSocketChannelProvider(socketAddress), config); + this(new SimpleSocketChannelProvider(socketAddress, config.username, config.password), config); } public TarantoolClientImpl(String address, TarantoolClientConfig config) { @@ -123,7 +128,11 @@ protected void reconnect(int retry, Throwable lastError) { SocketChannel channel; while (!Thread.currentThread().isInterrupted()) { try { - channel = socketProvider.get(retry++, lastError == NOT_INIT_EXCEPTION ? null : lastError); + if (areRetriesExhausted(retry)) { + Throwable cause = lastError == NOT_INIT_EXCEPTION ? null : lastError; + throw new CommunicationException("Connection retries exceeded.", cause); + } + channel = socketProvider.getNext(); } catch (Exception e) { close(e); return; @@ -140,11 +149,22 @@ protected void reconnect(int retry, Throwable lastError) { } } + /** + * Provides a decision on whether retries limit is hit. + * + * @param retries Current count of retries. + * @return {@code true} if retries are exhausted. + */ + private boolean areRetriesExhausted(int retries) { + int limit = reconnectRetriesLimit; + if (limit < 0) + return false; + return retries >= limit; + } + protected void connect(final SocketChannel channel) throws Exception { try { - TarantoolNodeInfo nodeInfo = BinaryProtoUtils.connect(channel, config.username, config.password); - this.salt = nodeInfo.getSalt(); - this.serverVersion = nodeInfo.getServerVersion(); + this.currentNodeInfo = BinaryProtoUtils.connect(channel, config.username, config.password); } catch (IOException e) { throw new CommunicationException("Couldn't connect to tarantool", e); } @@ -319,7 +339,7 @@ private boolean directWrite(ByteBuffer buffer) throws InterruptedException, IOEx if (rem > initialRequestSize) { stats.directPacketSizeGrowth++; } - BinaryProtoUtils.writeFully(channel, buffer); + BinaryProtoUtils.writeFully(getReadChannel(), buffer); stats.directWrite++; wait.incrementAndGet(); } finally { @@ -334,11 +354,19 @@ private boolean directWrite(ByteBuffer buffer) throws InterruptedException, IOEx return false; } + private SocketChannel getWriteChannel() { + return channel; + } + + private SocketChannel getReadChannel() { + return channel; + } + protected void readThread() { try { while (!Thread.currentThread().isInterrupted()) { try { - TarantoolBinaryPackage pack = BinaryProtoUtils.readPacket(channel); + TarantoolBinaryPackage pack = BinaryProtoUtils.readPacket(getReadChannel()); Map headers = pack.getHeaders(); @@ -377,7 +405,7 @@ protected void writeThread() { writerBuffer.flip(); writeLock.lock(); try { - BinaryProtoUtils.writeFully(channel, writerBuffer); + BinaryProtoUtils.writeFully(getWriteChannel(), writerBuffer); } finally { writeLock.unlock(); } diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index 985ab07d..0fbf6108 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -26,9 +26,9 @@ public class TarantoolClusterClient extends TarantoolClientImpl { /* Collection of operations to be retried. */ private ConcurrentHashMap> retries = new ConcurrentHashMap>(); - private final Collection slaveHosts; + private final Collection slaveHosts; - private final TarantoolNode infoHost; + private final TarantoolNodeInfo infoHost; private final Integer infoHostConnectionTimeout; private final ClusterTopologyDiscoverer topologyDiscoverer; /** @@ -48,7 +48,7 @@ public TarantoolClusterClient(TarantoolClusterClientConfig config, SocketChannel this.executor = config.executor == null ? Executors.newSingleThreadExecutor() : config.executor; - this.infoHost = TarantoolNode.create(config.infoHost); + this.infoHost = TarantoolNodeInfo.create(config.infoHost); this.infoHostConnectionTimeout = config.infoHostConnectionTimeout; this.topologyDiscoverer = new ClusterTopologyFromShardDiscovererImpl(config); @@ -61,8 +61,8 @@ public TarantoolClusterClient(TarantoolClusterClientConfig config, SocketChannel * @throws CommunicationException in case of communication with {@code infoNode} exception * @throws IllegalArgumentException in case when the info node returned invalid address */ - private Collection refreshServerList(TarantoolNode infoNode) { - List newServerList = topologyDiscoverer + private Collection refreshServerList(TarantoolNodeInfo infoNode) { + List newServerList = topologyDiscoverer .discoverTarantoolNodes(infoNode, infoHostConnectionTimeout); writeLock.lock(); @@ -71,7 +71,7 @@ private Collection refreshServerList(TarantoolNode infoNode) { RoundRobinSocketProviderImpl rSocketProvider = (RoundRobinSocketProviderImpl) this.socketProvider; - TarantoolNode currentNode = rSocketProvider.getCurrentNode(); + TarantoolNodeInfo currentNode = rSocketProvider.getCurrentNode(); int sameNodeIndex = newServerList.indexOf(currentNode); if (sameNodeIndex != -1) { diff --git a/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java b/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java index 460694cd..330f9e63 100644 --- a/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java +++ b/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java @@ -1,9 +1,9 @@ package org.tarantool.cluster; -import org.tarantool.server.TarantoolNode; +import org.tarantool.server.TarantoolNodeInfo; import java.util.List; public interface ClusterTopologyDiscoverer { - List discoverTarantoolNodes(TarantoolNode infoNode, Integer infoHostConnectionTimeout); + List discoverTarantoolNodes(TarantoolNodeInfo infoNode, Integer infoHostConnectionTimeout); } diff --git a/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java b/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java index ad70bc8c..5906fc7a 100644 --- a/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java +++ b/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java @@ -1,16 +1,10 @@ package org.tarantool.cluster; -import org.tarantool.CommunicationException; -import org.tarantool.TarantoolClientConfig; import org.tarantool.TarantoolClientImpl; -import org.tarantool.TarantoolClusterClient; import org.tarantool.TarantoolClusterClientConfig; -import org.tarantool.server.BinaryProtoUtils; -import org.tarantool.server.TarantoolNode; +import org.tarantool.server.TarantoolNodeInfo; -import java.io.IOException; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Map; @@ -25,8 +19,8 @@ public ClusterTopologyFromShardDiscovererImpl(TarantoolClusterClientConfig clien } @Override - public List discoverTarantoolNodes(TarantoolNode infoNode, - Integer infoHostConnectionTimeout) { + public List discoverTarantoolNodes(TarantoolNodeInfo infoNode, + Integer infoHostConnectionTimeout) { List list = new TarantoolClientImpl(infoNode.getSocketAddress(), clientConfig) .syncOps() @@ -36,7 +30,7 @@ public List discoverTarantoolNodes(TarantoolNode infoNode, Map shardHash2DescriptionMap = (Map) getValue(funcResult, "sharding"); - List result = new ArrayList<>(); + List result = new ArrayList<>(); for (Object shardHash2Description : shardHash2DescriptionMap.entrySet()) { @@ -45,7 +39,7 @@ public List discoverTarantoolNodes(TarantoolNode infoNode, for (Object replica : replicas.entrySet()) { Object replicaUri = getValue(((Map.Entry) replica).getValue(), "uri"); - result.add(TarantoolNode.create(parseReplicaUri(replicaUri.toString()))); + result.add(TarantoolNodeInfo.create(parseReplicaUri(replicaUri.toString()))); } } diff --git a/src/main/java/org/tarantool/server/BinaryProtoUtils.java b/src/main/java/org/tarantool/server/BinaryProtoUtils.java index 32bea42d..c9e8e9fd 100644 --- a/src/main/java/org/tarantool/server/BinaryProtoUtils.java +++ b/src/main/java/org/tarantool/server/BinaryProtoUtils.java @@ -53,7 +53,7 @@ public static TarantoolBinaryPackage readPacket(InputStream inputStream) throws * @throws CommunicationException when welcome string is invalid * @throws TarantoolException in case of failed authentication */ - public static TarantoolNodeInfo connect(Socket socket, String username, String password) throws IOException { + public static TarantoolNodeConnectionMeta connect(Socket socket, String username, String password) throws IOException { byte[] inputBytes = new byte[64]; InputStream inputStream = socket.getInputStream(); @@ -85,7 +85,7 @@ public static TarantoolNodeInfo connect(Socket socket, String username, String p } } - return new TarantoolNodeInfo(salt, serverVersion); + return new TarantoolNodeConnectionMeta(salt, serverVersion); } /** @@ -99,7 +99,7 @@ public static TarantoolNodeInfo connect(Socket socket, String username, String p * @throws CommunicationException when welcome string is invalid * @throws TarantoolException in case of failed authentication */ - public static TarantoolNodeInfo connect(SocketChannel channel, String username, String password) throws IOException { + public static TarantoolNodeConnectionMeta connect(SocketChannel channel, String username, String password) throws IOException { ByteBuffer welcomeBytes = ByteBuffer.wrap(new byte[64]); channel.read(welcomeBytes); @@ -125,7 +125,7 @@ public static TarantoolNodeInfo connect(SocketChannel channel, String username, } } - return new TarantoolNodeInfo(salt, serverVersion); + return new TarantoolNodeConnectionMeta(salt, serverVersion); } public static void writeFully(OutputStream stream, ByteBuffer buffer) throws IOException { @@ -146,7 +146,7 @@ public static void writeFully(SocketChannel channel, ByteBuffer buffer) throws I public static TarantoolBinaryPackage readPacket(SocketChannel channel) throws IOException { channel.configureBlocking(false); - //todo get rid of this because SelectorProvider.provider().openSelector() creates two pipes and socket in the /proc/fd + //todo getNext rid of this because SelectorProvider.provider().openSelector() creates two pipes and socket in the /proc/fd SelectorChannelReadHelper bufferReader = new SelectorChannelReadHelper(channel); ByteBuffer buffer = ByteBuffer.allocate(LENGTH_OF_SIZE_MESSAGE); diff --git a/src/main/java/org/tarantool/server/TarantoolNode.java b/src/main/java/org/tarantool/server/TarantoolNode.java deleted file mode 100644 index 2e96b2ed..00000000 --- a/src/main/java/org/tarantool/server/TarantoolNode.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.tarantool.server; - -import org.tarantool.CommunicationException; - -import java.io.IOException; -import java.net.*; -import java.nio.channels.SocketChannel; -import java.util.Objects; - -/** - * Holds info about a tarantool node. - */ -public class TarantoolNode { - - private final InetSocketAddress socketAddress; - - private TarantoolNode(InetSocketAddress socketAddress) { - this.socketAddress = socketAddress; - } - - /** - * @return A socket address that the client can be connected to - */ - public InetSocketAddress getSocketAddress() { - return socketAddress; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - TarantoolNode node = (TarantoolNode) o; - return socketAddress.equals(node.socketAddress); - } - - @Override - public int hashCode() { - return Objects.hash(socketAddress); - } - - @Deprecated - private SocketChannel socketChannel; - /** - * Opens a socket connection channel to the tarantool node or returns an opened one - * @return - */ - @Deprecated //it's bad to open socket at level of this class - public SocketChannel getSocketChannel(Integer timeout) { - if (socketChannel == null) { - socketChannel = openSocketChannel(timeout); - } - - return socketChannel; - } - - @Deprecated //it's bad to open socket at level of this class - private SocketChannel openSocketChannel(Integer timeout) { - SocketChannel result = null; - try { - result = SocketChannel.open(); - - if (timeout != null) { - result.socket().connect(socketAddress, timeout); - } else { - result.connect(socketAddress); - } - return result; - } catch (Exception e) { - if (result != null) { - try { - result.close(); - } catch (IOException ignored) { - } - } - throw new CommunicationException("Failed to connect to node " + this.toString(), e); - } - } - - /** - * - * @param socketAddress Nonnull socket address - * @return Instance of {@link TarantoolNode} - */ - public static TarantoolNode create(InetSocketAddress socketAddress) { - if (socketAddress == null) { - throw new IllegalArgumentException("A socket address can not be null."); - } - - return new TarantoolNode(socketAddress); - } - - - /** - * @param address hostname address as String - * - * @throws IllegalArgumentException if the port parameter is outside the range - * of valid port values, or if the hostname parameter is null. - * @throws SecurityException if a security manager is present and - * permission to resolve the host name is - * denied. - */ - public static TarantoolNode create(String address) { - if (address == null) { - throw new IllegalArgumentException("A hostname address can not be null."); - } - - return new TarantoolNode(parseAddress(address)); - } - - /** - * Parse a string address in the form of [host]:[port] - * and builds a socket address. - * - * @param addr Server address as string. - * @throws IllegalArgumentException if the port parameter is outside the range - * of valid port values, or if the hostname parameter is null. - * @throws SecurityException if a security manager is present and - * permission to resolve the host name is - * denied. - */ - private static InetSocketAddress parseAddress(String addr) { - int idx = addr.indexOf(':'); - String host = (idx < 0) ? addr : addr.substring(0, idx); - - int port; - try { - port = (idx < 0) ? 3301 : Integer.parseInt(addr.substring(idx + 1)); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Exception while parsing port in address '" + addr + "'", e); - } - - return new InetSocketAddress(host, port); - } - - @Override - public String toString() { - return "TarantoolNode{" + - "socketAddress=" + socketAddress.getHostString() + ":" + socketAddress.getPort() + - '}'; - } -} diff --git a/src/main/java/org/tarantool/server/TarantoolNodeConnection.java b/src/main/java/org/tarantool/server/TarantoolNodeConnection.java new file mode 100644 index 00000000..a2600cc5 --- /dev/null +++ b/src/main/java/org/tarantool/server/TarantoolNodeConnection.java @@ -0,0 +1,59 @@ +package org.tarantool.server; + +import org.tarantool.ByteBufferInputStream; +import org.tarantool.CommunicationException; + +import java.io.DataInputStream; +import java.io.IOException; +import java.net.*; +import java.nio.channels.SocketChannel; + +@Deprecated +public class TarantoolNodeConnection { + + /** + * External + */ + private final TarantoolNodeInfo nodeInfo; + private final TarantoolNodeConnectionMeta meta; + + protected final SocketChannel channel; + + private TarantoolNodeConnection(TarantoolNodeInfo nodeInfo, TarantoolNodeConnectionMeta meta, SocketChannel channel) { + this.nodeInfo = nodeInfo; + this.meta = meta; + this.channel = channel; + } + + /** + * + * + * @param tarantoolNodeInfo + * @throws CommunicationException + */ + public static TarantoolNodeConnection connect(TarantoolNodeInfo tarantoolNodeInfo) { + SocketChannel channel; + try { + channel = SocketChannel.open(tarantoolNodeInfo.getSocketAddress()); + String username = tarantoolNodeInfo.getUsername(); + String password = tarantoolNodeInfo.getPassword(); + + TarantoolNodeConnectionMeta meta = BinaryProtoUtils.connect(channel, username, password); + + return new TarantoolNodeConnection(tarantoolNodeInfo, meta, channel); + } catch (IOException e) { + throw new CommunicationException("Exception occurred while connecting to node " + tarantoolNodeInfo, e); + } + } + + @Deprecated + public void closeConnection() { + if (channel != null) { + try { + channel.close(); + } catch (IOException ignored) { + + } + } + } +} diff --git a/src/main/java/org/tarantool/server/TarantoolNodeConnectionMeta.java b/src/main/java/org/tarantool/server/TarantoolNodeConnectionMeta.java new file mode 100644 index 00000000..40102ce6 --- /dev/null +++ b/src/main/java/org/tarantool/server/TarantoolNodeConnectionMeta.java @@ -0,0 +1,20 @@ +package org.tarantool.server; + +public class TarantoolNodeConnectionMeta { + + private final String salt; + private final String serverVersion; + + public TarantoolNodeConnectionMeta(String salt, String serverVersion) { + this.salt = salt; + this.serverVersion = serverVersion; + } + + public String getSalt() { + return salt; + } + + public String getServerVersion() { + return serverVersion; + } +} diff --git a/src/main/java/org/tarantool/server/TarantoolNodeInfo.java b/src/main/java/org/tarantool/server/TarantoolNodeInfo.java index 7e39a488..5bdc7765 100644 --- a/src/main/java/org/tarantool/server/TarantoolNodeInfo.java +++ b/src/main/java/org/tarantool/server/TarantoolNodeInfo.java @@ -1,20 +1,114 @@ package org.tarantool.server; +import java.net.*; +import java.nio.channels.*; +import java.util.*; + +/** + * Holds info about a tarantool node. + */ public class TarantoolNodeInfo { - private final String salt; - private final String serverVersion; + private final InetSocketAddress socketAddress; + private final String username; + private final String password; + + private TarantoolNodeInfo(InetSocketAddress socketAddress, String username, String password) { + this.socketAddress = socketAddress; + this.username = username; + this.password = password; + } + + /** + * @return A socket address that the client can be connected to + */ + public InetSocketAddress getSocketAddress() { + return socketAddress; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TarantoolNodeInfo node = (TarantoolNodeInfo) o; + return socketAddress.equals(node.socketAddress); + } + + @Override + public int hashCode() { + return Objects.hash(socketAddress); + } + + /** + * + * @param socketAddress Nonnull socket address + * @return Instance of {@link TarantoolNodeInfo} + */ + public static TarantoolNodeInfo create(InetSocketAddress socketAddress, String username, String password) { + if (socketAddress == null) { + throw new IllegalArgumentException("A socket address can not be null."); + } + + return new TarantoolNodeInfo(socketAddress, username, password); + } + + + /** + * @param address hostname address as String + * + * @throws IllegalArgumentException if the port parameter is outside the range + * of valid port values, or if the hostname parameter is null. + * @throws SecurityException if a security manager is present and + * permission to resolve the host name is + * denied. + */ + public static TarantoolNodeInfo create(String address, String username, String password) { + if (address == null) { + throw new IllegalArgumentException("A hostname address can not be null."); + } - public TarantoolNodeInfo(String salt, String serverVersion) { - this.salt = salt; - this.serverVersion = serverVersion; + return new TarantoolNodeInfo(parseAddress(address), username, password); } - public String getSalt() { - return salt; + /** + * Parse a string address in the form of [host]:[port] + * and builds a socket address. + * + * @param addr Server address as string. + * @throws IllegalArgumentException if the port parameter is outside the range + * of valid port values, or if the hostname parameter is null. + * @throws SecurityException if a security manager is present and + * permission to resolve the host name is + * denied. + */ + private static InetSocketAddress parseAddress(String addr) { + int idx = addr.indexOf(':'); + String host = (idx < 0) ? addr : addr.substring(0, idx); + + int port; + try { + port = (idx < 0) ? 3301 : Integer.parseInt(addr.substring(idx + 1)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Exception while parsing port in address '" + addr + "'", e); + } + + return new InetSocketAddress(host, port); } - public String getServerVersion() { - return serverVersion; + @Override + public String toString() { + return "TarantoolNodeInfo{" + + "socketAddress=" + socketAddress + + ", username='" + username + '\'' + + ", password='" + password + '\'' + + '}'; } } diff --git a/src/main/java/org/tarantool/server/TarantoolServer.java b/src/main/java/org/tarantool/server/TarantoolServer.java deleted file mode 100644 index cba6069f..00000000 --- a/src/main/java/org/tarantool/server/TarantoolServer.java +++ /dev/null @@ -1,166 +0,0 @@ -package org.tarantool.server; - -import org.tarantool.ByteBufferInputStream; -import org.tarantool.CommunicationException; -import org.tarantool.Key; -import org.tarantool.MsgPackLite; -import org.tarantool.SocketChannelProvider; -import org.tarantool.TarantoolClientConfig; -import org.tarantool.TarantoolException; - -import java.io.DataInputStream; -import java.io.IOException; -import java.net.SocketException; -import java.nio.ByteBuffer; -import java.nio.channels.SocketChannel; -import java.util.Map; - -public class TarantoolServer { - - /** - * External - */ - private SocketChannelProvider socketProvider; - private TarantoolClientConfig config; - - private CountingInputStream dis; - protected SocketChannel channel; - - private MsgPackLite msgPackLite = MsgPackLite.INSTANCE; - - public static void connect(TarantoolNode node, String username, String password) { - SocketChannel channel; - try { - channel = SocketChannel.open(node.getSocketAddress()); - } catch (IOException e) { - throw new CommunicationException("Exception occurred while connecting to node " + node, e); - } -//todo -// TarantoolNodeInfo nodeInfo = BinaryProtoUtils.connect(channel, username, password); -// this.salt = nodeInfo.getSalt(); -// this.serverVersion = nodeInfo.getServerVersion(); - } - - /** - * Connection state - */ - protected String salt; - - private TarantoolServer(SocketChannelProvider socketProvider, TarantoolClientConfig config) { - this.socketProvider = socketProvider; - - this.config = config; - } - - public void init() throws Exception { - connect(); - } - - protected void connect() throws Exception { - connect(socketProvider.get(0, null)); - } - - public static final class CountingInputStream extends DataInputStream { - - private final ByteBufferInputStream in; - - public CountingInputStream(ByteBufferInputStream byteBufferInputStream) { - super(byteBufferInputStream); - this.in = byteBufferInputStream; - } - - private long getBytesRead() { - return in.getBytesRead(); - } - } - - protected void connect(final SocketChannel channel) throws Exception { - ByteBufferInputStream bufferInputStream = null; - try { - ; - CountingInputStream dis = new CountingInputStream((bufferInputStream = new ByteBufferInputStream(channel))); - byte[] bytes = new byte[64]; - dis.readFully(bytes); - String firstLine = new String(bytes); - if (!firstLine.startsWith("Tarantool")) { - CommunicationException e = new CommunicationException("Welcome message should starts with tarantool " + - "but starts with '" + firstLine + "'", new IllegalStateException("Invalid welcome packet")); - - throw e; - } - dis.readFully(bytes); - String salt = new String(bytes); - if (config.username != null && config.password != null) { - writeFully(channel, BinaryProtoUtils.createAuthPacket(config.username, config.password, salt)); - TarantoolBinaryPackage biPack = readPacket(); - Long code = (Long) biPack.getHeaders().get(Key.CODE.getId()); - if (code != 0) { - throw serverError(code, biPack.getBody().get(Key.ERROR.getId())); - } - } - - this.dis = dis; - } catch (IOException e) { - try { - if (bufferInputStream != null) { - bufferInputStream.close(); - } - } catch (IOException ignored) { - - } - throw new CommunicationException("Couldn't connect to tarantool", e); - } - channel.configureBlocking(false); - } - - protected TarantoolException serverError(long code, Object error) { - return new TarantoolException(code, error instanceof String ? (String) error : new String((byte[]) error)); - } - - public TarantoolBinaryPackage readPacket() throws IOException { - return readPacket(dis); - } - - @Deprecated // use org.tarantool.server.BinaryProtoUtils.readPacket - private TarantoolBinaryPackage readPacket(CountingInputStream countingInputStream) throws IOException { - int size = ((Number) getMsgPackLite().unpack(countingInputStream)).intValue(); - - long mark = countingInputStream.getBytesRead(); - Map headers = (Map) getMsgPackLite().unpack(countingInputStream); - - Map body = null; - if (countingInputStream.getBytesRead() - mark < size) { - body = (Map) getMsgPackLite().unpack(countingInputStream); - } - countingInputStream.skipBytes((int) (countingInputStream.getBytesRead() - mark - size)); - - return new TarantoolBinaryPackage(headers, body); - } - - private static MsgPackLite getMsgPackLite() { - return MsgPackLite.INSTANCE; - } - - public void writeFully(ByteBuffer buffer) throws IOException { - writeFully(channel, buffer); - } - - private void writeFully(SocketChannel channel, ByteBuffer buffer) throws IOException { - long code = 0; - while (buffer.remaining() > 0 && (code = channel.write(buffer)) > -1) { - } - if (code < 0) { - throw new SocketException("write failed code: " + code); - } - } - - public void closeConnection() { - if (channel != null) { - try { - channel.close(); - } catch (IOException ignored) { - - } - } - } -} diff --git a/src/test/java/org/tarantool/ClientReconnectIT.java b/src/test/java/org/tarantool/ClientReconnectIT.java index 2472bf05..3edde950 100644 --- a/src/test/java/org/tarantool/ClientReconnectIT.java +++ b/src/test/java/org/tarantool/ClientReconnectIT.java @@ -87,11 +87,11 @@ public void testSpuriousReturnFromPark() { final CountDownLatch latch = new CountDownLatch(2); SocketChannelProvider provider = new SocketChannelProvider() { @Override - public SocketChannel get(int retryNumber, Throwable lastError) { + public SocketChannel getNext(int retryNumber, Throwable lastError) { if (lastError == null) { latch.countDown(); } - return socketChannelProvider.get(retryNumber, lastError); + return socketChannelProvider.getNext(retryNumber, lastError); } }; diff --git a/src/test/java/org/tarantool/TarantoolConsole.java b/src/test/java/org/tarantool/TarantoolConsole.java index f664f86b..94f76f16 100644 --- a/src/test/java/org/tarantool/TarantoolConsole.java +++ b/src/test/java/org/tarantool/TarantoolConsole.java @@ -168,7 +168,7 @@ private static class TarantoolTcpConsole extends TarantoolConsole { final Socket socket; TarantoolTcpConsole(String host, int port) { - socket = new TestSocketChannelProvider(host, port, TIMEOUT).get(1, null).socket(); + socket = new TestSocketChannelProvider(host, port, TIMEOUT).getNext(1, null).socket(); try { reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); writer = new OutputStreamWriter(socket.getOutputStream()); diff --git a/src/test/java/org/tarantool/TestSocketChannelProvider.java b/src/test/java/org/tarantool/TestSocketChannelProvider.java index 469bc77c..c724e824 100644 --- a/src/test/java/org/tarantool/TestSocketChannelProvider.java +++ b/src/test/java/org/tarantool/TestSocketChannelProvider.java @@ -18,7 +18,7 @@ public TestSocketChannelProvider(String host, int port, int restart_timeout) { } @Override - public SocketChannel get(int retryNumber, Throwable lastError) { + public SocketChannel getNext(int retryNumber, Throwable lastError) { long budget = System.currentTimeMillis() + restart_timeout; while (!Thread.currentThread().isInterrupted()) { try { diff --git a/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java b/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java index ecf5283a..8f3060b0 100644 --- a/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java +++ b/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java @@ -3,24 +3,22 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.tarantool.TarantoolClusterClientConfig; -import org.tarantool.server.TarantoolNode; - -import java.util.Collection; +import org.tarantool.server.TarantoolNodeInfo; class ClusterTopologyFromShardDiscovererImplTest { @DisplayName("Test that a list which describes the topology is fetched correctly") @Test void testListFetching() { - TarantoolNode tarantoolNode = TarantoolNode.create("localhost:3301"); + TarantoolNodeInfo tarantoolNodeInfo = TarantoolNodeInfo.create("localhost:3301"); TarantoolClusterClientConfig clientConfig = new TarantoolClusterClientConfig(); clientConfig.username = "storage"; clientConfig.password = "storage"; -// Collection tarantoolNodes = +// Collection tarantoolNodes = // new ClusterTopologyFromShardDiscovererImpl(clientConfig) -// .discoverTarantoolNodes(tarantoolNode, 5000); +// .discoverTarantoolNodes(tarantoolNodeInfo, 5000); int i = 0; } } \ No newline at end of file diff --git a/src/test/java/org/tarantool/server/TarantoolNodeTest.java b/src/test/java/org/tarantool/server/TarantoolNodeInfoTest.java similarity index 79% rename from src/test/java/org/tarantool/server/TarantoolNodeTest.java rename to src/test/java/org/tarantool/server/TarantoolNodeInfoTest.java index 2ee988a0..147ea5a5 100644 --- a/src/test/java/org/tarantool/server/TarantoolNodeTest.java +++ b/src/test/java/org/tarantool/server/TarantoolNodeInfoTest.java @@ -6,9 +6,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; -class TarantoolNodeTest { +class TarantoolNodeInfoTest { - @DisplayName("Test that a TarantoolNode throws an illegal argument exception" + + @DisplayName("Test that a TarantoolNodeInfo throws an illegal argument exception" + "in case when it's being created with wrong address string") @ParameterizedTest @ValueSource(strings = { @@ -17,7 +17,7 @@ class TarantoolNodeTest { }) void testThrowsExceptionInCaseOfInvalidStringAddress(String address) { assertThrows(IllegalArgumentException.class, - () -> TarantoolNode.create(address), + () -> TarantoolNodeInfo.create(address), "We expect the code under test to throw an IllegalArgumentException, but it didn't"); } } \ No newline at end of file diff --git a/src/test/perf/org/tarantool/MyBenchmark.java b/src/test/perf/org/tarantool/MyBenchmark.java index 16a12a7c..89d3428d 100644 --- a/src/test/perf/org/tarantool/MyBenchmark.java +++ b/src/test/perf/org/tarantool/MyBenchmark.java @@ -17,7 +17,7 @@ private DodgeSocketChannelProvider(Integer defaultSocketQueueSize) { } @Override - public SocketChannel get(int retryNumber, Throwable lastError) { + public SocketChannel getNext(int retryNumber, Throwable lastError) { return new DodgeSocketChannel(defaultSocketQueueSize); } } From 4918ba43cf5f93dd1b297cf0fce4203755611077 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Tue, 26 Feb 2019 02:11:30 +0300 Subject: [PATCH 14/30] Works onRoundRobinSocketChannelProvider; Some refactors --- .../RoundRobinSocketChannelProvider.java | 190 ++++++++++++++++++ .../RoundRobinSocketProviderImpl.java | 14 +- .../SimpleSocketChannelProvider.java | 31 ++- .../org/tarantool/SocketChannelProvider.java | 10 +- .../java/org/tarantool/TarantoolBase.java | 4 +- .../org/tarantool/TarantoolClientImpl.java | 10 +- .../org/tarantool/TarantoolClusterClient.java | 12 +- .../cluster/ClusterTopologyDiscoverer.java | 4 +- ...lusterTopologyFromShardDiscovererImpl.java | 10 +- .../tarantool/server/BinaryProtoUtils.java | 8 +- .../server/TarantoolInstanceConnection.java | 81 ++++++++ ...a => TarantoolInstanceConnectionMeta.java} | 4 +- ...deInfo.java => TarantoolInstanceInfo.java} | 21 +- .../server/TarantoolNodeConnection.java | 59 ------ ...erTopologyFromShardDiscovererImplTest.java | 10 +- ...st.java => TarantoolInstanceInfoTest.java} | 6 +- 16 files changed, 355 insertions(+), 119 deletions(-) create mode 100644 src/main/java/org/tarantool/RoundRobinSocketChannelProvider.java create mode 100644 src/main/java/org/tarantool/server/TarantoolInstanceConnection.java rename src/main/java/org/tarantool/server/{TarantoolNodeConnectionMeta.java => TarantoolInstanceConnectionMeta.java} (71%) rename src/main/java/org/tarantool/server/{TarantoolNodeInfo.java => TarantoolInstanceInfo.java} (81%) delete mode 100644 src/main/java/org/tarantool/server/TarantoolNodeConnection.java rename src/test/java/org/tarantool/server/{TarantoolNodeInfoTest.java => TarantoolInstanceInfoTest.java} (75%) diff --git a/src/main/java/org/tarantool/RoundRobinSocketChannelProvider.java b/src/main/java/org/tarantool/RoundRobinSocketChannelProvider.java new file mode 100644 index 00000000..507dfd6b --- /dev/null +++ b/src/main/java/org/tarantool/RoundRobinSocketChannelProvider.java @@ -0,0 +1,190 @@ +package org.tarantool; + +import org.tarantool.server.BinaryProtoUtils; +import org.tarantool.server.TarantoolBinaryPackage; +import org.tarantool.server.TarantoolInstanceConnection; +import org.tarantool.server.TarantoolInstanceInfo; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.util.List; + +public class RoundRobinSocketChannelProvider implements SocketChannelProvider { + + /** Timeout to establish socket connection with an individual server. */ + private int timeout; // 0 is infinite. + + /** Limit of retries. */ + private int retriesLimit = -1; // No-limit. + + + private TarantoolInstanceInfo[] nodes; + private TarantoolInstanceInfo currentNode; + + private int pos = 0; + + public RoundRobinSocketChannelProvider(String[] slaveHosts, String username, String password) { + if (slaveHosts == null || slaveHosts.length < 1) { + throw new IllegalArgumentException("slave hosts is null ot empty"); + } + + updateNodes(slaveHosts, username, password); + } + + private void updateNodes(String[] slaveHosts, String username, String password) { + //todo add read-write lock + nodes = new TarantoolInstanceInfo[slaveHosts.length]; + for (int i = 0; i < slaveHosts.length; i++) { + String slaveHostAddress = slaveHosts[i]; + nodes[i] = TarantoolInstanceInfo.create(slaveHostAddress, username, password); + } + + pos = 0; + } + + public void updateNodes(List slaveHosts) { + if (slaveHosts == null) { + throw new IllegalArgumentException("slaveHosts can not be null"); + } + //todo add read-write lock + + this.nodes = (TarantoolInstanceInfo[]) slaveHosts.toArray(); + + pos = 0; + } + + + + /** + * @return Non-empty list of round-robined nodes + */ + public TarantoolInstanceInfo[] getNodes() { + return nodes; + } + + /** {@inheritDoc} */ + public TarantoolInstanceConnection connectNextNode() { + int attempts = getAddressCount(); + long deadline = System.currentTimeMillis() + timeout * attempts; + while (!Thread.currentThread().isInterrupted()) { + TarantoolInstanceConnection connection = null; + try { + TarantoolInstanceInfo tarantoolInstanceInfo = getNextNode(); + connection = TarantoolInstanceConnection.connect(tarantoolInstanceInfo); + return connection; + } catch (IOException e) { + if (connection != null) { + try { + connection.close(); + } catch (IOException ignored) { + // No-op. + } + } + long now = System.currentTimeMillis(); + if (deadline <= now) { + throw new CommunicationException("Connection time out.", e); + } + if (--attempts == 0) { + // Tried all addresses without any lack, but still have time. + attempts = getAddressCount(); + try { + Thread.sleep((deadline - now) / attempts); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + } + } + } + throw new CommunicationException("Thread interrupted.", new InterruptedException()); + } + + /** {@inheritDoc} */ + @Override + public SocketChannel getNext() { + int attempts = getAddressCount(); + long deadline = System.currentTimeMillis() + timeout * attempts; + while (!Thread.currentThread().isInterrupted()) { + SocketChannel channel = null; + try { + TarantoolInstanceInfo tarantoolInstanceInfo = getNextNode(); + + + TarantoolInstanceConnection nodeConnection = TarantoolInstanceConnection.connect(tarantoolInstanceInfo); + + channel = nodeConnection.getChannel(); + + return channel; + } catch (IOException e) { + if (channel != null) { + try { + channel.close(); + } catch (IOException ignored) { + // No-op. + } + } + long now = System.currentTimeMillis(); + if (deadline <= now) { + throw new CommunicationException("Connection time out.", e); + } + if (--attempts == 0) { + // Tried all addresses without any lack, but still have time. + attempts = getAddressCount(); + try { + Thread.sleep((deadline - now) / attempts); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + } + } + } + throw new CommunicationException("Thread interrupted.", new InterruptedException()); + } + + /** + * @return Socket address to use for the next reconnection attempt. + */ + protected TarantoolInstanceInfo getNextNode() { + TarantoolInstanceInfo res = nodes[pos]; + pos = (pos + 1) % nodes.length; + return res; + } + + + /** + * @return Number of configured addresses. + */ + protected int getAddressCount() { + return nodes.length; + } + + + private final TarantoolInstanceInfo tarantoolInstanceInfo; + + private TarantoolInstanceConnection nodeConnection; + + @Override + public void connect() { + nodeConnection = TarantoolInstanceConnection.connect(tarantoolInstanceInfo); + } + + + public void writeBuffer(ByteBuffer byteBuffer) throws IOException { + SocketChannel channel2Write = getChannel(); + BinaryProtoUtils.writeFully(channel2Write, byteBuffer); + } + + public TarantoolBinaryPackage readPackage() throws IOException { + SocketChannel channel2Read = getChannel(); + return BinaryProtoUtils.readPacket(channel2Read); + } + + @Override + public SocketChannel getChannel() { + if (nodeConnection == null) { + throw new IllegalStateException("Not initialized"); + } + return nodeConnection.getChannel(); + } +} diff --git a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java index 99627653..986825ef 100644 --- a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java +++ b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java @@ -19,7 +19,7 @@ public class RoundRobinSocketProviderImpl implements SocketChannelProvider { /** Limit of retries. */ private int retriesLimit = -1; // No-limit. - private TarantoolNodeInfo[] nodes; + private TarantoolInstanceInfo[] nodes; /** Current position within {@link #nodes} array. */ private int pos; @@ -39,23 +39,23 @@ public RoundRobinSocketProviderImpl(String[] slaveHosts) { private void updateNodes(String[] slaveHosts) { //todo add read-write lock - nodes = new TarantoolNodeInfo[slaveHosts.length]; + nodes = new TarantoolInstanceInfo[slaveHosts.length]; for (int i = 0; i < slaveHosts.length; i++) { String slaveHostAddress = slaveHosts[i]; - nodes[i] = TarantoolNodeInfo.create(slaveHostAddress); + nodes[i] = TarantoolInstanceInfo.create(slaveHostAddress); } pos = 0; } - public void updateNodes(List slaveHosts) { + public void updateNodes(List slaveHosts) { if (slaveHosts == null) { throw new IllegalArgumentException("slaveHosts can not be null"); } //todo add read-write lock - this.nodes = (TarantoolNodeInfo[]) slaveHosts.toArray(); + this.nodes = (TarantoolInstanceInfo[]) slaveHosts.toArray(); pos = 0; } @@ -64,7 +64,7 @@ public void updateNodes(List slaveHosts) { /** * @return Non-empty list of round-robined nodes */ - public TarantoolNodeInfo[] getNodes() { + public TarantoolInstanceInfo[] getNodes() { return nodes; } @@ -173,7 +173,7 @@ protected InetSocketAddress getNextSocketAddress() { return res; } - protected TarantoolNodeInfo getCurrentNode() { + protected TarantoolInstanceInfo getCurrentNode() { return nodes[pos]; } diff --git a/src/main/java/org/tarantool/SimpleSocketChannelProvider.java b/src/main/java/org/tarantool/SimpleSocketChannelProvider.java index 2bc2c126..38ac5ff6 100644 --- a/src/main/java/org/tarantool/SimpleSocketChannelProvider.java +++ b/src/main/java/org/tarantool/SimpleSocketChannelProvider.java @@ -4,41 +4,52 @@ import java.io.IOException; import java.net.InetSocketAddress; +import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; -public class SimpleSocketChannelProvider implements SocketChannelProvider{ +public class SimpleSocketChannelProvider implements SocketChannelProvider { - private final TarantoolNodeInfo tarantoolNodeInfo; + private final TarantoolInstanceInfo tarantoolInstanceInfo; - private TarantoolNodeConnection nodeConnection; + private TarantoolInstanceConnection nodeConnection; public SimpleSocketChannelProvider(InetSocketAddress socketAddress, String username, String password) { - this.tarantoolNodeInfo = TarantoolNodeInfo.create(socketAddress, username, password); + this.tarantoolInstanceInfo = TarantoolInstanceInfo.create(socketAddress, username, password); } public SimpleSocketChannelProvider(String address, String username, String password) { - this.tarantoolNodeInfo = TarantoolNodeInfo.create(address, username, password); + this.tarantoolInstanceInfo = TarantoolInstanceInfo.create(address, username, password); } @Override - public void connect() { - nodeConnection = TarantoolNodeConnection.connect(tarantoolNodeInfo); + public void connect() throws IOException { + nodeConnection = TarantoolInstanceConnection.connect(tarantoolInstanceInfo); } @Override public SocketChannel getNext() { try { - return SocketChannel.open(tarantoolNodeInfo.getSocketAddress()); + return SocketChannel.open(tarantoolInstanceInfo.getSocketAddress()); } catch (IOException e) { - throw new CommunicationException("Exception occurred while connecting to node " + tarantoolNodeInfo, e); + throw new CommunicationException("Exception occurred while connecting to node " + tarantoolInstanceInfo, e); } } + public void writeBuffer(ByteBuffer byteBuffer) throws IOException { + SocketChannel channel2Write = getChannel(); + BinaryProtoUtils.writeFully(channel2Write, byteBuffer); + } + + public TarantoolBinaryPackage readPackage() throws IOException { + SocketChannel channel2Read = getChannel(); + return BinaryProtoUtils.readPacket(channel2Read); + } + @Override public SocketChannel getChannel() { if (nodeConnection == null) { throw new IllegalStateException("Not initialized"); } - return null; + return nodeConnection.getChannel(); } } diff --git a/src/main/java/org/tarantool/SocketChannelProvider.java b/src/main/java/org/tarantool/SocketChannelProvider.java index 5d4700fe..cfae7ba1 100644 --- a/src/main/java/org/tarantool/SocketChannelProvider.java +++ b/src/main/java/org/tarantool/SocketChannelProvider.java @@ -1,11 +1,15 @@ package org.tarantool; +import org.tarantool.server.TarantoolBinaryPackage; + +import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public interface SocketChannelProvider { - void connect(); + void connect() throws IOException; /** * Provides socket channel to init restore connection. @@ -15,4 +19,8 @@ public interface SocketChannelProvider { SocketChannel getNext(); SocketChannel getChannel(); + + TarantoolBinaryPackage readPackage() throws IOException; + + void writeBuffer(ByteBuffer byteBuffer) throws IOException; } diff --git a/src/main/java/org/tarantool/TarantoolBase.java b/src/main/java/org/tarantool/TarantoolBase.java index e225c0d8..23a160d1 100644 --- a/src/main/java/org/tarantool/TarantoolBase.java +++ b/src/main/java/org/tarantool/TarantoolBase.java @@ -1,7 +1,7 @@ package org.tarantool; import org.tarantool.server.BinaryProtoUtils; -import org.tarantool.server.TarantoolNodeConnectionMeta; +import org.tarantool.server.TarantoolInstanceConnectionMeta; import java.io.IOException; import java.net.Socket; @@ -15,7 +15,7 @@ public abstract class TarantoolBase extends AbstractTarantoolOps headers = pack.getHeaders(); @@ -405,7 +407,9 @@ protected void writeThread() { writerBuffer.flip(); writeLock.lock(); try { - BinaryProtoUtils.writeFully(getWriteChannel(), writerBuffer); + socketProvider.writeBuffer(writerBuffer); + //todo +// BinaryProtoUtils.writeFully(getWriteChannel(), writerBuffer); } finally { writeLock.unlock(); } diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index 0fbf6108..27c8022b 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -26,9 +26,9 @@ public class TarantoolClusterClient extends TarantoolClientImpl { /* Collection of operations to be retried. */ private ConcurrentHashMap> retries = new ConcurrentHashMap>(); - private final Collection slaveHosts; + private final Collection slaveHosts; - private final TarantoolNodeInfo infoHost; + private final TarantoolInstanceInfo infoHost; private final Integer infoHostConnectionTimeout; private final ClusterTopologyDiscoverer topologyDiscoverer; /** @@ -48,7 +48,7 @@ public TarantoolClusterClient(TarantoolClusterClientConfig config, SocketChannel this.executor = config.executor == null ? Executors.newSingleThreadExecutor() : config.executor; - this.infoHost = TarantoolNodeInfo.create(config.infoHost); + this.infoHost = TarantoolInstanceInfo.create(config.infoHost); this.infoHostConnectionTimeout = config.infoHostConnectionTimeout; this.topologyDiscoverer = new ClusterTopologyFromShardDiscovererImpl(config); @@ -61,8 +61,8 @@ public TarantoolClusterClient(TarantoolClusterClientConfig config, SocketChannel * @throws CommunicationException in case of communication with {@code infoNode} exception * @throws IllegalArgumentException in case when the info node returned invalid address */ - private Collection refreshServerList(TarantoolNodeInfo infoNode) { - List newServerList = topologyDiscoverer + private Collection refreshServerList(TarantoolInstanceInfo infoNode) { + List newServerList = topologyDiscoverer .discoverTarantoolNodes(infoNode, infoHostConnectionTimeout); writeLock.lock(); @@ -71,7 +71,7 @@ private Collection refreshServerList(TarantoolNodeInfo infoNo RoundRobinSocketProviderImpl rSocketProvider = (RoundRobinSocketProviderImpl) this.socketProvider; - TarantoolNodeInfo currentNode = rSocketProvider.getCurrentNode(); + TarantoolInstanceInfo currentNode = rSocketProvider.getCurrentNode(); int sameNodeIndex = newServerList.indexOf(currentNode); if (sameNodeIndex != -1) { diff --git a/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java b/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java index 330f9e63..6931fe34 100644 --- a/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java +++ b/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java @@ -1,9 +1,9 @@ package org.tarantool.cluster; -import org.tarantool.server.TarantoolNodeInfo; +import org.tarantool.server.TarantoolInstanceInfo; import java.util.List; public interface ClusterTopologyDiscoverer { - List discoverTarantoolNodes(TarantoolNodeInfo infoNode, Integer infoHostConnectionTimeout); + List discoverTarantoolNodes(TarantoolInstanceInfo infoNode, Integer infoHostConnectionTimeout); } diff --git a/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java b/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java index 5906fc7a..792084b6 100644 --- a/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java +++ b/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java @@ -2,7 +2,7 @@ import org.tarantool.TarantoolClientImpl; import org.tarantool.TarantoolClusterClientConfig; -import org.tarantool.server.TarantoolNodeInfo; +import org.tarantool.server.TarantoolInstanceInfo; import java.util.ArrayList; import java.util.List; @@ -19,8 +19,8 @@ public ClusterTopologyFromShardDiscovererImpl(TarantoolClusterClientConfig clien } @Override - public List discoverTarantoolNodes(TarantoolNodeInfo infoNode, - Integer infoHostConnectionTimeout) { + public List discoverTarantoolNodes(TarantoolInstanceInfo infoNode, + Integer infoHostConnectionTimeout) { List list = new TarantoolClientImpl(infoNode.getSocketAddress(), clientConfig) .syncOps() @@ -30,7 +30,7 @@ public List discoverTarantoolNodes(TarantoolNodeInfo infoNode Map shardHash2DescriptionMap = (Map) getValue(funcResult, "sharding"); - List result = new ArrayList<>(); + List result = new ArrayList<>(); for (Object shardHash2Description : shardHash2DescriptionMap.entrySet()) { @@ -39,7 +39,7 @@ public List discoverTarantoolNodes(TarantoolNodeInfo infoNode for (Object replica : replicas.entrySet()) { Object replicaUri = getValue(((Map.Entry) replica).getValue(), "uri"); - result.add(TarantoolNodeInfo.create(parseReplicaUri(replicaUri.toString()))); + result.add(TarantoolInstanceInfo.create(parseReplicaUri(replicaUri.toString()))); } } diff --git a/src/main/java/org/tarantool/server/BinaryProtoUtils.java b/src/main/java/org/tarantool/server/BinaryProtoUtils.java index c9e8e9fd..757d02ac 100644 --- a/src/main/java/org/tarantool/server/BinaryProtoUtils.java +++ b/src/main/java/org/tarantool/server/BinaryProtoUtils.java @@ -53,7 +53,7 @@ public static TarantoolBinaryPackage readPacket(InputStream inputStream) throws * @throws CommunicationException when welcome string is invalid * @throws TarantoolException in case of failed authentication */ - public static TarantoolNodeConnectionMeta connect(Socket socket, String username, String password) throws IOException { + public static TarantoolInstanceConnectionMeta connect(Socket socket, String username, String password) throws IOException { byte[] inputBytes = new byte[64]; InputStream inputStream = socket.getInputStream(); @@ -85,7 +85,7 @@ public static TarantoolNodeConnectionMeta connect(Socket socket, String username } } - return new TarantoolNodeConnectionMeta(salt, serverVersion); + return new TarantoolInstanceConnectionMeta(salt, serverVersion); } /** @@ -99,7 +99,7 @@ public static TarantoolNodeConnectionMeta connect(Socket socket, String username * @throws CommunicationException when welcome string is invalid * @throws TarantoolException in case of failed authentication */ - public static TarantoolNodeConnectionMeta connect(SocketChannel channel, String username, String password) throws IOException { + public static TarantoolInstanceConnectionMeta connect(SocketChannel channel, String username, String password) throws IOException { ByteBuffer welcomeBytes = ByteBuffer.wrap(new byte[64]); channel.read(welcomeBytes); @@ -125,7 +125,7 @@ public static TarantoolNodeConnectionMeta connect(SocketChannel channel, String } } - return new TarantoolNodeConnectionMeta(salt, serverVersion); + return new TarantoolInstanceConnectionMeta(salt, serverVersion); } public static void writeFully(OutputStream stream, ByteBuffer buffer) throws IOException { diff --git a/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java b/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java new file mode 100644 index 00000000..a8e08226 --- /dev/null +++ b/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java @@ -0,0 +1,81 @@ +package org.tarantool.server; + +import org.tarantool.CommunicationException; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.channels.SocketChannel; + +public class TarantoolInstanceConnection implements Closeable { + + /** + * Information about connection + */ + private final TarantoolInstanceInfo nodeInfo; + + /** + * Connection metadata + */ + private final TarantoolInstanceConnectionMeta meta; + + /** + * Nonnull connection to a tarantool instance + */ + protected final SocketChannel channel; + + private TarantoolInstanceConnection(TarantoolInstanceInfo nodeInfo, + TarantoolInstanceConnectionMeta meta, + SocketChannel channel) { + this.nodeInfo = nodeInfo; + this.meta = meta; + this.channel = channel; + } + + /** + * Attempts to connect to a tarantool instance and create correspond TarantoolInstanceConnection + * + * @param tarantoolInstanceInfo + * @throws CommunicationException + */ + public static TarantoolInstanceConnection connect(TarantoolInstanceInfo tarantoolInstanceInfo) throws IOException { + SocketChannel channel; + try { + channel = SocketChannel.open(tarantoolInstanceInfo.getSocketAddress()); + String username = tarantoolInstanceInfo.getUsername(); + String password = tarantoolInstanceInfo.getPassword(); + + TarantoolInstanceConnectionMeta meta = BinaryProtoUtils.connect(channel, username, password); + + return new TarantoolInstanceConnection(tarantoolInstanceInfo, meta, channel); + } catch (IOException e) { + throw new IOException("IOException occurred while connecting to node " + tarantoolInstanceInfo, e); + } + } + + public TarantoolInstanceInfo getNodeInfo() { + return nodeInfo; + } + + public TarantoolInstanceConnectionMeta getMeta() { + return meta; + } + + public SocketChannel getChannel() { + return channel; + } + + private void closeConnection() { + if (channel != null) { + try { + channel.close(); + } catch (IOException ignored) { + + } + } + } + + @Override + public void close() throws IOException { + closeConnection(); + } +} diff --git a/src/main/java/org/tarantool/server/TarantoolNodeConnectionMeta.java b/src/main/java/org/tarantool/server/TarantoolInstanceConnectionMeta.java similarity index 71% rename from src/main/java/org/tarantool/server/TarantoolNodeConnectionMeta.java rename to src/main/java/org/tarantool/server/TarantoolInstanceConnectionMeta.java index 40102ce6..dba054b8 100644 --- a/src/main/java/org/tarantool/server/TarantoolNodeConnectionMeta.java +++ b/src/main/java/org/tarantool/server/TarantoolInstanceConnectionMeta.java @@ -1,11 +1,11 @@ package org.tarantool.server; -public class TarantoolNodeConnectionMeta { +public class TarantoolInstanceConnectionMeta { private final String salt; private final String serverVersion; - public TarantoolNodeConnectionMeta(String salt, String serverVersion) { + public TarantoolInstanceConnectionMeta(String salt, String serverVersion) { this.salt = salt; this.serverVersion = serverVersion; } diff --git a/src/main/java/org/tarantool/server/TarantoolNodeInfo.java b/src/main/java/org/tarantool/server/TarantoolInstanceInfo.java similarity index 81% rename from src/main/java/org/tarantool/server/TarantoolNodeInfo.java rename to src/main/java/org/tarantool/server/TarantoolInstanceInfo.java index 5bdc7765..98e78b44 100644 --- a/src/main/java/org/tarantool/server/TarantoolNodeInfo.java +++ b/src/main/java/org/tarantool/server/TarantoolInstanceInfo.java @@ -1,19 +1,18 @@ package org.tarantool.server; import java.net.*; -import java.nio.channels.*; import java.util.*; /** - * Holds info about a tarantool node. + * Holds info about a tarantool instance. */ -public class TarantoolNodeInfo { +public class TarantoolInstanceInfo { private final InetSocketAddress socketAddress; private final String username; private final String password; - private TarantoolNodeInfo(InetSocketAddress socketAddress, String username, String password) { + private TarantoolInstanceInfo(InetSocketAddress socketAddress, String username, String password) { this.socketAddress = socketAddress; this.username = username; this.password = password; @@ -38,7 +37,7 @@ public String getPassword() { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - TarantoolNodeInfo node = (TarantoolNodeInfo) o; + TarantoolInstanceInfo node = (TarantoolInstanceInfo) o; return socketAddress.equals(node.socketAddress); } @@ -50,14 +49,14 @@ public int hashCode() { /** * * @param socketAddress Nonnull socket address - * @return Instance of {@link TarantoolNodeInfo} + * @return Instance of {@link TarantoolInstanceInfo} */ - public static TarantoolNodeInfo create(InetSocketAddress socketAddress, String username, String password) { + public static TarantoolInstanceInfo create(InetSocketAddress socketAddress, String username, String password) { if (socketAddress == null) { throw new IllegalArgumentException("A socket address can not be null."); } - return new TarantoolNodeInfo(socketAddress, username, password); + return new TarantoolInstanceInfo(socketAddress, username, password); } @@ -70,12 +69,12 @@ public static TarantoolNodeInfo create(InetSocketAddress socketAddress, String u * permission to resolve the host name is * denied. */ - public static TarantoolNodeInfo create(String address, String username, String password) { + public static TarantoolInstanceInfo create(String address, String username, String password) { if (address == null) { throw new IllegalArgumentException("A hostname address can not be null."); } - return new TarantoolNodeInfo(parseAddress(address), username, password); + return new TarantoolInstanceInfo(parseAddress(address), username, password); } /** @@ -105,7 +104,7 @@ private static InetSocketAddress parseAddress(String addr) { @Override public String toString() { - return "TarantoolNodeInfo{" + + return "TarantoolInstanceInfo{" + "socketAddress=" + socketAddress + ", username='" + username + '\'' + ", password='" + password + '\'' + diff --git a/src/main/java/org/tarantool/server/TarantoolNodeConnection.java b/src/main/java/org/tarantool/server/TarantoolNodeConnection.java deleted file mode 100644 index a2600cc5..00000000 --- a/src/main/java/org/tarantool/server/TarantoolNodeConnection.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.tarantool.server; - -import org.tarantool.ByteBufferInputStream; -import org.tarantool.CommunicationException; - -import java.io.DataInputStream; -import java.io.IOException; -import java.net.*; -import java.nio.channels.SocketChannel; - -@Deprecated -public class TarantoolNodeConnection { - - /** - * External - */ - private final TarantoolNodeInfo nodeInfo; - private final TarantoolNodeConnectionMeta meta; - - protected final SocketChannel channel; - - private TarantoolNodeConnection(TarantoolNodeInfo nodeInfo, TarantoolNodeConnectionMeta meta, SocketChannel channel) { - this.nodeInfo = nodeInfo; - this.meta = meta; - this.channel = channel; - } - - /** - * - * - * @param tarantoolNodeInfo - * @throws CommunicationException - */ - public static TarantoolNodeConnection connect(TarantoolNodeInfo tarantoolNodeInfo) { - SocketChannel channel; - try { - channel = SocketChannel.open(tarantoolNodeInfo.getSocketAddress()); - String username = tarantoolNodeInfo.getUsername(); - String password = tarantoolNodeInfo.getPassword(); - - TarantoolNodeConnectionMeta meta = BinaryProtoUtils.connect(channel, username, password); - - return new TarantoolNodeConnection(tarantoolNodeInfo, meta, channel); - } catch (IOException e) { - throw new CommunicationException("Exception occurred while connecting to node " + tarantoolNodeInfo, e); - } - } - - @Deprecated - public void closeConnection() { - if (channel != null) { - try { - channel.close(); - } catch (IOException ignored) { - - } - } - } -} diff --git a/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java b/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java index 8f3060b0..fc240994 100644 --- a/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java +++ b/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java @@ -3,22 +3,24 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.tarantool.TarantoolClusterClientConfig; -import org.tarantool.server.TarantoolNodeInfo; +import org.tarantool.server.TarantoolInstanceInfo; class ClusterTopologyFromShardDiscovererImplTest { @DisplayName("Test that a list which describes the topology is fetched correctly") @Test void testListFetching() { - TarantoolNodeInfo tarantoolNodeInfo = TarantoolNodeInfo.create("localhost:3301"); + //todo + TarantoolInstanceInfo tarantoolInstanceInfo = TarantoolInstanceInfo + .create("localhost:3301", "testUsername", "testPassword"); TarantoolClusterClientConfig clientConfig = new TarantoolClusterClientConfig(); clientConfig.username = "storage"; clientConfig.password = "storage"; -// Collection tarantoolNodes = +// Collection tarantoolNodes = // new ClusterTopologyFromShardDiscovererImpl(clientConfig) -// .discoverTarantoolNodes(tarantoolNodeInfo, 5000); +// .discoverTarantoolNodes(tarantoolInstanceInfo, 5000); int i = 0; } } \ No newline at end of file diff --git a/src/test/java/org/tarantool/server/TarantoolNodeInfoTest.java b/src/test/java/org/tarantool/server/TarantoolInstanceInfoTest.java similarity index 75% rename from src/test/java/org/tarantool/server/TarantoolNodeInfoTest.java rename to src/test/java/org/tarantool/server/TarantoolInstanceInfoTest.java index 147ea5a5..818f25c1 100644 --- a/src/test/java/org/tarantool/server/TarantoolNodeInfoTest.java +++ b/src/test/java/org/tarantool/server/TarantoolInstanceInfoTest.java @@ -6,9 +6,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; -class TarantoolNodeInfoTest { +class TarantoolInstanceInfoTest { - @DisplayName("Test that a TarantoolNodeInfo throws an illegal argument exception" + + @DisplayName("Test that a TarantoolInstanceInfo throws an illegal argument exception" + "in case when it's being created with wrong address string") @ParameterizedTest @ValueSource(strings = { @@ -17,7 +17,7 @@ class TarantoolNodeInfoTest { }) void testThrowsExceptionInCaseOfInvalidStringAddress(String address) { assertThrows(IllegalArgumentException.class, - () -> TarantoolNodeInfo.create(address), + () -> TarantoolInstanceInfo.create(address, "validUsername", "validPassword"), "We expect the code under test to throw an IllegalArgumentException, but it didn't"); } } \ No newline at end of file From a8dde82978a881b01ce7bb51b88f0ca4dbe348e5 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Tue, 26 Feb 2019 23:28:34 +0300 Subject: [PATCH 15/30] Try replace socket provider with NodeCommunicationProvider. --- .../tarantool/NodeCommunicationProvider.java | 17 ++++ ... RoundRobinNodeCommunicationProvider.java} | 85 ++++++------------- .../RoundRobinSocketProviderImpl.java | 10 +-- .../SimpleSocketChannelProvider.java | 39 ++------- .../SingleNodeCommunicationProvider.java | 56 ++++++++++++ .../org/tarantool/SocketChannelProvider.java | 15 +--- .../org/tarantool/TarantoolClientImpl.java | 42 ++++----- .../org/tarantool/TarantoolClusterClient.java | 19 +++-- 8 files changed, 135 insertions(+), 148 deletions(-) create mode 100644 src/main/java/org/tarantool/NodeCommunicationProvider.java rename src/main/java/org/tarantool/{RoundRobinSocketChannelProvider.java => RoundRobinNodeCommunicationProvider.java} (62%) create mode 100644 src/main/java/org/tarantool/SingleNodeCommunicationProvider.java diff --git a/src/main/java/org/tarantool/NodeCommunicationProvider.java b/src/main/java/org/tarantool/NodeCommunicationProvider.java new file mode 100644 index 00000000..7d3fbb9f --- /dev/null +++ b/src/main/java/org/tarantool/NodeCommunicationProvider.java @@ -0,0 +1,17 @@ +package org.tarantool; + +import org.tarantool.server.*; + +import java.io.*; +import java.nio.*; + +public interface NodeCommunicationProvider { + + void connect() throws IOException; + + TarantoolBinaryPackage readPackage() throws IOException; + + void writeBuffer(ByteBuffer byteBuffer) throws IOException; + + String getDescription(); +} diff --git a/src/main/java/org/tarantool/RoundRobinSocketChannelProvider.java b/src/main/java/org/tarantool/RoundRobinNodeCommunicationProvider.java similarity index 62% rename from src/main/java/org/tarantool/RoundRobinSocketChannelProvider.java rename to src/main/java/org/tarantool/RoundRobinNodeCommunicationProvider.java index 507dfd6b..350e0c7d 100644 --- a/src/main/java/org/tarantool/RoundRobinSocketChannelProvider.java +++ b/src/main/java/org/tarantool/RoundRobinNodeCommunicationProvider.java @@ -6,34 +6,35 @@ import org.tarantool.server.TarantoolInstanceInfo; import java.io.IOException; -import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; -import java.util.List; +import java.util.*; +import java.util.stream.*; -public class RoundRobinSocketChannelProvider implements SocketChannelProvider { +public class RoundRobinNodeCommunicationProvider implements NodeCommunicationProvider { /** Timeout to establish socket connection with an individual server. */ - private int timeout; // 0 is infinite. + private final int timeout; // 0 is infinite. /** Limit of retries. */ private int retriesLimit = -1; // No-limit. private TarantoolInstanceInfo[] nodes; - private TarantoolInstanceInfo currentNode; + private TarantoolInstanceConnection currentConnection; private int pos = 0; - public RoundRobinSocketChannelProvider(String[] slaveHosts, String username, String password) { + public RoundRobinNodeCommunicationProvider(String[] slaveHosts, String username, String password, int timeout) { + this.timeout = timeout; if (slaveHosts == null || slaveHosts.length < 1) { throw new IllegalArgumentException("slave hosts is null ot empty"); } - updateNodes(slaveHosts, username, password); + setNodes(slaveHosts, username, password); } - private void updateNodes(String[] slaveHosts, String username, String password) { + private void setNodes(String[] slaveHosts, String username, String password) { //todo add read-write lock nodes = new TarantoolInstanceInfo[slaveHosts.length]; for (int i = 0; i < slaveHosts.length; i++) { @@ -56,7 +57,6 @@ public void updateNodes(List slaveHosts) { } - /** * @return Non-empty list of round-robined nodes */ @@ -100,48 +100,6 @@ public TarantoolInstanceConnection connectNextNode() { throw new CommunicationException("Thread interrupted.", new InterruptedException()); } - /** {@inheritDoc} */ - @Override - public SocketChannel getNext() { - int attempts = getAddressCount(); - long deadline = System.currentTimeMillis() + timeout * attempts; - while (!Thread.currentThread().isInterrupted()) { - SocketChannel channel = null; - try { - TarantoolInstanceInfo tarantoolInstanceInfo = getNextNode(); - - - TarantoolInstanceConnection nodeConnection = TarantoolInstanceConnection.connect(tarantoolInstanceInfo); - - channel = nodeConnection.getChannel(); - - return channel; - } catch (IOException e) { - if (channel != null) { - try { - channel.close(); - } catch (IOException ignored) { - // No-op. - } - } - long now = System.currentTimeMillis(); - if (deadline <= now) { - throw new CommunicationException("Connection time out.", e); - } - if (--attempts == 0) { - // Tried all addresses without any lack, but still have time. - attempts = getAddressCount(); - try { - Thread.sleep((deadline - now) / attempts); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - } - } - } - } - throw new CommunicationException("Thread interrupted.", new InterruptedException()); - } - /** * @return Socket address to use for the next reconnection attempt. */ @@ -160,16 +118,11 @@ protected int getAddressCount() { } - private final TarantoolInstanceInfo tarantoolInstanceInfo; - - private TarantoolInstanceConnection nodeConnection; - @Override public void connect() { - nodeConnection = TarantoolInstanceConnection.connect(tarantoolInstanceInfo); + currentConnection = connectNextNode(); } - public void writeBuffer(ByteBuffer byteBuffer) throws IOException { SocketChannel channel2Write = getChannel(); BinaryProtoUtils.writeFully(channel2Write, byteBuffer); @@ -180,11 +133,21 @@ public TarantoolBinaryPackage readPackage() throws IOException { return BinaryProtoUtils.readPacket(channel2Read); } - @Override - public SocketChannel getChannel() { - if (nodeConnection == null) { + private SocketChannel getChannel() { + if (currentConnection == null) { throw new IllegalStateException("Not initialized"); } - return nodeConnection.getChannel(); + return currentConnection.getChannel(); + } + + @Override + public String getDescription() { + if (currentConnection != null) { + return currentConnection.getNodeInfo().getSocketAddress().toString(); + } else { + return "Unconnected. Available nodes [" + Arrays.stream(nodes) + .map(instanceInfo -> instanceInfo.getSocketAddress().toString()) + .collect(Collectors.joining(", ")); + } } } diff --git a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java index 986825ef..e2d77787 100644 --- a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java +++ b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java @@ -29,20 +29,20 @@ public class RoundRobinSocketProviderImpl implements SocketChannelProvider { * * @param slaveHosts Array of addresses in a form of [host]:[port]. */ - public RoundRobinSocketProviderImpl(String[] slaveHosts) { + public RoundRobinSocketProviderImpl(String[] slaveHosts, String username, String password) { if (slaveHosts == null || slaveHosts.length < 1) { throw new IllegalArgumentException("slave hosts is null ot empty"); } - updateNodes(slaveHosts); + updateNodes(slaveHosts, username, password); } - private void updateNodes(String[] slaveHosts) { + private void updateNodes(String[] slaveHosts, String username, String password) { //todo add read-write lock nodes = new TarantoolInstanceInfo[slaveHosts.length]; for (int i = 0; i < slaveHosts.length; i++) { String slaveHostAddress = slaveHosts[i]; - nodes[i] = TarantoolInstanceInfo.create(slaveHostAddress); + nodes[i] = TarantoolInstanceInfo.create(slaveHostAddress, username, password); } pos = 0; @@ -120,7 +120,7 @@ public int getRetriesLimit() { /** {@inheritDoc} */ @Override - public SocketChannel getNext() { + public SocketChannel get() { int attempts = getAddressCount(); long deadline = System.currentTimeMillis() + timeout * attempts; while (!Thread.currentThread().isInterrupted()) { diff --git a/src/main/java/org/tarantool/SimpleSocketChannelProvider.java b/src/main/java/org/tarantool/SimpleSocketChannelProvider.java index 38ac5ff6..119def39 100644 --- a/src/main/java/org/tarantool/SimpleSocketChannelProvider.java +++ b/src/main/java/org/tarantool/SimpleSocketChannelProvider.java @@ -2,17 +2,14 @@ import org.tarantool.server.*; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.nio.channels.SocketChannel; +import java.io.*; +import java.net.*; +import java.nio.channels.*; -public class SimpleSocketChannelProvider implements SocketChannelProvider { +public class SimpleSocketChannelProvider implements SocketChannelProvider{ private final TarantoolInstanceInfo tarantoolInstanceInfo; - private TarantoolInstanceConnection nodeConnection; - public SimpleSocketChannelProvider(InetSocketAddress socketAddress, String username, String password) { this.tarantoolInstanceInfo = TarantoolInstanceInfo.create(socketAddress, username, password); } @@ -22,34 +19,12 @@ public SimpleSocketChannelProvider(String address, String username, String passw } @Override - public void connect() throws IOException { - nodeConnection = TarantoolInstanceConnection.connect(tarantoolInstanceInfo); - } - - @Override - public SocketChannel getNext() { + public SocketChannel get() { try { return SocketChannel.open(tarantoolInstanceInfo.getSocketAddress()); } catch (IOException e) { - throw new CommunicationException("Exception occurred while connecting to node " + tarantoolInstanceInfo, e); - } - } - - public void writeBuffer(ByteBuffer byteBuffer) throws IOException { - SocketChannel channel2Write = getChannel(); - BinaryProtoUtils.writeFully(channel2Write, byteBuffer); - } - - public TarantoolBinaryPackage readPackage() throws IOException { - SocketChannel channel2Read = getChannel(); - return BinaryProtoUtils.readPacket(channel2Read); - } - - @Override - public SocketChannel getChannel() { - if (nodeConnection == null) { - throw new IllegalStateException("Not initialized"); + String msg = "Exception occurred while connecting to instance " + tarantoolInstanceInfo; + throw new CommunicationException(msg, e); } - return nodeConnection.getChannel(); } } diff --git a/src/main/java/org/tarantool/SingleNodeCommunicationProvider.java b/src/main/java/org/tarantool/SingleNodeCommunicationProvider.java new file mode 100644 index 00000000..857f33d8 --- /dev/null +++ b/src/main/java/org/tarantool/SingleNodeCommunicationProvider.java @@ -0,0 +1,56 @@ +package org.tarantool; + +import org.tarantool.server.*; + +import java.io.*; +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.util.stream.*; + +public class SingleNodeCommunicationProvider implements NodeCommunicationProvider { + + private final TarantoolInstanceInfo tarantoolInstanceInfo; + + private TarantoolInstanceConnection nodeConnection; + + public SingleNodeCommunicationProvider(InetSocketAddress socketAddress, String username, String password) { + this.tarantoolInstanceInfo = TarantoolInstanceInfo.create(socketAddress, username, password); + } + + public SingleNodeCommunicationProvider(String address, String username, String password) { + this.tarantoolInstanceInfo = TarantoolInstanceInfo.create(address, username, password); + } + + @Override + public void connect() throws IOException { + nodeConnection = TarantoolInstanceConnection.connect(tarantoolInstanceInfo); + } + + public void writeBuffer(ByteBuffer byteBuffer) throws IOException { + SocketChannel channel2Write = getChannel(); + BinaryProtoUtils.writeFully(channel2Write, byteBuffer); + } + + public TarantoolBinaryPackage readPackage() throws IOException { + SocketChannel channel2Read = getChannel(); + return BinaryProtoUtils.readPacket(channel2Read); + } + + private SocketChannel getChannel() { + if (nodeConnection == null) { + throw new IllegalStateException("Not initialized"); + } + return nodeConnection.getChannel(); + } + + @Override + public String getDescription() { + if (nodeConnection != null) { + return nodeConnection.getNodeInfo().getSocketAddress().toString(); + } else { + return "Unconnected. Node " + tarantoolInstanceInfo.getSocketAddress().toString(); + } + } +} diff --git a/src/main/java/org/tarantool/SocketChannelProvider.java b/src/main/java/org/tarantool/SocketChannelProvider.java index cfae7ba1..49224667 100644 --- a/src/main/java/org/tarantool/SocketChannelProvider.java +++ b/src/main/java/org/tarantool/SocketChannelProvider.java @@ -1,26 +1,13 @@ package org.tarantool; -import org.tarantool.server.TarantoolBinaryPackage; - -import java.io.IOException; -import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public interface SocketChannelProvider { - - void connect() throws IOException; - /** * Provides socket channel to init restore connection. * You could change hosts on fail and sleep between retries in this method * @return the result of SocketChannel open(SocketAddress remote) call */ - SocketChannel getNext(); - - SocketChannel getChannel(); - - TarantoolBinaryPackage readPackage() throws IOException; - - void writeBuffer(ByteBuffer byteBuffer) throws IOException; + SocketChannel get(); } diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index c32d6d4d..b3f6c435 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -31,7 +31,7 @@ public class TarantoolClientImpl extends TarantoolBase> implements Tar /** * External */ - protected SocketChannelProvider socketProvider; + protected NodeCommunicationProvider communicationProvider; /** * Max amount of one by one reconnect attempts @@ -80,19 +80,19 @@ public void run() { }); public TarantoolClientImpl(InetSocketAddress socketAddress, TarantoolClientConfig config) { - this(new SimpleSocketChannelProvider(socketAddress, config.username, config.password), config); + this(new SingleNodeCommunicationProvider(socketAddress, config.username, config.password), config); } public TarantoolClientImpl(String address, TarantoolClientConfig config) { - this(new SimpleSocketChannelProvider(address, config.username, config.password), config); + this(new SingleNodeCommunicationProvider(address, config.username, config.password), config); } - public TarantoolClientImpl(SocketChannelProvider socketProvider, TarantoolClientConfig config) { + public TarantoolClientImpl(NodeCommunicationProvider communicationProvider, TarantoolClientConfig config) { super(); this.thumbstone = NOT_INIT_EXCEPTION; this.config = config; this.initialRequestSize = config.defaultRequestSize; - this.socketProvider = socketProvider; + this.communicationProvider = communicationProvider; this.stats = new TarantoolClientStats(); this.futures = new ConcurrentHashMap<>(config.predictedFutures); this.sharedBuffer = ByteBuffer.allocateDirect(config.sharedBufferSize); @@ -125,20 +125,13 @@ public TarantoolClientImpl(SocketChannelProvider socketProvider, TarantoolClient } protected void reconnect(int retry, Throwable lastError) { - SocketChannel channel; while (!Thread.currentThread().isInterrupted()) { - try { - if (areRetriesExhausted(retry)) { - Throwable cause = lastError == NOT_INIT_EXCEPTION ? null : lastError; - throw new CommunicationException("Connection retries exceeded.", cause); - } - channel = socketProvider.getNext(); - } catch (Exception e) { - close(e); - return; + if (areRetriesExhausted(retry)) { + Throwable cause = lastError == NOT_INIT_EXCEPTION ? null : lastError; + close(new CommunicationException("Connection retries exceeded.", cause)); } try { - connect(channel); + connect(communicationProvider); return; } catch (Exception e) { closeChannel(channel); @@ -162,15 +155,14 @@ private boolean areRetriesExhausted(int retries) { return retries >= limit; } - protected void connect(final SocketChannel channel) throws Exception { + protected void connect(final NodeCommunicationProvider communicationProvider) throws Exception { try { - this.currentNodeInfo = BinaryProtoUtils.connect(channel, config.username, config.password); +// this.currentNodeInfo = BinaryProtoUtils.connect(channel, config.username, config.password); + communicationProvider.connect(); } catch (IOException e) { throw new CommunicationException("Couldn't connect to tarantool", e); } - channel.configureBlocking(false); - this.channel = channel; writerBufferLock.lock(); try { sharedBuffer.clear(); @@ -178,7 +170,7 @@ protected void connect(final SocketChannel channel) throws Exception { writerBufferLock.unlock(); } this.thumbstone = null; - startThreads(channel.socket().getRemoteSocketAddress().toString()); + startThreads(communicationProvider.getDescription()); } protected void startThreads(String threadName) throws InterruptedException { @@ -366,9 +358,7 @@ protected void readThread() { try { while (!Thread.currentThread().isInterrupted()) { try { - TarantoolBinaryPackage pack = socketProvider.readPackage(); - //todo -// TarantoolBinaryPackage pack = BinaryProtoUtils.readPacket(getReadChannel()); + TarantoolBinaryPackage pack = communicationProvider.readPackage(); Map headers = pack.getHeaders(); @@ -407,9 +397,7 @@ protected void writeThread() { writerBuffer.flip(); writeLock.lock(); try { - socketProvider.writeBuffer(writerBuffer); - //todo -// BinaryProtoUtils.writeFully(getWriteChannel(), writerBuffer); + communicationProvider.writeBuffer(writerBuffer); } finally { writeLock.unlock(); } diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index 27c8022b..d34f0de2 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -35,20 +35,21 @@ public class TarantoolClusterClient extends TarantoolClientImpl { * @param config Configuration. */ public TarantoolClusterClient(TarantoolClusterClientConfig config) { - this(config, new RoundRobinSocketProviderImpl(config.slaveHosts).setTimeout(config.operationExpiryTimeMillis)); - +// this(config, new RoundRobinSocketProviderImpl(config.slaveHosts).setTimeout(config.operationExpiryTimeMillis)); + this(config, new RoundRobinNodeCommunicationProvider(config.slaveHosts, + config.username, config.password, config.operationExpiryTimeMillis)); } /** * @param provider Socket channel provider. * @param config Configuration. */ - public TarantoolClusterClient(TarantoolClusterClientConfig config, SocketChannelProvider provider) { + public TarantoolClusterClient(TarantoolClusterClientConfig config, NodeCommunicationProvider provider) { super(provider, config); this.executor = config.executor == null ? Executors.newSingleThreadExecutor() : config.executor; - this.infoHost = TarantoolInstanceInfo.create(config.infoHost); + this.infoHost = TarantoolInstanceInfo.create(config.infoHost, config.username, config.password); this.infoHostConnectionTimeout = config.infoHostConnectionTimeout; this.topologyDiscoverer = new ClusterTopologyFromShardDiscovererImpl(config); @@ -69,21 +70,21 @@ private Collection refreshServerList(TarantoolInstanceInf // todo add a read lock try { - RoundRobinSocketProviderImpl rSocketProvider = (RoundRobinSocketProviderImpl) this.socketProvider; + RoundRobinNodeCommunicationProvider cp = (RoundRobinNodeCommunicationProvider) this.communicationProvider; - TarantoolInstanceInfo currentNode = rSocketProvider.getCurrentNode(); + TarantoolInstanceInfo currentNode = cp.getCurrentNode(); int sameNodeIndex = newServerList.indexOf(currentNode); if (sameNodeIndex != -1) { Collections.swap(newServerList, 0, sameNodeIndex); - rSocketProvider.updateNodes(newServerList); + cp.setNodes(newServerList); } else { - rSocketProvider.updateNodes(newServerList); + cp.setNodes(newServerList); die("The server list have been changed.", null); //todo } - rSocketProvider.updateNodes(newServerList); + cp.updateNodes(newServerList); } finally { From 20b2eb03aa3a2b8ac7370c67941fe4d212b287da Mon Sep 17 00:00:00 2001 From: dponomarev Date: Wed, 27 Feb 2019 02:12:50 +0300 Subject: [PATCH 16/30] Make connector compilable --- .../org/tarantool/TestTarantoolClient.java | 25 +++----- .../org/tarantool/TarantoolClusterClient.java | 4 +- ...lusterTopologyFromShardDiscovererImpl.java | 4 +- .../AbstractTarantoolConnectorIT.java | 9 ++- .../java/org/tarantool/ClientReconnectIT.java | 62 ++++++++++++------- .../java/org/tarantool/TarantoolConsole.java | 2 +- .../TestNodeCommunicationProvider.java | 35 +++++++++++ .../tarantool/TestSocketChannelProvider.java | 2 +- src/test/perf/org/tarantool/MyBenchmark.java | 33 +++++++++- 9 files changed, 129 insertions(+), 47 deletions(-) create mode 100644 src/test/java/org/tarantool/TestNodeCommunicationProvider.java diff --git a/src/it/java/org/tarantool/TestTarantoolClient.java b/src/it/java/org/tarantool/TestTarantoolClient.java index a8a0e108..c4d53b96 100644 --- a/src/it/java/org/tarantool/TestTarantoolClient.java +++ b/src/it/java/org/tarantool/TestTarantoolClient.java @@ -35,8 +35,8 @@ public static class TarantoolClientTestImpl extends TarantoolClientImpl { final Semaphore s = new Semaphore(0); long latency = 1L; - public TarantoolClientTestImpl(SocketChannelProvider socketProvider, TarantoolClientConfig options) { - super(socketProvider, options); + public TarantoolClientTestImpl(NodeCommunicationProvider nodeComm, TarantoolClientConfig options) { + super(nodeComm, options); Thread t = new Thread(new Runnable() { @Override public void run() { @@ -94,21 +94,12 @@ public static void main(String[] args) throws IOException, InterruptedException, config.sharedBufferSize = 128; //config.sharedBufferSize = 0; - SocketChannelProvider socketChannelProvider = new SocketChannelProvider() { - @Override - public SocketChannel getNext(int retryNumber, Throwable lastError) { - if (lastError != null) { - lastError.printStackTrace(System.out); - } - System.out.println("reconnect"); - try { - return SocketChannel.open(new InetSocketAddress("localhost", 3301)); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - }; - final TarantoolClientTestImpl client = new TarantoolClientTestImpl(socketChannelProvider, config); + + NodeCommunicationProvider nodeComm = + new SingleNodeCommunicationProvider("localhost:3301", config.username, config.password); + + + final TarantoolClientTestImpl client = new TarantoolClientTestImpl(nodeComm, config); config.writeTimeoutMillis = 2; client.latency = 1; client.syncOps.ping(); diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index d34f0de2..e95adf0d 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -72,7 +72,7 @@ private Collection refreshServerList(TarantoolInstanceInf RoundRobinNodeCommunicationProvider cp = (RoundRobinNodeCommunicationProvider) this.communicationProvider; - TarantoolInstanceInfo currentNode = cp.getCurrentNode(); +/* TarantoolInstanceInfo currentNode = cp.getCurrentNode(); int sameNodeIndex = newServerList.indexOf(currentNode); if (sameNodeIndex != -1) { @@ -82,7 +82,7 @@ private Collection refreshServerList(TarantoolInstanceInf cp.setNodes(newServerList); die("The server list have been changed.", null); //todo - } + }*/ cp.updateNodes(newServerList); diff --git a/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java b/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java index 792084b6..b75850d7 100644 --- a/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java +++ b/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java @@ -39,7 +39,9 @@ public List discoverTarantoolNodes(TarantoolInstanceInfo for (Object replica : replicas.entrySet()) { Object replicaUri = getValue(((Map.Entry) replica).getValue(), "uri"); - result.add(TarantoolInstanceInfo.create(parseReplicaUri(replicaUri.toString()))); + + result.add(TarantoolInstanceInfo.create( + parseReplicaUri(replicaUri.toString()), clientConfig.username, clientConfig.password)); } } diff --git a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java index 4c494f50..4e68f89a 100644 --- a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java +++ b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java @@ -35,11 +35,15 @@ public abstract class AbstractTarantoolConnectorIT { protected static final int LISTEN = 3301; protected static final int ADMIN = 3313; protected static final int TIMEOUT = 500; - protected static final int RESTART_TIMEOUT = 2000; + protected static final int RESTART_TIMEOUT = 200000;//todo + @Deprecated protected static final SocketChannelProvider socketChannelProvider = new TestSocketChannelProvider(host, port, RESTART_TIMEOUT); + protected static final NodeCommunicationProvider testNodeCommunicationProvider = + new TestNodeCommunicationProvider(host + ":" + port, username, password, RESTART_TIMEOUT); + protected static TarantoolControl control; protected static TarantoolConsole console; @@ -132,7 +136,8 @@ protected void checkTupleResult(Object res, List tuple) { } protected TarantoolClient makeClient() { - return new TarantoolClientImpl(socketChannelProvider, makeClientConfig()); +// return new TarantoolClientImpl(socketChannelProvider, makeClientConfig()); + return new TarantoolClientImpl(testNodeCommunicationProvider, makeClientConfig()); } protected static TarantoolClientConfig makeClientConfig() { diff --git a/src/test/java/org/tarantool/ClientReconnectIT.java b/src/test/java/org/tarantool/ClientReconnectIT.java index 3edde950..1dc0cc1a 100644 --- a/src/test/java/org/tarantool/ClientReconnectIT.java +++ b/src/test/java/org/tarantool/ClientReconnectIT.java @@ -4,7 +4,10 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; +import org.tarantool.server.TarantoolBinaryPackage; +import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.Collections; import java.util.List; @@ -64,7 +67,7 @@ public void execute() { }); assertTrue(CommunicationException.class.isAssignableFrom(e.getClass()) || - IllegalStateException.class.isAssignableFrom(e.getClass())); + IllegalStateException.class.isAssignableFrom(e.getClass())); assertNotNull(((TarantoolClientImpl) client).getThumbstone()); @@ -85,21 +88,38 @@ public void execute() { @Test public void testSpuriousReturnFromPark() { final CountDownLatch latch = new CountDownLatch(2); - SocketChannelProvider provider = new SocketChannelProvider() { + + TarantoolClientConfig config = makeClientConfig(); + + NodeCommunicationProvider nodeCommunicationProvider = new NodeCommunicationProvider() { + @Override - public SocketChannel getNext(int retryNumber, Throwable lastError) { - if (lastError == null) { - latch.countDown(); - } - return socketChannelProvider.getNext(retryNumber, lastError); + public void connect() throws IOException { + latch.countDown(); + testNodeCommunicationProvider.connect(); + } + + @Override + public TarantoolBinaryPackage readPackage() throws IOException { + return testNodeCommunicationProvider.readPackage(); + } + + @Override + public void writeBuffer(ByteBuffer byteBuffer) throws IOException { + testNodeCommunicationProvider.writeBuffer(byteBuffer); + } + + @Override + public String getDescription() { + return testNodeCommunicationProvider.getDescription(); } }; - client = new TarantoolClientImpl(provider, makeClientConfig()); + client = new TarantoolClientImpl(nodeCommunicationProvider, config); client.syncOps().ping(); // The park() will return inside connector thread. - LockSupport.unpark(((TarantoolClientImpl)client).connector); + LockSupport.unpark(((TarantoolClientImpl) client).connector); // Wait on latch as a proof that reconnect did not happen. // In case of a failure, latch will reach 0 before timeout occurs. @@ -116,7 +136,7 @@ public SocketChannel getNext(int retryNumber, Throwable lastError) { */ @Test public void testCloseWhileOperationsAreInProgress() { - client = new TarantoolClientImpl(socketChannelProvider, makeClientConfig()) { + client = new TarantoolClientImpl(testNodeCommunicationProvider, makeClientConfig()) { @Override protected void write(Code code, Long syncId, Long schemaId, Object... args) { // Skip write. @@ -124,7 +144,7 @@ protected void write(Code code, Long syncId, Long schemaId, Object... args) { }; final Future> res = client.asyncOps().select(SPACE_ID, PK_INDEX_ID, Collections.singletonList(1), - 0, 1, Iterator.EQ); + 0, 1, Iterator.EQ); client.close(); @@ -144,7 +164,7 @@ public void execute() throws Throwable { @Test public void testReconnectWhileOperationsAreInProgress() { final AtomicBoolean writeEnabled = new AtomicBoolean(false); - client = new TarantoolClientImpl(socketChannelProvider, makeClientConfig()) { + client = new TarantoolClientImpl(testNodeCommunicationProvider, makeClientConfig()) { @Override protected void write(Code code, Long syncId, Long schemaId, Object... args) throws Exception { if (writeEnabled.get()) { @@ -154,7 +174,7 @@ protected void write(Code code, Long syncId, Long schemaId, Object... args) thro }; final Future> mustFail = client.asyncOps().select(SPACE_ID, PK_INDEX_ID, Collections.singletonList(1), - 0, 1, Iterator.EQ); + 0, 1, Iterator.EQ); stopTarantool(INSTANCE_NAME); @@ -176,7 +196,7 @@ public void execute() throws Throwable { } Future> res = client.asyncOps().select(SPACE_ID, PK_INDEX_ID, Collections.singletonList(1), - 0, 1, Iterator.EQ); + 0, 1, Iterator.EQ); try { res.get(TIMEOUT, TimeUnit.MILLISECONDS); @@ -188,11 +208,11 @@ public void execute() throws Throwable { @Test public void testConcurrentCloseAndReconnect() { final CountDownLatch latch = new CountDownLatch(2); - client = new TarantoolClientImpl(socketChannelProvider, makeClientConfig()) { + client = new TarantoolClientImpl(testNodeCommunicationProvider, makeClientConfig()) { @Override - protected void connect(final SocketChannel channel) throws Exception { + protected void connect(NodeCommunicationProvider communicationProvider) throws Exception { latch.countDown(); - super.connect(channel); + super.connect(communicationProvider); } }; @@ -221,10 +241,10 @@ public void run() { public void testLongParallelCloseReconnects() { int numThreads = 4; int numClients = 4; - int timeBudget = 30*1000; + int timeBudget = 30 * 1000; final AtomicReferenceArray clients = - new AtomicReferenceArray(numClients); + new AtomicReferenceArray(numClients); for (int idx = 0; idx < clients.length(); idx++) { clients.set(idx, makeClient()); @@ -242,7 +262,7 @@ public void testLongParallelCloseReconnects() { @Override public void run() { while (!Thread.currentThread().isInterrupted() && - deadline > System.currentTimeMillis()) { + deadline > System.currentTimeMillis()) { int idx = rnd.nextInt(clients.length()); @@ -284,7 +304,7 @@ public void run() { fail(e); } if (deadline > System.currentTimeMillis()) { - System.out.println("" + (deadline - System.currentTimeMillis())/1000 + "s remains."); + System.out.println("" + (deadline - System.currentTimeMillis()) / 1000 + "s remains."); } } diff --git a/src/test/java/org/tarantool/TarantoolConsole.java b/src/test/java/org/tarantool/TarantoolConsole.java index 94f76f16..c03f2618 100644 --- a/src/test/java/org/tarantool/TarantoolConsole.java +++ b/src/test/java/org/tarantool/TarantoolConsole.java @@ -168,7 +168,7 @@ private static class TarantoolTcpConsole extends TarantoolConsole { final Socket socket; TarantoolTcpConsole(String host, int port) { - socket = new TestSocketChannelProvider(host, port, TIMEOUT).getNext(1, null).socket(); + socket = new TestSocketChannelProvider(host, port, TIMEOUT).get().socket(); try { reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); writer = new OutputStreamWriter(socket.getOutputStream()); diff --git a/src/test/java/org/tarantool/TestNodeCommunicationProvider.java b/src/test/java/org/tarantool/TestNodeCommunicationProvider.java new file mode 100644 index 00000000..e8d71809 --- /dev/null +++ b/src/test/java/org/tarantool/TestNodeCommunicationProvider.java @@ -0,0 +1,35 @@ +package org.tarantool; + +import java.io.IOException; + +public class TestNodeCommunicationProvider extends SingleNodeCommunicationProvider { + private final long restartTimeout; + + public TestNodeCommunicationProvider(String address, String username, String password, long restartTimeout1) { + super(address, username, password); + + this.restartTimeout = restartTimeout1; + } + + @Override + public void connect() throws IOException { + + long budget = System.currentTimeMillis() + restartTimeout; + while (!Thread.currentThread().isInterrupted()) { + try { + super.connect(); + return; + } catch (Exception e) { + if (budget < System.currentTimeMillis()) + throw new RuntimeException(e); + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + // No-op. + Thread.currentThread().interrupt(); + } + } + } + throw new RuntimeException(new InterruptedException()); + } +} diff --git a/src/test/java/org/tarantool/TestSocketChannelProvider.java b/src/test/java/org/tarantool/TestSocketChannelProvider.java index c724e824..570fb413 100644 --- a/src/test/java/org/tarantool/TestSocketChannelProvider.java +++ b/src/test/java/org/tarantool/TestSocketChannelProvider.java @@ -18,7 +18,7 @@ public TestSocketChannelProvider(String host, int port, int restart_timeout) { } @Override - public SocketChannel getNext(int retryNumber, Throwable lastError) { + public SocketChannel get() { long budget = System.currentTimeMillis() + restart_timeout; while (!Thread.currentThread().isInterrupted()) { try { diff --git a/src/test/perf/org/tarantool/MyBenchmark.java b/src/test/perf/org/tarantool/MyBenchmark.java index 89d3428d..0cbc3797 100644 --- a/src/test/perf/org/tarantool/MyBenchmark.java +++ b/src/test/perf/org/tarantool/MyBenchmark.java @@ -1,7 +1,10 @@ package org.tarantool; import org.openjdk.jmh.annotations.*; +import org.tarantool.server.TarantoolBinaryPackage; +import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.*; import java.util.concurrent.*; @@ -17,11 +20,37 @@ private DodgeSocketChannelProvider(Integer defaultSocketQueueSize) { } @Override - public SocketChannel getNext(int retryNumber, Throwable lastError) { + public SocketChannel get() { return new DodgeSocketChannel(defaultSocketQueueSize); } } + /** + * todo + */ + private static class DodgeCommunication implements NodeCommunicationProvider { + @Override + public void connect() throws IOException { + + } + + @Override + public TarantoolBinaryPackage readPackage() throws IOException { + return null; + } + + @Override + public void writeBuffer(ByteBuffer byteBuffer) throws IOException { + + } + + @Override + public String getDescription() { + return null; + } + } + + @State(Scope.Benchmark) public static class SpeedOfWriteAndReadState { @@ -31,7 +60,7 @@ public static class SpeedOfWriteAndReadState { public void init() { System.out.println("INIT BENCHMARK"); TarantoolClientConfig config = new TarantoolClientConfig(); - this.tarantoolClient = new TarantoolClientImpl(new DodgeSocketChannelProvider(10), config); + this.tarantoolClient = new TarantoolClientImpl(new DodgeCommunication(), config); } @TearDown(Level.Invocation) From 7379be8beba702f54b5fcfe02c8ae90bf0687ef6 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Wed, 27 Feb 2019 23:36:09 +0300 Subject: [PATCH 17/30] Implementing TarantoolClusterClient#refreshServerList method --- .../tarantool/NodeCommunicationProvider.java | 2 +- .../RoundRobinNodeCommunicationProvider.java | 47 +++++------ .../SingleNodeCommunicationProvider.java | 3 +- .../org/tarantool/TarantoolClientImpl.java | 48 +++++++---- .../org/tarantool/TarantoolClusterClient.java | 80 ++++++++++++++----- .../tarantool/server/BinaryProtoUtils.java | 4 +- .../server/TarantoolInstanceConnection.java | 13 ++- .../java/org/tarantool/ClientReconnectIT.java | 6 +- 8 files changed, 131 insertions(+), 72 deletions(-) diff --git a/src/main/java/org/tarantool/NodeCommunicationProvider.java b/src/main/java/org/tarantool/NodeCommunicationProvider.java index 7d3fbb9f..edabba0a 100644 --- a/src/main/java/org/tarantool/NodeCommunicationProvider.java +++ b/src/main/java/org/tarantool/NodeCommunicationProvider.java @@ -7,7 +7,7 @@ public interface NodeCommunicationProvider { - void connect() throws IOException; + TarantoolInstanceConnection connect() throws IOException; TarantoolBinaryPackage readPackage() throws IOException; diff --git a/src/main/java/org/tarantool/RoundRobinNodeCommunicationProvider.java b/src/main/java/org/tarantool/RoundRobinNodeCommunicationProvider.java index 350e0c7d..ff000117 100644 --- a/src/main/java/org/tarantool/RoundRobinNodeCommunicationProvider.java +++ b/src/main/java/org/tarantool/RoundRobinNodeCommunicationProvider.java @@ -1,13 +1,10 @@ package org.tarantool; -import org.tarantool.server.BinaryProtoUtils; -import org.tarantool.server.TarantoolBinaryPackage; -import org.tarantool.server.TarantoolInstanceConnection; -import org.tarantool.server.TarantoolInstanceInfo; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.SocketChannel; +import org.tarantool.server.*; + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; import java.util.*; import java.util.stream.*; @@ -20,9 +17,12 @@ public class RoundRobinNodeCommunicationProvider implements NodeCommunicationPro private int retriesLimit = -1; // No-limit. - private TarantoolInstanceInfo[] nodes; + private final String clusterUsername; + private final String clusterPassword; + private TarantoolInstanceConnection currentConnection; + private TarantoolInstanceInfo[] nodes; private int pos = 0; public RoundRobinNodeCommunicationProvider(String[] slaveHosts, String username, String password, int timeout) { @@ -31,28 +31,28 @@ public RoundRobinNodeCommunicationProvider(String[] slaveHosts, String username, throw new IllegalArgumentException("slave hosts is null ot empty"); } - setNodes(slaveHosts, username, password); + clusterUsername = username; + clusterPassword = password; + + setNodes(slaveHosts); } - private void setNodes(String[] slaveHosts, String username, String password) { - //todo add read-write lock - nodes = new TarantoolInstanceInfo[slaveHosts.length]; - for (int i = 0; i < slaveHosts.length; i++) { - String slaveHostAddress = slaveHosts[i]; - nodes[i] = TarantoolInstanceInfo.create(slaveHostAddress, username, password); + private void setNodes(String[] instanceAddresses) { + nodes = new TarantoolInstanceInfo[instanceAddresses.length]; + for (int i = 0; i < instanceAddresses.length; i++) { + String slaveHostAddress = instanceAddresses[i]; + nodes[i] = TarantoolInstanceInfo.create(slaveHostAddress, clusterUsername, clusterPassword); } pos = 0; } - public void updateNodes(List slaveHosts) { - if (slaveHosts == null) { - throw new IllegalArgumentException("slaveHosts can not be null"); + public void updateNodes(List instanceAddresses) { + if (instanceAddresses == null) { + throw new IllegalArgumentException("instanceAddresses can not be null"); } - //todo add read-write lock - - this.nodes = (TarantoolInstanceInfo[]) slaveHosts.toArray(); + this.nodes = (TarantoolInstanceInfo[]) instanceAddresses.toArray(); pos = 0; } @@ -119,8 +119,9 @@ protected int getAddressCount() { @Override - public void connect() { + public TarantoolInstanceConnection connect() { currentConnection = connectNextNode(); + return currentConnection; } public void writeBuffer(ByteBuffer byteBuffer) throws IOException { diff --git a/src/main/java/org/tarantool/SingleNodeCommunicationProvider.java b/src/main/java/org/tarantool/SingleNodeCommunicationProvider.java index 857f33d8..9997b683 100644 --- a/src/main/java/org/tarantool/SingleNodeCommunicationProvider.java +++ b/src/main/java/org/tarantool/SingleNodeCommunicationProvider.java @@ -24,8 +24,9 @@ public SingleNodeCommunicationProvider(String address, String username, String p } @Override - public void connect() throws IOException { + public TarantoolInstanceConnection connect() throws IOException { nodeConnection = TarantoolInstanceConnection.connect(tarantoolInstanceInfo); + return nodeConnection; } public void writeBuffer(ByteBuffer byteBuffer) throws IOException { diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index b3f6c435..1ba03cfb 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -45,6 +45,8 @@ public class TarantoolClientImpl extends TarantoolBase> implements Tar * Write properties */ protected SocketChannel channel; + protected TarantoolInstanceConnection currConnection; + protected ByteBuffer sharedBuffer; protected ByteBuffer writerBuffer; protected ReentrantLock writerBufferLock = new ReentrantLock(false); @@ -132,6 +134,17 @@ protected void reconnect(int retry, Throwable lastError) { } try { connect(communicationProvider); + + writerBufferLock.lock(); + try { + sharedBuffer.clear(); + } finally { + writerBufferLock.unlock(); + } + + this.thumbstone = null; + startThreads(communicationProvider.getDescription()); + return; } catch (Exception e) { closeChannel(channel); @@ -157,20 +170,10 @@ private boolean areRetriesExhausted(int retries) { protected void connect(final NodeCommunicationProvider communicationProvider) throws Exception { try { -// this.currentNodeInfo = BinaryProtoUtils.connect(channel, config.username, config.password); - communicationProvider.connect(); + currConnection = communicationProvider.connect(); } catch (IOException e) { throw new CommunicationException("Couldn't connect to tarantool", e); } - - writerBufferLock.lock(); - try { - sharedBuffer.clear(); - } finally { - writerBufferLock.unlock(); - } - this.thumbstone = null; - startThreads(communicationProvider.getDescription()); } protected void startThreads(String threadName) throws InterruptedException { @@ -279,12 +282,12 @@ public void ping() { protected void write(Code code, Long syncId, Long schemaId, Object... args) throws Exception { - ByteBuffer buffer = BinaryProtoUtils.createPacket(code, syncId, schemaId, args); + ByteBuffer msgBytes = BinaryProtoUtils.createPacket(code, syncId, schemaId, args); - if (directWrite(buffer)) { + if (directWrite(msgBytes)) { return; } - sharedWrite(buffer); + sharedWrite(msgBytes); } @@ -358,7 +361,7 @@ protected void readThread() { try { while (!Thread.currentThread().isInterrupted()) { try { - TarantoolBinaryPackage pack = communicationProvider.readPackage(); + TarantoolBinaryPackage pack = readFromInstance(); Map headers = pack.getHeaders(); @@ -378,6 +381,11 @@ protected void readThread() { } } + protected TarantoolBinaryPackage readFromInstance() throws IOException { +// return communicationProvider.readPackage(); + return BinaryProtoUtils.readPacket(currConnection.getChannel()); + } + protected void writeThread() { writerBuffer.clear(); while (!Thread.currentThread().isInterrupted()) { @@ -397,7 +405,8 @@ protected void writeThread() { writerBuffer.flip(); writeLock.lock(); try { - communicationProvider.writeBuffer(writerBuffer); + ByteBuffer writerBuffer = this.writerBuffer; + sendToInstance(writerBuffer); } finally { writeLock.unlock(); } @@ -410,6 +419,13 @@ protected void writeThread() { } } + + protected void sendToInstance(ByteBuffer writerBuffer) throws IOException { +// communicationProvider.writeBuffer(writerBuffer); + BinaryProtoUtils.writeFully(currConnection.getChannel(), writerBuffer); + } + + protected void fail(CompletableFuture q, Exception e) { q.completeExceptionally(e); } diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index e95adf0d..35bcba6e 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -4,6 +4,8 @@ import org.tarantool.cluster.ClusterTopologyFromShardDiscovererImpl; import org.tarantool.server.*; +import java.io.*; +import java.nio.channels.*; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -31,6 +33,13 @@ public class TarantoolClusterClient extends TarantoolClientImpl { private final TarantoolInstanceInfo infoHost; private final Integer infoHostConnectionTimeout; private final ClusterTopologyDiscoverer topologyDiscoverer; + + + + private TarantoolInstanceConnection instanceWithSentRequests; + private Selector readSelector; + private final Object readSyncObject = new Object(); + /** * @param config Configuration. */ @@ -66,32 +75,65 @@ private Collection refreshServerList(TarantoolInstanceInf List newServerList = topologyDiscoverer .discoverTarantoolNodes(infoNode, infoHostConnectionTimeout); - writeLock.lock(); -// todo add a read lock - try { - - RoundRobinNodeCommunicationProvider cp = (RoundRobinNodeCommunicationProvider) this.communicationProvider; + synchronized (readSyncObject) { + writeLock.lock(); + try { + + RoundRobinNodeCommunicationProvider cp = (RoundRobinNodeCommunicationProvider) this.communicationProvider; + + int sameNodeIndex = newServerList.indexOf(currConnection.getNodeInfo()); + if (sameNodeIndex != -1) { + //current node is in the list + Collections.swap(newServerList, 0, sameNodeIndex); + cp.updateNodes(newServerList); + } else { + cp.updateNodes(newServerList); + instanceWithSentRequests = currConnection; + die("The server list have been changed.", null); + } + + } finally { + writeLock.unlock(); + } + } -/* TarantoolInstanceInfo currentNode = cp.getCurrentNode(); + return newServerList; + } - int sameNodeIndex = newServerList.indexOf(currentNode); - if (sameNodeIndex != -1) { - Collections.swap(newServerList, 0, sameNodeIndex); - cp.setNodes(newServerList); - } else { - cp.setNodes(newServerList); - die("The server list have been changed.", null); - //todo - }*/ + @Override + protected void connect(NodeCommunicationProvider communicationProvider) throws Exception { + //region drop all registered selectors from channel + if (currConnection != null) { + SelectionKey registeredKey = currConnection.getChannel().keyFor(readSelector); + if (registeredKey != null) { + registeredKey.cancel(); + } + } + //endregion - cp.updateNodes(newServerList); + super.connect(communicationProvider); + //region reregister selector + readSelector = Selector.open(); - } finally { - writeLock.unlock(); + if (instanceWithSentRequests != null) { + SelectionKey register = instanceWithSentRequests.getChannel().register(readSelector, SelectionKey.OP_READ); } - return newServerList; + currConnection.getChannel().register(readSelector, SelectionKey.OP_READ); + //endregion + } + + @Override + protected TarantoolBinaryPackage readFromInstance() throws IOException { + synchronized (readSyncObject) { + // return communicationProvider.readPackage(); + readSelector.select(); + SelectionKey selectedKey = readSelector.selectedKeys().iterator().next(); + + //todo обработать случай, когда прочитали последний пакет из instanceWithSentRequests инстанса + return BinaryProtoUtils.readPacket(((SocketChannel) selectedKey.channel())); + } } @Override diff --git a/src/main/java/org/tarantool/server/BinaryProtoUtils.java b/src/main/java/org/tarantool/server/BinaryProtoUtils.java index 757d02ac..f3130fa5 100644 --- a/src/main/java/org/tarantool/server/BinaryProtoUtils.java +++ b/src/main/java/org/tarantool/server/BinaryProtoUtils.java @@ -144,9 +144,9 @@ public static void writeFully(SocketChannel channel, ByteBuffer buffer) throws I public static final int LENGTH_OF_SIZE_MESSAGE = 5; public static TarantoolBinaryPackage readPacket(SocketChannel channel) throws IOException { - channel.configureBlocking(false); - //todo getNext rid of this because SelectorProvider.provider().openSelector() creates two pipes and socket in the /proc/fd + + //todo get rid of this because SelectorProvider.provider().openSelector() creates two pipes and socket in the /proc/fd SelectorChannelReadHelper bufferReader = new SelectorChannelReadHelper(channel); ByteBuffer buffer = ByteBuffer.allocate(LENGTH_OF_SIZE_MESSAGE); diff --git a/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java b/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java index a8e08226..375805bd 100644 --- a/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java +++ b/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java @@ -34,8 +34,9 @@ private TarantoolInstanceConnection(TarantoolInstanceInfo nodeInfo, /** * Attempts to connect to a tarantool instance and create correspond TarantoolInstanceConnection * - * @param tarantoolInstanceInfo - * @throws CommunicationException + * @param tarantoolInstanceInfo information about an instance to connect + * @throws CommunicationException if an error occurred during connection related exchanges + * @throws IOException in case of any IO fail */ public static TarantoolInstanceConnection connect(TarantoolInstanceInfo tarantoolInstanceInfo) throws IOException { SocketChannel channel; @@ -65,12 +66,10 @@ public SocketChannel getChannel() { } private void closeConnection() { - if (channel != null) { - try { - channel.close(); - } catch (IOException ignored) { + try { + channel.close(); + } catch (IOException ignored) { - } } } diff --git a/src/test/java/org/tarantool/ClientReconnectIT.java b/src/test/java/org/tarantool/ClientReconnectIT.java index 1dc0cc1a..6c269630 100644 --- a/src/test/java/org/tarantool/ClientReconnectIT.java +++ b/src/test/java/org/tarantool/ClientReconnectIT.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; -import org.tarantool.server.TarantoolBinaryPackage; +import org.tarantool.server.*; import java.io.IOException; import java.nio.ByteBuffer; @@ -94,9 +94,9 @@ public void testSpuriousReturnFromPark() { NodeCommunicationProvider nodeCommunicationProvider = new NodeCommunicationProvider() { @Override - public void connect() throws IOException { + public TarantoolInstanceConnection connect() throws IOException { latch.countDown(); - testNodeCommunicationProvider.connect(); + return testNodeCommunicationProvider.connect(); } @Override From 9bf51b74e3657a313bce35f5dd599f4cf77fccc7 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Thu, 28 Feb 2019 12:07:57 +0300 Subject: [PATCH 18/30] Make connector compilable --- .../java/org/tarantool/TestNodeCommunicationProvider.java | 7 ++++--- src/test/perf/org/tarantool/MyBenchmark.java | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/tarantool/TestNodeCommunicationProvider.java b/src/test/java/org/tarantool/TestNodeCommunicationProvider.java index e8d71809..35c16d0a 100644 --- a/src/test/java/org/tarantool/TestNodeCommunicationProvider.java +++ b/src/test/java/org/tarantool/TestNodeCommunicationProvider.java @@ -1,5 +1,7 @@ package org.tarantool; +import org.tarantool.server.TarantoolInstanceConnection; + import java.io.IOException; public class TestNodeCommunicationProvider extends SingleNodeCommunicationProvider { @@ -12,13 +14,12 @@ public TestNodeCommunicationProvider(String address, String username, String pas } @Override - public void connect() throws IOException { + public TarantoolInstanceConnection connect() throws IOException { long budget = System.currentTimeMillis() + restartTimeout; while (!Thread.currentThread().isInterrupted()) { try { - super.connect(); - return; + return super.connect(); } catch (Exception e) { if (budget < System.currentTimeMillis()) throw new RuntimeException(e); diff --git a/src/test/perf/org/tarantool/MyBenchmark.java b/src/test/perf/org/tarantool/MyBenchmark.java index 0cbc3797..3e37df61 100644 --- a/src/test/perf/org/tarantool/MyBenchmark.java +++ b/src/test/perf/org/tarantool/MyBenchmark.java @@ -2,6 +2,7 @@ import org.openjdk.jmh.annotations.*; import org.tarantool.server.TarantoolBinaryPackage; +import org.tarantool.server.TarantoolInstanceConnection; import java.io.IOException; import java.nio.ByteBuffer; @@ -30,8 +31,8 @@ public SocketChannel get() { */ private static class DodgeCommunication implements NodeCommunicationProvider { @Override - public void connect() throws IOException { - + public TarantoolInstanceConnection connect() throws IOException { + return null; } @Override From 8ddadda7dd3a9852e06f3c4d65765b907fef4bb7 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Mon, 4 Mar 2019 03:20:39 +0300 Subject: [PATCH 19/30] Try to resolve race-conditions at readFromInstance and refreshServerList methods in TarantoolClusterClient --- .../org/tarantool/TarantoolClientImpl.java | 53 ++++--- .../org/tarantool/TarantoolClusterClient.java | 143 +++++++++++++----- .../cluster/ClusterTopologyDiscoverer.java | 2 +- ...lusterTopologyFromShardDiscovererImpl.java | 4 +- .../tarantool/server/BinaryProtoUtils.java | 29 +++- ...r.java => ReadableViaSelectorChannel.java} | 26 +++- .../server/TarantoolBinaryPackage.java | 4 + .../server/TarantoolInstanceConnection.java | 17 ++- ...erTopologyFromShardDiscovererImplTest.java | 2 +- 9 files changed, 202 insertions(+), 78 deletions(-) rename src/main/java/org/tarantool/server/{SelectorChannelReadHelper.java => ReadableViaSelectorChannel.java} (63%) diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index 1ba03cfb..0382305e 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -89,8 +89,16 @@ public TarantoolClientImpl(String address, TarantoolClientConfig config) { this(new SingleNodeCommunicationProvider(address, config.username, config.password), config); } + protected TarantoolClientImpl() { +// delegate init to a descendant + } + public TarantoolClientImpl(NodeCommunicationProvider communicationProvider, TarantoolClientConfig config) { super(); + init(communicationProvider, config); + } + + protected void init(NodeCommunicationProvider communicationProvider, TarantoolClientConfig config) { this.thumbstone = NOT_INIT_EXCEPTION; this.config = config; this.initialRequestSize = config.defaultRequestSize; @@ -99,8 +107,6 @@ public TarantoolClientImpl(NodeCommunicationProvider communicationProvider, Tara this.futures = new ConcurrentHashMap<>(config.predictedFutures); this.sharedBuffer = ByteBuffer.allocateDirect(config.sharedBufferSize); this.writerBuffer = ByteBuffer.allocateDirect(sharedBuffer.capacity()); - this.connector.setDaemon(true); - this.connector.setName("Tarantool connector"); this.syncOps = new SyncOps(); this.composableAsyncOps = new ComposableAsyncOps(); this.fireAndForgetOps = new FireAndForgetOps(); @@ -110,6 +116,9 @@ public TarantoolClientImpl(NodeCommunicationProvider communicationProvider, Tara this.fireAndForgetOps.setCallCode(Code.CALL); this.composableAsyncOps.setCallCode(Code.CALL); } + + this.connector.setDaemon(true); + this.connector.setName("Tarantool connector"); connector.start(); try { if (!waitAlive(config.initTimeoutMillis, TimeUnit.MILLISECONDS)) { @@ -133,18 +142,7 @@ protected void reconnect(int retry, Throwable lastError) { close(new CommunicationException("Connection retries exceeded.", cause)); } try { - connect(communicationProvider); - - writerBufferLock.lock(); - try { - sharedBuffer.clear(); - } finally { - writerBufferLock.unlock(); - } - - this.thumbstone = null; - startThreads(communicationProvider.getDescription()); - + connectAndStartThreads(); return; } catch (Exception e) { closeChannel(channel); @@ -155,6 +153,20 @@ protected void reconnect(int retry, Throwable lastError) { } } + protected void connectAndStartThreads() throws Exception { + connect(communicationProvider); + + writerBufferLock.lock(); + try { + sharedBuffer.clear(); + } finally { + writerBufferLock.unlock(); + } + + this.thumbstone = null; + startThreads(communicationProvider.getDescription()); + } + /** * Provides a decision on whether retries limit is hit. * @@ -334,7 +346,7 @@ private boolean directWrite(ByteBuffer buffer) throws InterruptedException, IOEx if (rem > initialRequestSize) { stats.directPacketSizeGrowth++; } - BinaryProtoUtils.writeFully(getReadChannel(), buffer); + sendToInstance(buffer); stats.directWrite++; wait.incrementAndGet(); } finally { @@ -363,10 +375,7 @@ protected void readThread() { try { TarantoolBinaryPackage pack = readFromInstance(); - Map headers = pack.getHeaders(); - - Long syncId = (Long) headers.get(Key.SYNC.getId()); - CompletableFuture future = futures.remove(syncId); + CompletableFuture future = futures.remove(pack.getSync()); stats.received++; wait.decrementAndGet(); @@ -381,7 +390,7 @@ protected void readThread() { } } - protected TarantoolBinaryPackage readFromInstance() throws IOException { + protected TarantoolBinaryPackage readFromInstance() throws IOException, InterruptedException { // return communicationProvider.readPackage(); return BinaryProtoUtils.readPacket(currConnection.getChannel()); } @@ -665,6 +674,10 @@ protected int getState() { return state.get(); } + protected boolean isAtState(int state) { + return getState() == state; + } + protected boolean close() { for (;;) { int st = getState(); diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index 35bcba6e..67b11d4c 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -11,6 +11,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; import static org.tarantool.TarantoolClientImpl.StateHelper.CLOSED; @@ -28,17 +31,20 @@ public class TarantoolClusterClient extends TarantoolClientImpl { /* Collection of operations to be retried. */ private ConcurrentHashMap> retries = new ConcurrentHashMap>(); + @Deprecated private final Collection slaveHosts; - private final TarantoolInstanceInfo infoHost; - private final Integer infoHostConnectionTimeout; - private final ClusterTopologyDiscoverer topologyDiscoverer; + private TarantoolInstanceInfo infoHost; + private Integer infoHostConnectionTimeout; + private ClusterTopologyDiscoverer topologyDiscoverer; + private List newServerList = null; + private TarantoolInstanceConnection oldConnection; + private ConcurrentHashMap> futuresSentToOldConnection = new ConcurrentHashMap<>(); + private ReentrantLock initLock = new ReentrantLock(); - private TarantoolInstanceConnection instanceWithSentRequests; private Selector readSelector; - private final Object readSyncObject = new Object(); /** * @param config Configuration. @@ -54,16 +60,28 @@ public TarantoolClusterClient(TarantoolClusterClientConfig config) { * @param config Configuration. */ public TarantoolClusterClient(TarantoolClusterClientConfig config, NodeCommunicationProvider provider) { - super(provider, config); + init(provider, config); this.executor = config.executor == null ? Executors.newSingleThreadExecutor() : config.executor; - this.infoHost = TarantoolInstanceInfo.create(config.infoHost, config.username, config.password); - this.infoHostConnectionTimeout = config.infoHostConnectionTimeout; - this.topologyDiscoverer = new ClusterTopologyFromShardDiscovererImpl(config); + if (config.infoHost != null) { + this.infoHost = TarantoolInstanceInfo.create(config.infoHost, config.username, config.password); + + this.infoHostConnectionTimeout = config.infoHostConnectionTimeout; + this.topologyDiscoverer = new ClusterTopologyFromShardDiscovererImpl(config); + + slaveHosts = topologyDiscoverer.discoverTarantoolInstances(this.infoHost, infoHostConnectionTimeout); + } else { + if (config.slaveHosts == null || config.slaveHosts.length == 0) { + throw new IllegalArgumentException("Either slaveHosts or infoHost must be specified."); + } + + slaveHosts = Arrays.stream(config.slaveHosts) + .map(s -> TarantoolInstanceInfo.create(s, config.username, config.password)) + .collect(Collectors.toList()); + } - slaveHosts = topologyDiscoverer.discoverTarantoolNodes(this.infoHost, infoHostConnectionTimeout); } /** @@ -73,30 +91,45 @@ public TarantoolClusterClient(TarantoolClusterClientConfig config, NodeCommunica */ private Collection refreshServerList(TarantoolInstanceInfo infoNode) { List newServerList = topologyDiscoverer - .discoverTarantoolNodes(infoNode, infoHostConnectionTimeout); + .discoverTarantoolInstances(infoNode, infoHostConnectionTimeout); - synchronized (readSyncObject) { - writeLock.lock(); - try { - RoundRobinNodeCommunicationProvider cp = (RoundRobinNodeCommunicationProvider) this.communicationProvider; - - int sameNodeIndex = newServerList.indexOf(currConnection.getNodeInfo()); - if (sameNodeIndex != -1) { - //current node is in the list - Collections.swap(newServerList, 0, sameNodeIndex); - cp.updateNodes(newServerList); - } else { - cp.updateNodes(newServerList); - instanceWithSentRequests = currConnection; - die("The server list have been changed.", null); - } - - } finally { - writeLock.unlock(); + initLock.lock(); + try { + + RoundRobinNodeCommunicationProvider cp = (RoundRobinNodeCommunicationProvider) this.communicationProvider; + + int sameNodeIndex = newServerList.indexOf(currConnection.getNodeInfo()); + if (sameNodeIndex != -1) { + //current node is in the list + Collections.swap(newServerList, 0, sameNodeIndex); + cp.updateNodes(newServerList); + } else { + + + cp.updateNodes(newServerList); + + //todo move to "connect" method + futuresSentToOldConnection.values() + .forEach(f -> { + f.completeExceptionally(new CommunicationException("Connection is dead")); + }); + futuresSentToOldConnection.putAll(futures); + futures.clear(); + + oldConnection = currConnection; + + + this.newServerList = newServerList; + + //do reconnect to update list + die("The server list have been changed.", null); } + } finally { + initLock.unlock(); } + return newServerList; } @@ -114,25 +147,44 @@ protected void connect(NodeCommunicationProvider communicationProvider) throws E super.connect(communicationProvider); //region reregister selector + if (readSelector != null) { + try { + readSelector.close(); + } catch (IOException ignored) { + } + } readSelector = Selector.open(); - if (instanceWithSentRequests != null) { - SelectionKey register = instanceWithSentRequests.getChannel().register(readSelector, SelectionKey.OP_READ); + if (oldConnection != null) { + SelectionKey register = oldConnection.getChannel(). + register(readSelector, SelectionKey.OP_READ, currConnection); } - currConnection.getChannel().register(readSelector, SelectionKey.OP_READ); + currConnection.getChannel().register(readSelector, SelectionKey.OP_READ, currConnection); //endregion } @Override - protected TarantoolBinaryPackage readFromInstance() throws IOException { - synchronized (readSyncObject) { - // return communicationProvider.readPackage(); - readSelector.select(); - SelectionKey selectedKey = readSelector.selectedKeys().iterator().next(); - - //todo обработать случай, когда прочитали последний пакет из instanceWithSentRequests инстанса - return BinaryProtoUtils.readPacket(((SocketChannel) selectedKey.channel())); + protected TarantoolBinaryPackage readFromInstance() throws IOException, InterruptedException { + + readSelector.select(); + + SelectionKey selectedKey = readSelector.selectedKeys().iterator().next(); + + TarantoolInstanceConnection connection = (TarantoolInstanceConnection) selectedKey.attachment(); + ReadableByteChannel readChannel = connection + .getReadChannel(); + + //todo обработать случай, когда прочитали последний пакет из oldConnection инстанса + return BinaryProtoUtils.readPacket(readChannel); + } + + private void closeOldInstanceIfNeeded() { + if (futuresSentToOldConnection.isEmpty()) { + try { + oldConnection.close(); + } catch (IOException ignored) { + } } } @@ -237,6 +289,7 @@ protected void onReconnect() { public void run() { futures.put(fut.getId(), fut); try { + //todo invoke sendToInstance method? (maybe not) write(fut.getCode(), fut.getId(), null, fut.getArgs()); } catch (Exception e) { futures.remove(fut.getId()); @@ -248,6 +301,16 @@ public void run() { } } + @Override + protected void connectAndStartThreads() throws Exception { + initLock.lock(); + try { + super.connectAndStartThreads(); + } finally { + initLock.unlock(); + } + } + /** * Holds operation code and arguments for retry. */ diff --git a/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java b/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java index 6931fe34..44fc8b16 100644 --- a/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java +++ b/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java @@ -5,5 +5,5 @@ import java.util.List; public interface ClusterTopologyDiscoverer { - List discoverTarantoolNodes(TarantoolInstanceInfo infoNode, Integer infoHostConnectionTimeout); + List discoverTarantoolInstances(TarantoolInstanceInfo infoNode, Integer infoHostConnectionTimeout); } diff --git a/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java b/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java index b75850d7..6d097789 100644 --- a/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java +++ b/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java @@ -19,8 +19,8 @@ public ClusterTopologyFromShardDiscovererImpl(TarantoolClusterClientConfig clien } @Override - public List discoverTarantoolNodes(TarantoolInstanceInfo infoNode, - Integer infoHostConnectionTimeout) { + public List discoverTarantoolInstances(TarantoolInstanceInfo infoNode, + Integer infoHostConnectionTimeout) { List list = new TarantoolClientImpl(infoNode.getSocketAddress(), clientConfig) .syncOps() diff --git a/src/main/java/org/tarantool/server/BinaryProtoUtils.java b/src/main/java/org/tarantool/server/BinaryProtoUtils.java index f3130fa5..cbe1b35c 100644 --- a/src/main/java/org/tarantool/server/BinaryProtoUtils.java +++ b/src/main/java/org/tarantool/server/BinaryProtoUtils.java @@ -14,6 +14,7 @@ import java.net.Socket; import java.net.SocketException; import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; import java.nio.channels.SocketChannel; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -91,7 +92,7 @@ public static TarantoolInstanceConnectionMeta connect(Socket socket, String user /** * Connects to a tarantool node described by {@code socketChannel}. Performs an authentication if required * - * @param channel a socket channel to tarantool node + * @param channel a socket channel to tarantool node. The channel have to be in blocking mode * @param username auth username * @param password auth password * @return object with information about a connection/ @@ -117,7 +118,7 @@ public static TarantoolInstanceConnectionMeta connect(SocketChannel channel, Str if (username != null && password != null) { ByteBuffer authPacket = createAuthPacket(username, password, salt); writeFully(channel, authPacket); - TarantoolBinaryPackage authResponse = readPacket(channel); + TarantoolBinaryPackage authResponse = readPacket(((ReadableByteChannel) channel)); Long code = (Long) authResponse.getHeaders().get(Key.CODE.getId()); if (code != 0) { Object error = authResponse.getBody().get(Key.ERROR.getId()); @@ -143,20 +144,28 @@ public static void writeFully(SocketChannel channel, ByteBuffer buffer) throws I } public static final int LENGTH_OF_SIZE_MESSAGE = 5; - public static TarantoolBinaryPackage readPacket(SocketChannel channel) throws IOException { - channel.configureBlocking(false); + /** + * Reads a tarantool's binary protocol package from the reader + * + * @param bufferReader readable channel that have to be in blocking mode + * @return + * @throws IOException + * @throws + * @throws java.nio.channels.NonReadableChannelException – If this channel was not opened for reading + */ + public static TarantoolBinaryPackage readPacket(ReadableByteChannel bufferReader) + throws CommunicationException, IOException { //todo get rid of this because SelectorProvider.provider().openSelector() creates two pipes and socket in the /proc/fd - SelectorChannelReadHelper bufferReader = new SelectorChannelReadHelper(channel); ByteBuffer buffer = ByteBuffer.allocate(LENGTH_OF_SIZE_MESSAGE); - bufferReader.readFully(buffer); + bufferReader.read(buffer); buffer.flip(); int size = ((Number) getMsgPackLite().unpack(new ByteBufferBackedInputStream(buffer))).intValue(); buffer = ByteBuffer.allocate(size); - bufferReader.readFully(buffer); + bufferReader.read(buffer); buffer.flip(); ByteBufferBackedInputStream msgBytesStream = new ByteBufferBackedInputStream(buffer); @@ -184,6 +193,12 @@ public static TarantoolBinaryPackage readPacket(SocketChannel channel) throws IO return new TarantoolBinaryPackage(headers, body); } + @Deprecated + public static TarantoolBinaryPackage readPacket(SocketChannel channel) throws IOException { + ReadableViaSelectorChannel bufferReader = new ReadableViaSelectorChannel(channel); + return readPacket(bufferReader); + } + @Deprecated public static TarantoolBinaryPackage readPacketOld(SocketChannel channel) throws IOException { CountInputStream inputStream = new ByteBufferInputStream(channel); diff --git a/src/main/java/org/tarantool/server/SelectorChannelReadHelper.java b/src/main/java/org/tarantool/server/ReadableViaSelectorChannel.java similarity index 63% rename from src/main/java/org/tarantool/server/SelectorChannelReadHelper.java rename to src/main/java/org/tarantool/server/ReadableViaSelectorChannel.java index 4de5581b..cae17899 100644 --- a/src/main/java/org/tarantool/server/SelectorChannelReadHelper.java +++ b/src/main/java/org/tarantool/server/ReadableViaSelectorChannel.java @@ -4,18 +4,19 @@ import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; -class SelectorChannelReadHelper { +class ReadableViaSelectorChannel implements ReadableByteChannel { private final SocketChannel channel; private final Selector selector; - public SelectorChannelReadHelper(SocketChannel channel) throws IOException { + public ReadableViaSelectorChannel(SocketChannel channel) throws IOException { if (channel.isBlocking()) { - throw new IllegalArgumentException("Channel have to be blocking"); + throw new IllegalArgumentException("Channel have to be non-blocking"); } this.channel = channel; @@ -23,8 +24,11 @@ public SelectorChannelReadHelper(SocketChannel channel) throws IOException { channel.register(selector, SelectionKey.OP_READ); } - public void readFully(ByteBuffer buffer) throws IOException { - int n = channel.read(buffer); + @Override + public int read(ByteBuffer buffer) throws IOException { + int count, n; + count = n = channel.read(buffer); + if (n < 0) { throw new CommunicationException("Channel read failed " + n); } @@ -35,7 +39,19 @@ public void readFully(ByteBuffer buffer) throws IOException { if (n < 0) { throw new CommunicationException("Channel read failed " + n); } + count += n; } + return count; + } + + @Override + public boolean isOpen() { + return channel.isOpen(); } + @Override + public void close() throws IOException { + selector.close(); + channel.close(); + } } diff --git a/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java b/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java index 8fbaf263..8b52816b 100644 --- a/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java +++ b/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java @@ -32,6 +32,10 @@ public Long getCode() { return (Long) potenticalCode; } + public Long getSync() { + return (Long) getHeaders().get(Key.SYNC.getId()); + } + public Map getHeaders() { return headers; } diff --git a/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java b/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java index 375805bd..6dded241 100644 --- a/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java +++ b/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java @@ -4,6 +4,7 @@ import java.io.Closeable; import java.io.IOException; +import java.nio.channels.ReadableByteChannel; import java.nio.channels.SocketChannel; public class TarantoolInstanceConnection implements Closeable { @@ -23,12 +24,18 @@ public class TarantoolInstanceConnection implements Closeable { */ protected final SocketChannel channel; + /** + * Nonnull connection to a tarantool instance + */ + protected final ReadableViaSelectorChannel readChannel; + private TarantoolInstanceConnection(TarantoolInstanceInfo nodeInfo, TarantoolInstanceConnectionMeta meta, - SocketChannel channel) { + SocketChannel channel) throws IOException { this.nodeInfo = nodeInfo; this.meta = meta; this.channel = channel; + this.readChannel = new ReadableViaSelectorChannel(channel); } /** @@ -42,11 +49,13 @@ public static TarantoolInstanceConnection connect(TarantoolInstanceInfo tarantoo SocketChannel channel; try { channel = SocketChannel.open(tarantoolInstanceInfo.getSocketAddress()); + String username = tarantoolInstanceInfo.getUsername(); String password = tarantoolInstanceInfo.getPassword(); - TarantoolInstanceConnectionMeta meta = BinaryProtoUtils.connect(channel, username, password); + channel.configureBlocking(false); + return new TarantoolInstanceConnection(tarantoolInstanceInfo, meta, channel); } catch (IOException e) { throw new IOException("IOException occurred while connecting to node " + tarantoolInstanceInfo, e); @@ -65,6 +74,10 @@ public SocketChannel getChannel() { return channel; } + public ReadableByteChannel getReadChannel() { + return readChannel; + } + private void closeConnection() { try { channel.close(); diff --git a/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java b/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java index fc240994..062de278 100644 --- a/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java +++ b/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java @@ -20,7 +20,7 @@ void testListFetching() { // Collection tarantoolNodes = // new ClusterTopologyFromShardDiscovererImpl(clientConfig) -// .discoverTarantoolNodes(tarantoolInstanceInfo, 5000); +// .discoverTarantoolInstances(tarantoolInstanceInfo, 5000); int i = 0; } } \ No newline at end of file From 4aa08524962ac6e8bd8b6f93f8f4850918241264 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Mon, 4 Mar 2019 22:31:50 +0300 Subject: [PATCH 20/30] Upgrade refreshServerList method: stop IO before process old connection --- .../java/org/tarantool/TarantoolBase.java | 7 +- .../org/tarantool/TarantoolClientImpl.java | 10 ++- .../org/tarantool/TarantoolClusterClient.java | 73 +++++++++++-------- 3 files changed, 53 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/tarantool/TarantoolBase.java b/src/main/java/org/tarantool/TarantoolBase.java index 23a160d1..fc77f7d2 100644 --- a/src/main/java/org/tarantool/TarantoolBase.java +++ b/src/main/java/org/tarantool/TarantoolBase.java @@ -1,6 +1,7 @@ package org.tarantool; import org.tarantool.server.BinaryProtoUtils; +import org.tarantool.server.TarantoolInstanceConnection; import org.tarantool.server.TarantoolInstanceConnectionMeta; import java.io.IOException; @@ -68,10 +69,10 @@ ByteBuffer toByteBuffer() { } } - protected void closeChannel(SocketChannel channel) { - if (channel != null) { + protected void closeChannel(TarantoolInstanceConnection connection) { + if (connection != null) { try { - channel.close(); + connection.close(); } catch (IOException ignored) { } diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index 0382305e..fddb6525 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -145,7 +145,7 @@ protected void reconnect(int retry, Throwable lastError) { connectAndStartThreads(); return; } catch (Exception e) { - closeChannel(channel); + closeChannel(currConnection); lastError = e; if (e instanceof InterruptedException) Thread.currentThread().interrupt(); @@ -375,7 +375,7 @@ protected void readThread() { try { TarantoolBinaryPackage pack = readFromInstance(); - CompletableFuture future = futures.remove(pack.getSync()); + CompletableFuture future = getFuture(pack); stats.received++; wait.decrementAndGet(); @@ -390,6 +390,10 @@ protected void readThread() { } } + protected CompletableFuture getFuture(TarantoolBinaryPackage pack) { + return futures.remove(pack.getSync()); + } + protected TarantoolBinaryPackage readFromInstance() throws IOException, InterruptedException { // return communicationProvider.readPackage(); return BinaryProtoUtils.readPacket(currConnection.getChannel()); @@ -508,7 +512,7 @@ protected void stopIO() { if (writer != null) { writer.interrupt(); } - closeChannel(channel); + closeChannel(currConnection); } @Override diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index 67b11d4c..3189bb53 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -11,7 +11,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; @@ -39,7 +39,6 @@ public class TarantoolClusterClient extends TarantoolClientImpl { private ClusterTopologyDiscoverer topologyDiscoverer; - private List newServerList = null; private TarantoolInstanceConnection oldConnection; private ConcurrentHashMap> futuresSentToOldConnection = new ConcurrentHashMap<>(); private ReentrantLock initLock = new ReentrantLock(); @@ -109,21 +108,27 @@ private Collection refreshServerList(TarantoolInstanceInf cp.updateNodes(newServerList); - //todo move to "connect" method - futuresSentToOldConnection.values() - .forEach(f -> { - f.completeExceptionally(new CommunicationException("Connection is dead")); - }); - futuresSentToOldConnection.putAll(futures); - futures.clear(); - oldConnection = currConnection; + if (state.isAtState(StateHelper.ALIVE)) { + stopIO(); + + futuresSentToOldConnection.values() + .forEach(f -> { + f.completeExceptionally(new CommunicationException("Connection is dead")); + }); + futuresSentToOldConnection.putAll(futures); + futures.clear(); + + LockSupport.unpark(connector); - this.newServerList = newServerList; + //remove +// die("The server list have been changed.", null); + } + // if not alive, then do nothing because we are closed or on RECONNECT state. + // in the last case reconnect thread will wait untill the initLock will be unlocked - //do reconnect to update list - die("The server list have been changed.", null); + oldConnection = currConnection; } } finally { initLock.unlock(); @@ -136,28 +141,30 @@ private Collection refreshServerList(TarantoolInstanceInf @Override protected void connect(NodeCommunicationProvider communicationProvider) throws Exception { //region drop all registered selectors from channel - if (currConnection != null) { - SelectionKey registeredKey = currConnection.getChannel().keyFor(readSelector); - if (registeredKey != null) { - registeredKey.cancel(); - } - } - //endregion - - super.connect(communicationProvider); +// if (currConnection != null) { +// SelectionKey registeredKey = currConnection.getChannel().keyFor(readSelector); +// if (registeredKey != null) { +// registeredKey.cancel(); +// } +// } - //region reregister selector if (readSelector != null) { try { readSelector.close(); } catch (IOException ignored) { } } + //endregion + + //set a new value to currConnection variable + super.connect(communicationProvider); + + //region reregister selector + readSelector = Selector.open(); if (oldConnection != null) { - SelectionKey register = oldConnection.getChannel(). - register(readSelector, SelectionKey.OP_READ, currConnection); + oldConnection.getChannel().register(readSelector, SelectionKey.OP_READ, currConnection); } currConnection.getChannel().register(readSelector, SelectionKey.OP_READ, currConnection); @@ -179,13 +186,17 @@ protected TarantoolBinaryPackage readFromInstance() throws IOException, Interrup return BinaryProtoUtils.readPacket(readChannel); } - private void closeOldInstanceIfNeeded() { - if (futuresSentToOldConnection.isEmpty()) { - try { - oldConnection.close(); - } catch (IOException ignored) { + @Override + protected CompletableFuture getFuture(TarantoolBinaryPackage pack) { + Long sync = pack.getSync(); + if (!futuresSentToOldConnection.isEmpty()) { + CompletableFuture oldConnectionFuture = futuresSentToOldConnection.remove(sync); + if (oldConnectionFuture != null) { + futuresSentToOldConnection.entrySet().remo } + } + return futures.remove(sync); } @Override @@ -235,7 +246,7 @@ protected boolean checkFail(CompletableFuture q, Exception e) { q.completeExceptionally(e); return true; } else { - assert retries != null; + retries.put(((ExpirableOp) q).getId(), (ExpirableOp)q); return false; } From 57cbc7c6e0709ccf9052e967c646d14b3dabfb60 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Tue, 5 Mar 2019 03:15:39 +0300 Subject: [PATCH 21/30] IMplement cleaning futuresSentToOldConnection collection --- .../org/tarantool/TarantoolClusterClient.java | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index 3189bb53..f2477f7f 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -39,8 +39,8 @@ public class TarantoolClusterClient extends TarantoolClientImpl { private ClusterTopologyDiscoverer topologyDiscoverer; - private TarantoolInstanceConnection oldConnection; - private ConcurrentHashMap> futuresSentToOldConnection = new ConcurrentHashMap<>(); + private volatile TarantoolInstanceConnection oldConnection; + private ConcurrentHashMap> futuresSentToOldConnection = new ConcurrentHashMap<>(); private ReentrantLock initLock = new ReentrantLock(); private Selector readSelector; @@ -117,7 +117,7 @@ private Collection refreshServerList(TarantoolInstanceInf .forEach(f -> { f.completeExceptionally(new CommunicationException("Connection is dead")); }); - futuresSentToOldConnection.putAll(futures); + futuresSentToOldConnection.putAll((Map>) futures); futures.clear(); LockSupport.unpark(connector); @@ -182,7 +182,6 @@ protected TarantoolBinaryPackage readFromInstance() throws IOException, Interrup ReadableByteChannel readChannel = connection .getReadChannel(); - //todo обработать случай, когда прочитали последний пакет из oldConnection инстанса return BinaryProtoUtils.readPacket(readChannel); } @@ -192,7 +191,25 @@ protected CompletableFuture getFuture(TarantoolBinaryPackage pack) { if (!futuresSentToOldConnection.isEmpty()) { CompletableFuture oldConnectionFuture = futuresSentToOldConnection.remove(sync); if (oldConnectionFuture != null) { - futuresSentToOldConnection.entrySet().remo + long now = System.currentTimeMillis(); + futuresSentToOldConnection.entrySet() + .removeIf(entry -> { + ExpirableOp expirableOp = entry.getValue(); + if (expirableOp.hasExpired(now)) { + expirableOp.completeExceptionally( + new CommunicationException("Operation timeout is expired")); + return true; + } else { + return false; + } + }); + if (futuresSentToOldConnection.isEmpty()) { + try { + oldConnection.close(); + } catch (IOException e) { + } + oldConnection = null; + } } } From 3a3257489a92d288a9f18f8dcfa3c54d74737f73 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Thu, 7 Mar 2019 02:31:48 +0300 Subject: [PATCH 22/30] Pass Async/Sync tarantool client test. Cleanup code from todos and deprecated code --- .../tarantool/NodeCommunicationProvider.java | 4 -- .../RoundRobinNodeCommunicationProvider.java | 29 ++++--------- .../SingleNodeCommunicationProvider.java | 28 ++----------- .../org/tarantool/TarantoolClientImpl.java | 18 ++++---- .../org/tarantool/TarantoolClusterClient.java | 41 +++++++++---------- .../tarantool/server/BinaryProtoUtils.java | 15 +------ .../server/ReadableViaSelectorChannel.java | 2 +- .../server/TarantoolInstanceConnection.java | 2 +- .../AbstractTarantoolConnectorIT.java | 2 +- .../java/org/tarantool/ClientReconnectIT.java | 10 ----- src/test/perf/org/tarantool/MyBenchmark.java | 10 ----- 11 files changed, 44 insertions(+), 117 deletions(-) diff --git a/src/main/java/org/tarantool/NodeCommunicationProvider.java b/src/main/java/org/tarantool/NodeCommunicationProvider.java index edabba0a..c1168269 100644 --- a/src/main/java/org/tarantool/NodeCommunicationProvider.java +++ b/src/main/java/org/tarantool/NodeCommunicationProvider.java @@ -9,9 +9,5 @@ public interface NodeCommunicationProvider { TarantoolInstanceConnection connect() throws IOException; - TarantoolBinaryPackage readPackage() throws IOException; - - void writeBuffer(ByteBuffer byteBuffer) throws IOException; - String getDescription(); } diff --git a/src/main/java/org/tarantool/RoundRobinNodeCommunicationProvider.java b/src/main/java/org/tarantool/RoundRobinNodeCommunicationProvider.java index ff000117..3e1da90b 100644 --- a/src/main/java/org/tarantool/RoundRobinNodeCommunicationProvider.java +++ b/src/main/java/org/tarantool/RoundRobinNodeCommunicationProvider.java @@ -13,10 +13,6 @@ public class RoundRobinNodeCommunicationProvider implements NodeCommunicationPro /** Timeout to establish socket connection with an individual server. */ private final int timeout; // 0 is infinite. - /** Limit of retries. */ - private int retriesLimit = -1; // No-limit. - - private final String clusterUsername; private final String clusterPassword; @@ -64,7 +60,13 @@ public TarantoolInstanceInfo[] getNodes() { return nodes; } - /** {@inheritDoc} */ + /** + * Tries to connect amid nodes in {@code nodes} in round-robin manner. + * + * @return A request-ready connection to an instance + * @throws CommunicationException if it's failed to connect and authorize to a node in given deadline + * described in {@code timeout} field. + */ public TarantoolInstanceConnection connectNextNode() { int attempts = getAddressCount(); long deadline = System.currentTimeMillis() + timeout * attempts; @@ -124,23 +126,6 @@ public TarantoolInstanceConnection connect() { return currentConnection; } - public void writeBuffer(ByteBuffer byteBuffer) throws IOException { - SocketChannel channel2Write = getChannel(); - BinaryProtoUtils.writeFully(channel2Write, byteBuffer); - } - - public TarantoolBinaryPackage readPackage() throws IOException { - SocketChannel channel2Read = getChannel(); - return BinaryProtoUtils.readPacket(channel2Read); - } - - private SocketChannel getChannel() { - if (currentConnection == null) { - throw new IllegalStateException("Not initialized"); - } - return currentConnection.getChannel(); - } - @Override public String getDescription() { if (currentConnection != null) { diff --git a/src/main/java/org/tarantool/SingleNodeCommunicationProvider.java b/src/main/java/org/tarantool/SingleNodeCommunicationProvider.java index 9997b683..7a761a59 100644 --- a/src/main/java/org/tarantool/SingleNodeCommunicationProvider.java +++ b/src/main/java/org/tarantool/SingleNodeCommunicationProvider.java @@ -1,13 +1,10 @@ package org.tarantool; -import org.tarantool.server.*; +import org.tarantool.server.TarantoolInstanceConnection; +import org.tarantool.server.TarantoolInstanceInfo; -import java.io.*; -import java.net.*; -import java.nio.*; -import java.nio.channels.*; -import java.util.*; -import java.util.stream.*; +import java.io.IOException; +import java.net.InetSocketAddress; public class SingleNodeCommunicationProvider implements NodeCommunicationProvider { @@ -29,23 +26,6 @@ public TarantoolInstanceConnection connect() throws IOException { return nodeConnection; } - public void writeBuffer(ByteBuffer byteBuffer) throws IOException { - SocketChannel channel2Write = getChannel(); - BinaryProtoUtils.writeFully(channel2Write, byteBuffer); - } - - public TarantoolBinaryPackage readPackage() throws IOException { - SocketChannel channel2Read = getChannel(); - return BinaryProtoUtils.readPacket(channel2Read); - } - - private SocketChannel getChannel() { - if (nodeConnection == null) { - throw new IllegalStateException("Not initialized"); - } - return nodeConnection.getChannel(); - } - @Override public String getDescription() { if (nodeConnection != null) { diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index fddb6525..1b2f837b 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -1,8 +1,10 @@ package org.tarantool; -import org.tarantool.server.*; +import org.tarantool.server.BinaryProtoUtils; +import org.tarantool.server.TarantoolBinaryPackage; +import org.tarantool.server.TarantoolInstanceConnection; -import java.io.*; +import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; @@ -145,10 +147,11 @@ protected void reconnect(int retry, Throwable lastError) { connectAndStartThreads(); return; } catch (Exception e) { - closeChannel(currConnection); - lastError = e; - if (e instanceof InterruptedException) - Thread.currentThread().interrupt(); + close(e); +// closeChannel(currConnection); +// lastError = e; +// if (e instanceof InterruptedException) +// Thread.currentThread().interrupt(); } } } @@ -395,8 +398,7 @@ protected CompletableFuture getFuture(TarantoolBinaryPackage pack) { } protected TarantoolBinaryPackage readFromInstance() throws IOException, InterruptedException { -// return communicationProvider.readPackage(); - return BinaryProtoUtils.readPacket(currConnection.getChannel()); + return BinaryProtoUtils.readPacket(currConnection.getReadChannel()); } protected void writeThread() { diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index f2477f7f..6b525f82 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -31,16 +31,13 @@ public class TarantoolClusterClient extends TarantoolClientImpl { /* Collection of operations to be retried. */ private ConcurrentHashMap> retries = new ConcurrentHashMap>(); - @Deprecated - private final Collection slaveHosts; - - private TarantoolInstanceInfo infoHost; + private TarantoolInstanceInfo infoHost;//todo will be used (remove this comment later) private Integer infoHostConnectionTimeout; private ClusterTopologyDiscoverer topologyDiscoverer; private volatile TarantoolInstanceConnection oldConnection; - private ConcurrentHashMap> futuresSentToOldConnection = new ConcurrentHashMap<>(); + private ConcurrentHashMap> futuresSentToOldConnection = new ConcurrentHashMap<>(); private ReentrantLock initLock = new ReentrantLock(); private Selector readSelector; @@ -69,16 +66,10 @@ public TarantoolClusterClient(TarantoolClusterClientConfig config, NodeCommunica this.infoHostConnectionTimeout = config.infoHostConnectionTimeout; this.topologyDiscoverer = new ClusterTopologyFromShardDiscovererImpl(config); - - slaveHosts = topologyDiscoverer.discoverTarantoolInstances(this.infoHost, infoHostConnectionTimeout); } else { if (config.slaveHosts == null || config.slaveHosts.length == 0) { throw new IllegalArgumentException("Either slaveHosts or infoHost must be specified."); } - - slaveHosts = Arrays.stream(config.slaveHosts) - .map(s -> TarantoolInstanceInfo.create(s, config.username, config.password)) - .collect(Collectors.toList()); } } @@ -104,12 +95,8 @@ private Collection refreshServerList(TarantoolInstanceInf Collections.swap(newServerList, 0, sameNodeIndex); cp.updateNodes(newServerList); } else { - - cp.updateNodes(newServerList); - - if (state.isAtState(StateHelper.ALIVE)) { stopIO(); @@ -117,14 +104,13 @@ private Collection refreshServerList(TarantoolInstanceInf .forEach(f -> { f.completeExceptionally(new CommunicationException("Connection is dead")); }); - futuresSentToOldConnection.putAll((Map>) futures); + + futuresSentToOldConnection.putAll(futures); futures.clear(); LockSupport.unpark(connector); - - //remove -// die("The server list have been changed.", null); } + // if not alive, then do nothing because we are closed or on RECONNECT state. // in the last case reconnect thread will wait untill the initLock will be unlocked @@ -150,7 +136,7 @@ protected void connect(NodeCommunicationProvider communicationProvider) throws E if (readSelector != null) { try { - readSelector.close(); + readSelector.close();//todo must be closed at stopIO operation } catch (IOException ignored) { } } @@ -164,7 +150,7 @@ protected void connect(NodeCommunicationProvider communicationProvider) throws E readSelector = Selector.open(); if (oldConnection != null) { - oldConnection.getChannel().register(readSelector, SelectionKey.OP_READ, currConnection); + oldConnection.getChannel().register(readSelector, SelectionKey.OP_READ, oldConnection); } currConnection.getChannel().register(readSelector, SelectionKey.OP_READ, currConnection); @@ -194,7 +180,7 @@ protected CompletableFuture getFuture(TarantoolBinaryPackage pack) { long now = System.currentTimeMillis(); futuresSentToOldConnection.entrySet() .removeIf(entry -> { - ExpirableOp expirableOp = entry.getValue(); + ExpirableOp expirableOp = (ExpirableOp) entry.getValue(); if (expirableOp.hasExpired(now)) { expirableOp.completeExceptionally( new CommunicationException("Operation timeout is expired")); @@ -283,6 +269,17 @@ protected void close(Exception e) { } } + @Override + protected void stopIO() { + super.stopIO(); + closeChannel(oldConnection); + try { + readSelector.close(); + } catch (IOException e) { + //ignored + } + } + protected boolean isTransientError(Exception e) { if (e instanceof CommunicationException) { return true; diff --git a/src/main/java/org/tarantool/server/BinaryProtoUtils.java b/src/main/java/org/tarantool/server/BinaryProtoUtils.java index cbe1b35c..4e0acb86 100644 --- a/src/main/java/org/tarantool/server/BinaryProtoUtils.java +++ b/src/main/java/org/tarantool/server/BinaryProtoUtils.java @@ -156,7 +156,6 @@ public static void writeFully(SocketChannel channel, ByteBuffer buffer) throws I */ public static TarantoolBinaryPackage readPacket(ReadableByteChannel bufferReader) throws CommunicationException, IOException { - //todo get rid of this because SelectorProvider.provider().openSelector() creates two pipes and socket in the /proc/fd ByteBuffer buffer = ByteBuffer.allocate(LENGTH_OF_SIZE_MESSAGE); bufferReader.read(buffer); @@ -193,18 +192,6 @@ public static TarantoolBinaryPackage readPacket(ReadableByteChannel bufferReader return new TarantoolBinaryPackage(headers, body); } - @Deprecated - public static TarantoolBinaryPackage readPacket(SocketChannel channel) throws IOException { - ReadableViaSelectorChannel bufferReader = new ReadableViaSelectorChannel(channel); - return readPacket(bufferReader); - } - - @Deprecated - public static TarantoolBinaryPackage readPacketOld(SocketChannel channel) throws IOException { - CountInputStream inputStream = new ByteBufferInputStream(channel); - return readPacket(inputStream); - } - @Deprecated private static TarantoolBinaryPackage readPacket(CountInputStream inputStream) throws IOException { int size = ((Number) getMsgPackLite().unpack(inputStream)).intValue(); @@ -270,7 +257,7 @@ public static ByteBuffer createPacket(Code code, Long syncId, Long schemaId, Obj } public static ByteBuffer createPacket(int initialRequestSize, Code code, Long syncId, Long schemaId, Object... args) throws IOException { -// TarantoolClientImpl.ByteArrayOutputStream bos = new TarantoolClientImpl.ByteArrayOutputStream(initialRequestSize); + ByteArrayOutputStream bos = new ByteArrayOutputStream(initialRequestSize); bos.write(new byte[5]); DataOutputStream ds = new DataOutputStream(bos); diff --git a/src/main/java/org/tarantool/server/ReadableViaSelectorChannel.java b/src/main/java/org/tarantool/server/ReadableViaSelectorChannel.java index cae17899..9360b62e 100644 --- a/src/main/java/org/tarantool/server/ReadableViaSelectorChannel.java +++ b/src/main/java/org/tarantool/server/ReadableViaSelectorChannel.java @@ -37,7 +37,7 @@ public int read(ByteBuffer buffer) throws IOException { selector.select();//todo think about read timeout n = channel.read(buffer); if (n < 0) { - throw new CommunicationException("Channel read failed " + n); + throw new CommunicationException("Channel read failed: " + n); } count += n; } diff --git a/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java b/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java index 6dded241..2a514625 100644 --- a/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java +++ b/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java @@ -80,7 +80,7 @@ public ReadableByteChannel getReadChannel() { private void closeConnection() { try { - channel.close(); + readChannel.close();//also closes the channel } catch (IOException ignored) { } diff --git a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java index 4e68f89a..ef794573 100644 --- a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java +++ b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java @@ -35,7 +35,7 @@ public abstract class AbstractTarantoolConnectorIT { protected static final int LISTEN = 3301; protected static final int ADMIN = 3313; protected static final int TIMEOUT = 500; - protected static final int RESTART_TIMEOUT = 200000;//todo + protected static final int RESTART_TIMEOUT = 2000; @Deprecated protected static final SocketChannelProvider socketChannelProvider = new TestSocketChannelProvider(host, port, diff --git a/src/test/java/org/tarantool/ClientReconnectIT.java b/src/test/java/org/tarantool/ClientReconnectIT.java index 6c269630..65bdb3c3 100644 --- a/src/test/java/org/tarantool/ClientReconnectIT.java +++ b/src/test/java/org/tarantool/ClientReconnectIT.java @@ -99,16 +99,6 @@ public TarantoolInstanceConnection connect() throws IOException { return testNodeCommunicationProvider.connect(); } - @Override - public TarantoolBinaryPackage readPackage() throws IOException { - return testNodeCommunicationProvider.readPackage(); - } - - @Override - public void writeBuffer(ByteBuffer byteBuffer) throws IOException { - testNodeCommunicationProvider.writeBuffer(byteBuffer); - } - @Override public String getDescription() { return testNodeCommunicationProvider.getDescription(); diff --git a/src/test/perf/org/tarantool/MyBenchmark.java b/src/test/perf/org/tarantool/MyBenchmark.java index 3e37df61..58044a5d 100644 --- a/src/test/perf/org/tarantool/MyBenchmark.java +++ b/src/test/perf/org/tarantool/MyBenchmark.java @@ -35,16 +35,6 @@ public TarantoolInstanceConnection connect() throws IOException { return null; } - @Override - public TarantoolBinaryPackage readPackage() throws IOException { - return null; - } - - @Override - public void writeBuffer(ByteBuffer byteBuffer) throws IOException { - - } - @Override public String getDescription() { return null; From 7d7bafdb4d27c59379c90946bbc083db0d507604 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Sun, 10 Mar 2019 19:32:48 +0300 Subject: [PATCH 23/30] Write a test for cluster topology discoverer. Fix the discoverer interface --- .../org/tarantool/TarantoolClusterClient.java | 6 +- .../TarantoolClusterClientConfig.java | 8 +- .../cluster/ClusterTopologyDiscoverer.java | 2 +- ...terTopologyFromFunctionDiscovererImpl.java | 57 ++++++++++++ ...lusterTopologyFromShardDiscovererImpl.java | 10 ++- .../server/TarantoolInstanceInfo.java | 20 +++++ .../AbstractTarantoolConnectorIT.java | 2 +- .../tarantool/ClientReconnectClusterIT.java | 2 +- .../org/tarantool/VshardClusterClientIT.java | 4 + ...rTopologyFromFunctionDiscovererImplIT.java | 87 +++++++++++++++++++ ...erTopologyFromShardDiscovererImplTest.java | 1 + 11 files changed, 187 insertions(+), 12 deletions(-) create mode 100644 src/main/java/org/tarantool/cluster/ClusterTopologyFromFunctionDiscovererImpl.java create mode 100644 src/test/java/org/tarantool/VshardClusterClientIT.java create mode 100644 src/test/java/org/tarantool/cluster/ClusterTopologyFromFunctionDiscovererImplIT.java diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index 6b525f82..a4ae0e80 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -75,14 +75,12 @@ public TarantoolClusterClient(TarantoolClusterClientConfig config, NodeCommunica } /** - * @param infoNode a node from which a topology of the cluster is discovered. * @throws CommunicationException in case of communication with {@code infoNode} exception * @throws IllegalArgumentException in case when the info node returned invalid address */ - private Collection refreshServerList(TarantoolInstanceInfo infoNode) { + protected Collection refreshServerList() { List newServerList = topologyDiscoverer - .discoverTarantoolInstances(infoNode, infoHostConnectionTimeout); - + .discoverTarantoolInstances(infoHostConnectionTimeout); initLock.lock(); try { diff --git a/src/main/java/org/tarantool/TarantoolClusterClientConfig.java b/src/main/java/org/tarantool/TarantoolClusterClientConfig.java index c44b9c9f..8f3d4b54 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClientConfig.java +++ b/src/main/java/org/tarantool/TarantoolClusterClientConfig.java @@ -18,10 +18,16 @@ public class TarantoolClusterClientConfig extends TarantoolClientConfig { public String[] slaveHosts; /** - * Address of a tarantool instance that can act as provider of host list + * Address of a tarantool instance form which a cluster host list can be discovered. */ public String infoHost; + /** + * Name of a function that called on info host instance to fetch the list of + * tarantool cluster instances + */ + public String infoFunctionName; + /** * timeout of connecting to a info host */ diff --git a/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java b/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java index 44fc8b16..8843b90d 100644 --- a/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java +++ b/src/main/java/org/tarantool/cluster/ClusterTopologyDiscoverer.java @@ -5,5 +5,5 @@ import java.util.List; public interface ClusterTopologyDiscoverer { - List discoverTarantoolInstances(TarantoolInstanceInfo infoNode, Integer infoHostConnectionTimeout); + List discoverTarantoolInstances(Integer infoHostConnectionTimeout); } diff --git a/src/main/java/org/tarantool/cluster/ClusterTopologyFromFunctionDiscovererImpl.java b/src/main/java/org/tarantool/cluster/ClusterTopologyFromFunctionDiscovererImpl.java new file mode 100644 index 00000000..038b1c72 --- /dev/null +++ b/src/main/java/org/tarantool/cluster/ClusterTopologyFromFunctionDiscovererImpl.java @@ -0,0 +1,57 @@ +package org.tarantool.cluster; + +import org.tarantool.TarantoolClientImpl; +import org.tarantool.TarantoolClientOps; +import org.tarantool.TarantoolClusterClientConfig; +import org.tarantool.server.TarantoolInstanceInfo; + +import java.util.List; +import java.util.stream.Collectors; + +public class ClusterTopologyFromFunctionDiscovererImpl implements ClusterTopologyDiscoverer { + + private final TarantoolClusterClientConfig clientConfig; + + private final TarantoolInstanceInfo infoNode; + private final String functionName; + + public ClusterTopologyFromFunctionDiscovererImpl(TarantoolClusterClientConfig clientConfig) { + this.clientConfig = clientConfig; + if (clientConfig.infoFunctionName == null) { + throw new IllegalArgumentException("infoFuntionName in the config cannot be null"); + } + if (clientConfig.infoHost == null) { + throw new IllegalArgumentException("infoHost in the config cannot be null"); + } + + functionName = clientConfig.infoFunctionName; + this.infoNode = TarantoolInstanceInfo.create( + clientConfig.infoHost, clientConfig.username, clientConfig.password); + } + + @Override + public List discoverTarantoolInstances(Integer infoHostConnectionTimeout) { + + TarantoolClientOps, Object, List> syncOps = + new TarantoolClientImpl(infoNode.getSocketAddress(), clientConfig) + .syncOps(); + + List list = syncOps + .call(functionName); + + List funcResult = (List) list.get(0); + return funcResult.stream() + .map(addr -> TarantoolInstanceInfo.create( + parseReplicaUri(addr.toString()), clientConfig.username, clientConfig.password)) + .collect(Collectors.toList()); + } + + private String parseReplicaUri(String uri) { + String[] split = uri.split("@"); + if (split.length == 2) { + return split[1]; + } else { + return split[0]; + } + } +} diff --git a/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java b/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java index 6d097789..722c7b8a 100644 --- a/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java +++ b/src/main/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImpl.java @@ -11,20 +11,22 @@ public class ClusterTopologyFromShardDiscovererImpl implements ClusterTopologyDiscoverer { private final TarantoolClusterClientConfig clientConfig; + private final TarantoolInstanceInfo infoNode; - private static final String DISCOVER_TOPOLOGY_TARANTOOL_SIDE_FUNCTION_NAME = "get_shard_cfg"; + private static final String DEFAULT_TOPOLOGY_DISCOVER_CALL = "require('vshard').storage.internal.current_cfg"; public ClusterTopologyFromShardDiscovererImpl(TarantoolClusterClientConfig clientConfig) { this.clientConfig = clientConfig; + this.infoNode = TarantoolInstanceInfo.create( + clientConfig.infoHost, clientConfig.username, clientConfig.password); } @Override - public List discoverTarantoolInstances(TarantoolInstanceInfo infoNode, - Integer infoHostConnectionTimeout) { + public List discoverTarantoolInstances(Integer infoHostConnectionTimeout) { List list = new TarantoolClientImpl(infoNode.getSocketAddress(), clientConfig) .syncOps() - .call(DISCOVER_TOPOLOGY_TARANTOOL_SIDE_FUNCTION_NAME); + .call(DEFAULT_TOPOLOGY_DISCOVER_CALL); Map funcResult = (Map) ((List) list.get(0)).get(0); diff --git a/src/main/java/org/tarantool/server/TarantoolInstanceInfo.java b/src/main/java/org/tarantool/server/TarantoolInstanceInfo.java index 98e78b44..84b16cbe 100644 --- a/src/main/java/org/tarantool/server/TarantoolInstanceInfo.java +++ b/src/main/java/org/tarantool/server/TarantoolInstanceInfo.java @@ -61,7 +61,27 @@ public static TarantoolInstanceInfo create(InetSocketAddress socketAddress, Stri /** + * Creates an instance info object with no authentication data. + * + * @param address hostname address as String + * + * @throws IllegalArgumentException if the port parameter is outside the range + * of valid port values, or if the hostname parameter is null. + * @throws SecurityException if a security manager is present and + * permission to resolve the host name is + * denied. + */ + public static TarantoolInstanceInfo create(String address) { + return create(address, null, null); + } + + + + /** + * Creates an instance info object * @param address hostname address as String + * @param username authentication username + * @param password authentication password * * @throws IllegalArgumentException if the port parameter is outside the range * of valid port values, or if the hostname parameter is null. diff --git a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java index ef794573..6f1e17e4 100644 --- a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java +++ b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java @@ -144,7 +144,7 @@ protected static TarantoolClientConfig makeClientConfig() { return fillClientConfig(new TarantoolClientConfig()); } - protected static TarantoolClusterClientConfig makeClusterClientConfig() { + public static TarantoolClusterClientConfig makeClusterClientConfig() { TarantoolClusterClientConfig config = fillClientConfig(new TarantoolClusterClientConfig()); config.executor = null; config.operationExpiryTimeMillis = TIMEOUT; diff --git a/src/test/java/org/tarantool/ClientReconnectClusterIT.java b/src/test/java/org/tarantool/ClientReconnectClusterIT.java index 49f05db6..f83c1915 100644 --- a/src/test/java/org/tarantool/ClientReconnectClusterIT.java +++ b/src/test/java/org/tarantool/ClientReconnectClusterIT.java @@ -110,7 +110,7 @@ public void execute() throws Throwable { assertEquals("Connection time out.", e.getMessage()); } - private TarantoolClientImpl makeClient(String...addrs) { + private TarantoolClusterClient makeClient(String...addrs) { TarantoolClusterClientConfig config = makeClusterClientConfig(); config.slaveHosts = addrs; return new TarantoolClusterClient(config); diff --git a/src/test/java/org/tarantool/VshardClusterClientIT.java b/src/test/java/org/tarantool/VshardClusterClientIT.java new file mode 100644 index 00000000..2365b4c1 --- /dev/null +++ b/src/test/java/org/tarantool/VshardClusterClientIT.java @@ -0,0 +1,4 @@ +package org.tarantool; + +public class VshardClusterClientIT { +} diff --git a/src/test/java/org/tarantool/cluster/ClusterTopologyFromFunctionDiscovererImplIT.java b/src/test/java/org/tarantool/cluster/ClusterTopologyFromFunctionDiscovererImplIT.java new file mode 100644 index 00000000..7d1d04ef --- /dev/null +++ b/src/test/java/org/tarantool/cluster/ClusterTopologyFromFunctionDiscovererImplIT.java @@ -0,0 +1,87 @@ +package org.tarantool.cluster; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.tarantool.AbstractTarantoolConnectorIT; +import org.tarantool.TarantoolClusterClientConfig; +import org.tarantool.TarantoolControl; +import org.tarantool.server.TarantoolInstanceInfo; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.tarantool.TestUtils.makeInstanceEnv; + +@DisplayName("ClusterTopologyFromFunctionDiscovererImpl integration tests") +class ClusterTopologyFromFunctionDiscovererImplIT { + protected static final int INSTANCE_LISTEN_PORT = 3301; + protected static final int INSTANCE_ADMIN_PORT = 3313; + private static final String LUA_FILE = "jdk-testing.lua"; + + private static final String INSTANCE_NAME = "jdk-testing"; + private static TarantoolControl control; + private static TarantoolClusterClientConfig clusterConfig; + + private static String INFO_FUNCTION_NAME = "retAddrs"; + private static String TEST_RETURN_HOSTLIST_SCRIPT = + "function " + INFO_FUNCTION_NAME + "() return {'localhost:80', '127.0.0.1:3301'} end"; + + private static final Integer DISCOVER_TIMEOUT_MILLIS = 1000; + + @BeforeAll + public static void setupEnv() { + control = new TarantoolControl(); + control.createInstance(INSTANCE_NAME, LUA_FILE, makeInstanceEnv(INSTANCE_LISTEN_PORT, INSTANCE_ADMIN_PORT)); + + control.start(INSTANCE_NAME); + control.waitStarted("jdk-testing"); + + clusterConfig = AbstractTarantoolConnectorIT.makeClusterClientConfig(); + clusterConfig.infoHost = "localhost:" + INSTANCE_LISTEN_PORT; + clusterConfig.infoFunctionName = INFO_FUNCTION_NAME; + } + + @AfterAll + public static void tearDownEnv() { + control.stop(INSTANCE_NAME); + } + + @Test + @DisplayName("Discoverer successfully fetches and parses a node list from an info node.") + void testSuccessfulAddressParsing() { + //inject the function + control.openConsole(INSTANCE_NAME).exec(TEST_RETURN_HOSTLIST_SCRIPT); + ClusterTopologyFromFunctionDiscovererImpl discoverer = + new ClusterTopologyFromFunctionDiscovererImpl(clusterConfig); + + + List instances = discoverer.discoverTarantoolInstances(DISCOVER_TIMEOUT_MILLIS); + + + assertNotNull(instances); + assertEquals(2, instances.size()); + assertTrue(instances.contains(TarantoolInstanceInfo.create("localhost:80"))); + assertTrue(instances.contains(TarantoolInstanceInfo.create("127.0.0.1:3301"))); + } + + @Test + @DisplayName("Gracefully process case when the function returns empty node list") + void testFunctionReturnedEmptyList() { + String functionCode = "function " + INFO_FUNCTION_NAME + "() return {} end"; + //inject the function + control.openConsole(INSTANCE_NAME).exec(functionCode); + ClusterTopologyFromFunctionDiscovererImpl discoverer = + new ClusterTopologyFromFunctionDiscovererImpl(clusterConfig); + + + List instances = discoverer.discoverTarantoolInstances(DISCOVER_TIMEOUT_MILLIS); + + + assertNotNull(instances); + assertTrue(instances.isEmpty()); + } +} \ No newline at end of file diff --git a/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java b/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java index 062de278..511b4742 100644 --- a/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java +++ b/src/test/java/org/tarantool/cluster/ClusterTopologyFromShardDiscovererImplTest.java @@ -5,6 +5,7 @@ import org.tarantool.TarantoolClusterClientConfig; import org.tarantool.server.TarantoolInstanceInfo; +@Deprecated class ClusterTopologyFromShardDiscovererImplTest { @DisplayName("Test that a list which describes the topology is fetched correctly") From 6e98d2226fd8c985858eec78cc5516284977c448 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Sun, 10 Mar 2019 19:41:58 +0300 Subject: [PATCH 24/30] Rename: SingleNodeCommunicationProvider -> SingleNodeConnectionProvider --- .../org/tarantool/TestTarantoolClient.java | 4 +-- ...java => SingleNodeConnectionProvider.java} | 6 ++-- .../org/tarantool/TarantoolClientImpl.java | 4 +-- .../AbstractTarantoolConnectorIT.java | 2 +- .../tarantool/ClientReconnectClusterIT.java | 29 +++++++++++++++++++ ...r.java => TestNodeConnectionProvider.java} | 4 +-- 6 files changed, 38 insertions(+), 11 deletions(-) rename src/main/java/org/tarantool/{SingleNodeCommunicationProvider.java => SingleNodeConnectionProvider.java} (78%) rename src/test/java/org/tarantool/{TestNodeCommunicationProvider.java => TestNodeConnectionProvider.java} (82%) diff --git a/src/it/java/org/tarantool/TestTarantoolClient.java b/src/it/java/org/tarantool/TestTarantoolClient.java index c4d53b96..27e24596 100644 --- a/src/it/java/org/tarantool/TestTarantoolClient.java +++ b/src/it/java/org/tarantool/TestTarantoolClient.java @@ -3,9 +3,7 @@ import org.tarantool.server.TarantoolBinaryPackage; import java.io.IOException; -import java.net.InetSocketAddress; import java.nio.ByteBuffer; -import java.nio.channels.SocketChannel; import java.sql.SQLException; import java.util.Arrays; import java.util.concurrent.CompletableFuture; @@ -96,7 +94,7 @@ public static void main(String[] args) throws IOException, InterruptedException, //config.sharedBufferSize = 0; NodeCommunicationProvider nodeComm = - new SingleNodeCommunicationProvider("localhost:3301", config.username, config.password); + new SingleNodeConnectionProvider("localhost:3301", config.username, config.password); final TarantoolClientTestImpl client = new TarantoolClientTestImpl(nodeComm, config); diff --git a/src/main/java/org/tarantool/SingleNodeCommunicationProvider.java b/src/main/java/org/tarantool/SingleNodeConnectionProvider.java similarity index 78% rename from src/main/java/org/tarantool/SingleNodeCommunicationProvider.java rename to src/main/java/org/tarantool/SingleNodeConnectionProvider.java index 7a761a59..0df8cb1e 100644 --- a/src/main/java/org/tarantool/SingleNodeCommunicationProvider.java +++ b/src/main/java/org/tarantool/SingleNodeConnectionProvider.java @@ -6,17 +6,17 @@ import java.io.IOException; import java.net.InetSocketAddress; -public class SingleNodeCommunicationProvider implements NodeCommunicationProvider { +public class SingleNodeConnectionProvider implements NodeCommunicationProvider { private final TarantoolInstanceInfo tarantoolInstanceInfo; private TarantoolInstanceConnection nodeConnection; - public SingleNodeCommunicationProvider(InetSocketAddress socketAddress, String username, String password) { + public SingleNodeConnectionProvider(InetSocketAddress socketAddress, String username, String password) { this.tarantoolInstanceInfo = TarantoolInstanceInfo.create(socketAddress, username, password); } - public SingleNodeCommunicationProvider(String address, String username, String password) { + public SingleNodeConnectionProvider(String address, String username, String password) { this.tarantoolInstanceInfo = TarantoolInstanceInfo.create(address, username, password); } diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index 1b2f837b..b60802b9 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -84,11 +84,11 @@ public void run() { }); public TarantoolClientImpl(InetSocketAddress socketAddress, TarantoolClientConfig config) { - this(new SingleNodeCommunicationProvider(socketAddress, config.username, config.password), config); + this(new SingleNodeConnectionProvider(socketAddress, config.username, config.password), config); } public TarantoolClientImpl(String address, TarantoolClientConfig config) { - this(new SingleNodeCommunicationProvider(address, config.username, config.password), config); + this(new SingleNodeConnectionProvider(address, config.username, config.password), config); } protected TarantoolClientImpl() { diff --git a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java index 6f1e17e4..75096166 100644 --- a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java +++ b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java @@ -42,7 +42,7 @@ public abstract class AbstractTarantoolConnectorIT { RESTART_TIMEOUT); protected static final NodeCommunicationProvider testNodeCommunicationProvider = - new TestNodeCommunicationProvider(host + ":" + port, username, password, RESTART_TIMEOUT); + new TestNodeConnectionProvider(host + ":" + port, username, password, RESTART_TIMEOUT); protected static TarantoolControl control; protected static TarantoolConsole console; diff --git a/src/test/java/org/tarantool/ClientReconnectClusterIT.java b/src/test/java/org/tarantool/ClientReconnectClusterIT.java index f83c1915..7d552186 100644 --- a/src/test/java/org/tarantool/ClientReconnectClusterIT.java +++ b/src/test/java/org/tarantool/ClientReconnectClusterIT.java @@ -115,4 +115,33 @@ private TarantoolClusterClient makeClient(String...addrs) { config.slaveHosts = addrs; return new TarantoolClusterClient(config); } + + + @Test + void testUpdateNodeList() { + control.start(SRV1); + control.start(SRV2); + control.start(SRV3); + + control.waitStarted(SRV1); + control.waitStarted(SRV2); + control.waitStarted(SRV3); + + String srv1_address = "localhost:" + PORTS[0]; + String srv2_address = "127.0.0.1:" + PORTS[1]; + String srv3_address = "localhost:" + PORTS[2]; + final TarantoolClusterClient client = makeClient( + srv1_address, + srv2_address); + + + List ids = client.syncOps().eval( + "return box.schema.space.create('rr_test').id, " + + "box.space.rr_test:create_index('primary').id"); + + +// client.ref +//todo + + } } diff --git a/src/test/java/org/tarantool/TestNodeCommunicationProvider.java b/src/test/java/org/tarantool/TestNodeConnectionProvider.java similarity index 82% rename from src/test/java/org/tarantool/TestNodeCommunicationProvider.java rename to src/test/java/org/tarantool/TestNodeConnectionProvider.java index 35c16d0a..50776aec 100644 --- a/src/test/java/org/tarantool/TestNodeCommunicationProvider.java +++ b/src/test/java/org/tarantool/TestNodeConnectionProvider.java @@ -4,10 +4,10 @@ import java.io.IOException; -public class TestNodeCommunicationProvider extends SingleNodeCommunicationProvider { +public class TestNodeConnectionProvider extends SingleNodeConnectionProvider { private final long restartTimeout; - public TestNodeCommunicationProvider(String address, String username, String password, long restartTimeout1) { + public TestNodeConnectionProvider(String address, String username, String password, long restartTimeout1) { super(address, username, password); this.restartTimeout = restartTimeout1; From bb69dd81265dbb0269600d2f5969627c329d0e4a Mon Sep 17 00:00:00 2001 From: dponomarev Date: Sun, 10 Mar 2019 19:43:18 +0300 Subject: [PATCH 25/30] Rename: NodeCommunicationProvider -> InstanceConnectionProvider --- .../org/tarantool/TestTarantoolClient.java | 4 ++-- ...er.java => InstanceConnectionProvider.java} | 2 +- ... RoundRobinInstanceConnectionProvider.java} | 6 ++---- .../SingleNodeConnectionProvider.java | 2 +- .../org/tarantool/TarantoolClientImpl.java | 8 ++++---- .../org/tarantool/TarantoolClusterClient.java | 9 ++++----- .../AbstractTarantoolConnectorIT.java | 4 ++-- .../java/org/tarantool/ClientReconnectIT.java | 18 ++++++++---------- src/test/perf/org/tarantool/MyBenchmark.java | 4 +--- 9 files changed, 25 insertions(+), 32 deletions(-) rename src/main/java/org/tarantool/{NodeCommunicationProvider.java => InstanceConnectionProvider.java} (80%) rename src/main/java/org/tarantool/{RoundRobinNodeCommunicationProvider.java => RoundRobinInstanceConnectionProvider.java} (94%) diff --git a/src/it/java/org/tarantool/TestTarantoolClient.java b/src/it/java/org/tarantool/TestTarantoolClient.java index 27e24596..3573db9c 100644 --- a/src/it/java/org/tarantool/TestTarantoolClient.java +++ b/src/it/java/org/tarantool/TestTarantoolClient.java @@ -33,7 +33,7 @@ public static class TarantoolClientTestImpl extends TarantoolClientImpl { final Semaphore s = new Semaphore(0); long latency = 1L; - public TarantoolClientTestImpl(NodeCommunicationProvider nodeComm, TarantoolClientConfig options) { + public TarantoolClientTestImpl(InstanceConnectionProvider nodeComm, TarantoolClientConfig options) { super(nodeComm, options); Thread t = new Thread(new Runnable() { @Override @@ -93,7 +93,7 @@ public static void main(String[] args) throws IOException, InterruptedException, //config.sharedBufferSize = 0; - NodeCommunicationProvider nodeComm = + InstanceConnectionProvider nodeComm = new SingleNodeConnectionProvider("localhost:3301", config.username, config.password); diff --git a/src/main/java/org/tarantool/NodeCommunicationProvider.java b/src/main/java/org/tarantool/InstanceConnectionProvider.java similarity index 80% rename from src/main/java/org/tarantool/NodeCommunicationProvider.java rename to src/main/java/org/tarantool/InstanceConnectionProvider.java index c1168269..e6c40109 100644 --- a/src/main/java/org/tarantool/NodeCommunicationProvider.java +++ b/src/main/java/org/tarantool/InstanceConnectionProvider.java @@ -5,7 +5,7 @@ import java.io.*; import java.nio.*; -public interface NodeCommunicationProvider { +public interface InstanceConnectionProvider { TarantoolInstanceConnection connect() throws IOException; diff --git a/src/main/java/org/tarantool/RoundRobinNodeCommunicationProvider.java b/src/main/java/org/tarantool/RoundRobinInstanceConnectionProvider.java similarity index 94% rename from src/main/java/org/tarantool/RoundRobinNodeCommunicationProvider.java rename to src/main/java/org/tarantool/RoundRobinInstanceConnectionProvider.java index 3e1da90b..a492de25 100644 --- a/src/main/java/org/tarantool/RoundRobinNodeCommunicationProvider.java +++ b/src/main/java/org/tarantool/RoundRobinInstanceConnectionProvider.java @@ -3,12 +3,10 @@ import org.tarantool.server.*; import java.io.*; -import java.nio.*; -import java.nio.channels.*; import java.util.*; import java.util.stream.*; -public class RoundRobinNodeCommunicationProvider implements NodeCommunicationProvider { +public class RoundRobinInstanceConnectionProvider implements InstanceConnectionProvider { /** Timeout to establish socket connection with an individual server. */ private final int timeout; // 0 is infinite. @@ -21,7 +19,7 @@ public class RoundRobinNodeCommunicationProvider implements NodeCommunicationPro private TarantoolInstanceInfo[] nodes; private int pos = 0; - public RoundRobinNodeCommunicationProvider(String[] slaveHosts, String username, String password, int timeout) { + public RoundRobinInstanceConnectionProvider(String[] slaveHosts, String username, String password, int timeout) { this.timeout = timeout; if (slaveHosts == null || slaveHosts.length < 1) { throw new IllegalArgumentException("slave hosts is null ot empty"); diff --git a/src/main/java/org/tarantool/SingleNodeConnectionProvider.java b/src/main/java/org/tarantool/SingleNodeConnectionProvider.java index 0df8cb1e..fe48ddc7 100644 --- a/src/main/java/org/tarantool/SingleNodeConnectionProvider.java +++ b/src/main/java/org/tarantool/SingleNodeConnectionProvider.java @@ -6,7 +6,7 @@ import java.io.IOException; import java.net.InetSocketAddress; -public class SingleNodeConnectionProvider implements NodeCommunicationProvider { +public class SingleNodeConnectionProvider implements InstanceConnectionProvider { private final TarantoolInstanceInfo tarantoolInstanceInfo; diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index b60802b9..41b5f879 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -33,7 +33,7 @@ public class TarantoolClientImpl extends TarantoolBase> implements Tar /** * External */ - protected NodeCommunicationProvider communicationProvider; + protected InstanceConnectionProvider communicationProvider; /** * Max amount of one by one reconnect attempts @@ -95,12 +95,12 @@ protected TarantoolClientImpl() { // delegate init to a descendant } - public TarantoolClientImpl(NodeCommunicationProvider communicationProvider, TarantoolClientConfig config) { + public TarantoolClientImpl(InstanceConnectionProvider communicationProvider, TarantoolClientConfig config) { super(); init(communicationProvider, config); } - protected void init(NodeCommunicationProvider communicationProvider, TarantoolClientConfig config) { + protected void init(InstanceConnectionProvider communicationProvider, TarantoolClientConfig config) { this.thumbstone = NOT_INIT_EXCEPTION; this.config = config; this.initialRequestSize = config.defaultRequestSize; @@ -183,7 +183,7 @@ private boolean areRetriesExhausted(int retries) { return retries >= limit; } - protected void connect(final NodeCommunicationProvider communicationProvider) throws Exception { + protected void connect(final InstanceConnectionProvider communicationProvider) throws Exception { try { currConnection = communicationProvider.connect(); } catch (IOException e) { diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index a4ae0e80..1949420a 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -13,7 +13,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.ReentrantLock; -import java.util.stream.Collectors; import static org.tarantool.TarantoolClientImpl.StateHelper.CLOSED; @@ -47,7 +46,7 @@ public class TarantoolClusterClient extends TarantoolClientImpl { */ public TarantoolClusterClient(TarantoolClusterClientConfig config) { // this(config, new RoundRobinSocketProviderImpl(config.slaveHosts).setTimeout(config.operationExpiryTimeMillis)); - this(config, new RoundRobinNodeCommunicationProvider(config.slaveHosts, + this(config, new RoundRobinInstanceConnectionProvider(config.slaveHosts, config.username, config.password, config.operationExpiryTimeMillis)); } @@ -55,7 +54,7 @@ public TarantoolClusterClient(TarantoolClusterClientConfig config) { * @param provider Socket channel provider. * @param config Configuration. */ - public TarantoolClusterClient(TarantoolClusterClientConfig config, NodeCommunicationProvider provider) { + public TarantoolClusterClient(TarantoolClusterClientConfig config, InstanceConnectionProvider provider) { init(provider, config); this.executor = config.executor == null ? @@ -85,7 +84,7 @@ protected Collection refreshServerList() { initLock.lock(); try { - RoundRobinNodeCommunicationProvider cp = (RoundRobinNodeCommunicationProvider) this.communicationProvider; + RoundRobinInstanceConnectionProvider cp = (RoundRobinInstanceConnectionProvider) this.communicationProvider; int sameNodeIndex = newServerList.indexOf(currConnection.getNodeInfo()); if (sameNodeIndex != -1) { @@ -123,7 +122,7 @@ protected Collection refreshServerList() { } @Override - protected void connect(NodeCommunicationProvider communicationProvider) throws Exception { + protected void connect(InstanceConnectionProvider communicationProvider) throws Exception { //region drop all registered selectors from channel // if (currConnection != null) { // SelectionKey registeredKey = currConnection.getChannel().keyFor(readSelector); diff --git a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java index 75096166..993b9607 100644 --- a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java +++ b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java @@ -41,7 +41,7 @@ public abstract class AbstractTarantoolConnectorIT { protected static final SocketChannelProvider socketChannelProvider = new TestSocketChannelProvider(host, port, RESTART_TIMEOUT); - protected static final NodeCommunicationProvider testNodeCommunicationProvider = + protected static final InstanceConnectionProvider TEST_INSTANCE_CONNECTION_PROVIDER = new TestNodeConnectionProvider(host + ":" + port, username, password, RESTART_TIMEOUT); protected static TarantoolControl control; @@ -137,7 +137,7 @@ protected void checkTupleResult(Object res, List tuple) { protected TarantoolClient makeClient() { // return new TarantoolClientImpl(socketChannelProvider, makeClientConfig()); - return new TarantoolClientImpl(testNodeCommunicationProvider, makeClientConfig()); + return new TarantoolClientImpl(TEST_INSTANCE_CONNECTION_PROVIDER, makeClientConfig()); } protected static TarantoolClientConfig makeClientConfig() { diff --git a/src/test/java/org/tarantool/ClientReconnectIT.java b/src/test/java/org/tarantool/ClientReconnectIT.java index 65bdb3c3..a2193db8 100644 --- a/src/test/java/org/tarantool/ClientReconnectIT.java +++ b/src/test/java/org/tarantool/ClientReconnectIT.java @@ -7,8 +7,6 @@ import org.tarantool.server.*; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.SocketChannel; import java.util.Collections; import java.util.List; import java.util.Random; @@ -91,21 +89,21 @@ public void testSpuriousReturnFromPark() { TarantoolClientConfig config = makeClientConfig(); - NodeCommunicationProvider nodeCommunicationProvider = new NodeCommunicationProvider() { + InstanceConnectionProvider instanceConnectionProvider = new InstanceConnectionProvider() { @Override public TarantoolInstanceConnection connect() throws IOException { latch.countDown(); - return testNodeCommunicationProvider.connect(); + return TEST_INSTANCE_CONNECTION_PROVIDER.connect(); } @Override public String getDescription() { - return testNodeCommunicationProvider.getDescription(); + return TEST_INSTANCE_CONNECTION_PROVIDER.getDescription(); } }; - client = new TarantoolClientImpl(nodeCommunicationProvider, config); + client = new TarantoolClientImpl(instanceConnectionProvider, config); client.syncOps().ping(); // The park() will return inside connector thread. @@ -126,7 +124,7 @@ public String getDescription() { */ @Test public void testCloseWhileOperationsAreInProgress() { - client = new TarantoolClientImpl(testNodeCommunicationProvider, makeClientConfig()) { + client = new TarantoolClientImpl(TEST_INSTANCE_CONNECTION_PROVIDER, makeClientConfig()) { @Override protected void write(Code code, Long syncId, Long schemaId, Object... args) { // Skip write. @@ -154,7 +152,7 @@ public void execute() throws Throwable { @Test public void testReconnectWhileOperationsAreInProgress() { final AtomicBoolean writeEnabled = new AtomicBoolean(false); - client = new TarantoolClientImpl(testNodeCommunicationProvider, makeClientConfig()) { + client = new TarantoolClientImpl(TEST_INSTANCE_CONNECTION_PROVIDER, makeClientConfig()) { @Override protected void write(Code code, Long syncId, Long schemaId, Object... args) throws Exception { if (writeEnabled.get()) { @@ -198,9 +196,9 @@ public void execute() throws Throwable { @Test public void testConcurrentCloseAndReconnect() { final CountDownLatch latch = new CountDownLatch(2); - client = new TarantoolClientImpl(testNodeCommunicationProvider, makeClientConfig()) { + client = new TarantoolClientImpl(TEST_INSTANCE_CONNECTION_PROVIDER, makeClientConfig()) { @Override - protected void connect(NodeCommunicationProvider communicationProvider) throws Exception { + protected void connect(InstanceConnectionProvider communicationProvider) throws Exception { latch.countDown(); super.connect(communicationProvider); } diff --git a/src/test/perf/org/tarantool/MyBenchmark.java b/src/test/perf/org/tarantool/MyBenchmark.java index 58044a5d..e188e75c 100644 --- a/src/test/perf/org/tarantool/MyBenchmark.java +++ b/src/test/perf/org/tarantool/MyBenchmark.java @@ -1,11 +1,9 @@ package org.tarantool; import org.openjdk.jmh.annotations.*; -import org.tarantool.server.TarantoolBinaryPackage; import org.tarantool.server.TarantoolInstanceConnection; import java.io.IOException; -import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.*; import java.util.concurrent.*; @@ -29,7 +27,7 @@ public SocketChannel get() { /** * todo */ - private static class DodgeCommunication implements NodeCommunicationProvider { + private static class DodgeCommunication implements InstanceConnectionProvider { @Override public TarantoolInstanceConnection connect() throws IOException { return null; From 65db6abf8230ff6ce088283ac4b798253bc4db74 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Sun, 10 Mar 2019 19:44:10 +0300 Subject: [PATCH 26/30] Rename: TestNodeConnectionProvider -> TestInstanceConnectionProvider; SingleNodeConnectionProvider -> SingleInstanceConnectionProvider --- src/it/java/org/tarantool/TestTarantoolClient.java | 2 +- ...nProvider.java => SingleInstanceConnectionProvider.java} | 6 +++--- src/main/java/org/tarantool/TarantoolClientImpl.java | 4 ++-- .../java/org/tarantool/AbstractTarantoolConnectorIT.java | 2 +- ...ionProvider.java => TestInstanceConnectionProvider.java} | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) rename src/main/java/org/tarantool/{SingleNodeConnectionProvider.java => SingleInstanceConnectionProvider.java} (77%) rename src/test/java/org/tarantool/{TestNodeConnectionProvider.java => TestInstanceConnectionProvider.java} (82%) diff --git a/src/it/java/org/tarantool/TestTarantoolClient.java b/src/it/java/org/tarantool/TestTarantoolClient.java index 3573db9c..a9294fe9 100644 --- a/src/it/java/org/tarantool/TestTarantoolClient.java +++ b/src/it/java/org/tarantool/TestTarantoolClient.java @@ -94,7 +94,7 @@ public static void main(String[] args) throws IOException, InterruptedException, //config.sharedBufferSize = 0; InstanceConnectionProvider nodeComm = - new SingleNodeConnectionProvider("localhost:3301", config.username, config.password); + new SingleInstanceConnectionProvider("localhost:3301", config.username, config.password); final TarantoolClientTestImpl client = new TarantoolClientTestImpl(nodeComm, config); diff --git a/src/main/java/org/tarantool/SingleNodeConnectionProvider.java b/src/main/java/org/tarantool/SingleInstanceConnectionProvider.java similarity index 77% rename from src/main/java/org/tarantool/SingleNodeConnectionProvider.java rename to src/main/java/org/tarantool/SingleInstanceConnectionProvider.java index fe48ddc7..e6a9742c 100644 --- a/src/main/java/org/tarantool/SingleNodeConnectionProvider.java +++ b/src/main/java/org/tarantool/SingleInstanceConnectionProvider.java @@ -6,17 +6,17 @@ import java.io.IOException; import java.net.InetSocketAddress; -public class SingleNodeConnectionProvider implements InstanceConnectionProvider { +public class SingleInstanceConnectionProvider implements InstanceConnectionProvider { private final TarantoolInstanceInfo tarantoolInstanceInfo; private TarantoolInstanceConnection nodeConnection; - public SingleNodeConnectionProvider(InetSocketAddress socketAddress, String username, String password) { + public SingleInstanceConnectionProvider(InetSocketAddress socketAddress, String username, String password) { this.tarantoolInstanceInfo = TarantoolInstanceInfo.create(socketAddress, username, password); } - public SingleNodeConnectionProvider(String address, String username, String password) { + public SingleInstanceConnectionProvider(String address, String username, String password) { this.tarantoolInstanceInfo = TarantoolInstanceInfo.create(address, username, password); } diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index 41b5f879..e981dd2a 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -84,11 +84,11 @@ public void run() { }); public TarantoolClientImpl(InetSocketAddress socketAddress, TarantoolClientConfig config) { - this(new SingleNodeConnectionProvider(socketAddress, config.username, config.password), config); + this(new SingleInstanceConnectionProvider(socketAddress, config.username, config.password), config); } public TarantoolClientImpl(String address, TarantoolClientConfig config) { - this(new SingleNodeConnectionProvider(address, config.username, config.password), config); + this(new SingleInstanceConnectionProvider(address, config.username, config.password), config); } protected TarantoolClientImpl() { diff --git a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java index 993b9607..9ce92c34 100644 --- a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java +++ b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java @@ -42,7 +42,7 @@ public abstract class AbstractTarantoolConnectorIT { RESTART_TIMEOUT); protected static final InstanceConnectionProvider TEST_INSTANCE_CONNECTION_PROVIDER = - new TestNodeConnectionProvider(host + ":" + port, username, password, RESTART_TIMEOUT); + new TestInstanceConnectionProvider(host + ":" + port, username, password, RESTART_TIMEOUT); protected static TarantoolControl control; protected static TarantoolConsole console; diff --git a/src/test/java/org/tarantool/TestNodeConnectionProvider.java b/src/test/java/org/tarantool/TestInstanceConnectionProvider.java similarity index 82% rename from src/test/java/org/tarantool/TestNodeConnectionProvider.java rename to src/test/java/org/tarantool/TestInstanceConnectionProvider.java index 50776aec..5e60bcc2 100644 --- a/src/test/java/org/tarantool/TestNodeConnectionProvider.java +++ b/src/test/java/org/tarantool/TestInstanceConnectionProvider.java @@ -4,10 +4,10 @@ import java.io.IOException; -public class TestNodeConnectionProvider extends SingleNodeConnectionProvider { +public class TestInstanceConnectionProvider extends SingleInstanceConnectionProvider { private final long restartTimeout; - public TestNodeConnectionProvider(String address, String username, String password, long restartTimeout1) { + public TestInstanceConnectionProvider(String address, String username, String password, long restartTimeout1) { super(address, username, password); this.restartTimeout = restartTimeout1; From db8e2e2a6a6d95ba9d4aecf35b86f9b2d479f952 Mon Sep 17 00:00:00 2001 From: nicktorwald Date: Mon, 11 Mar 2019 12:34:33 +0700 Subject: [PATCH 27/30] jdbc: remove the outdated tarantool sql types NUMERIC/DATE-related types were removed and BLOB type was replaced by SCALAR in scope of new SQL type changes. See https://github.com/tarantool/tarantool/issues/4019 for more information. Fixes #130. (cherry picked from commit d2dfa4c1be7b8d4fcaadf1b0c705532207720d32) --- .../jdbc/JdbcPreparedStatementIT.java | 8 +++---- .../org/tarantool/jdbc/JdbcResultSetIT.java | 8 +++---- .../java/org/tarantool/jdbc/TntSqlType.java | 22 ++----------------- 3 files changed, 8 insertions(+), 30 deletions(-) diff --git a/src/test/java/org/tarantool/jdbc/JdbcPreparedStatementIT.java b/src/test/java/org/tarantool/jdbc/JdbcPreparedStatementIT.java index 93f99443..8e117bf7 100644 --- a/src/test/java/org/tarantool/jdbc/JdbcPreparedStatementIT.java +++ b/src/test/java/org/tarantool/jdbc/JdbcPreparedStatementIT.java @@ -189,7 +189,7 @@ public void testSetLong() throws SQLException { @Test public void testSetString() throws SQLException { makeHelper(String.class) - .setColumns(TntSqlType.CHAR, TntSqlType.VARCHAR, TntSqlType.TEXT) + .setColumns(TntSqlType.VARCHAR, TntSqlType.TEXT) .setValues(STRING_VALS) .testSetParameter(); } @@ -213,9 +213,7 @@ public void testSetDouble() throws SQLException { @Test public void testSetBigDecimal() throws SQLException { makeHelper(BigDecimal.class) - .setColumns(TntSqlType.DECIMAL, TntSqlType.DECIMAL_PREC, TntSqlType.DECIMAL_PREC_SCALE, - TntSqlType.NUMERIC, TntSqlType.NUMERIC_PREC, TntSqlType.NUMERIC_PREC_SCALE, - TntSqlType.NUM, TntSqlType.NUM_PREC, TntSqlType.NUM_PREC_SCALE) + .setColumns(TntSqlType.REAL, TntSqlType.FLOAT, TntSqlType.DOUBLE) .setValues(BIGDEC_VALS) .testSetParameter(); } @@ -224,7 +222,7 @@ public void testSetBigDecimal() throws SQLException { @Test public void testSetByteArray() throws SQLException { makeHelper(byte[].class) - .setColumns(TntSqlType.BLOB) + .setColumns(TntSqlType.SCALAR) .setValues(BINARY_VALS) .testSetParameter(); } diff --git a/src/test/java/org/tarantool/jdbc/JdbcResultSetIT.java b/src/test/java/org/tarantool/jdbc/JdbcResultSetIT.java index c81f4edf..d7c53e1d 100644 --- a/src/test/java/org/tarantool/jdbc/JdbcResultSetIT.java +++ b/src/test/java/org/tarantool/jdbc/JdbcResultSetIT.java @@ -85,9 +85,7 @@ public void testGetLongColumn() throws SQLException { @Test public void testGetBigDecimalColumn() throws SQLException { makeHelper(BigDecimal.class) - .setColumns(TntSqlType.DECIMAL, TntSqlType.DECIMAL_PREC, TntSqlType.DECIMAL_PREC_SCALE, - TntSqlType.NUMERIC, TntSqlType.NUMERIC_PREC, TntSqlType.NUMERIC_PREC_SCALE, - TntSqlType.NUM, TntSqlType.NUM_PREC, TntSqlType.NUM_PREC_SCALE) + .setColumns(TntSqlType.REAL, TntSqlType.FLOAT, TntSqlType.DOUBLE) .setValues(BIGDEC_VALS) .testGetColumn(); } @@ -111,7 +109,7 @@ public void testGetDoubleColumn() throws SQLException { @Test public void testGetStringColumn() throws SQLException { makeHelper(String.class) - .setColumns(TntSqlType.CHAR, TntSqlType.VARCHAR, TntSqlType.TEXT) + .setColumns(TntSqlType.VARCHAR, TntSqlType.TEXT) .setValues(STRING_VALS) .testGetColumn(); } @@ -119,7 +117,7 @@ public void testGetStringColumn() throws SQLException { @Test public void testGetByteArrayColumn() throws SQLException { makeHelper(byte[].class) - .setColumns(TntSqlType.BLOB) + .setColumns(TntSqlType.SCALAR) .setValues(BINARY_VALS) .testGetColumn(); } diff --git a/src/test/java/org/tarantool/jdbc/TntSqlType.java b/src/test/java/org/tarantool/jdbc/TntSqlType.java index 66d518c2..16f078ab 100644 --- a/src/test/java/org/tarantool/jdbc/TntSqlType.java +++ b/src/test/java/org/tarantool/jdbc/TntSqlType.java @@ -4,37 +4,19 @@ * Enumeration of SQL types recognizable by tarantool. */ public enum TntSqlType { - FLOAT("FLOAT"), + FLOAT("FLOAT"), DOUBLE("DOUBLE"), - REAL("REAL"), INT("INT"), INTEGER("INTEGER"), - DECIMAL("DECIMAL"), - DECIMAL_PREC("DECIMAL(20)"), - DECIMAL_PREC_SCALE("DECIMAL(20,10)"), - - NUMERIC("NUMERIC"), - NUMERIC_PREC("NUMERIC(20)"), - NUMERIC_PREC_SCALE("NUMERIC(20,10)"), - - NUM("NUM"), - NUM_PREC("NUM(20)"), - NUM_PREC_SCALE("NUM(20,10)"), - - CHAR("CHAR(128)"), - VARCHAR("VARCHAR(128)"), - TEXT("TEXT"), - BLOB("BLOB"); + SCALAR("SCALAR"); - //DATE("DATE"), - //TIME("TIME"), //TIMESTAMP("TIMESTAMP"), public String sqlType; From 2520b599a9b0c0b5450e9683f1fcb5a69ab608d6 Mon Sep 17 00:00:00 2001 From: nicktorwald Date: Thu, 14 Mar 2019 04:07:28 +0700 Subject: [PATCH 28/30] fix broken tests --- src/main/java/org/tarantool/JDBCBridge.java | 12 +- .../java/org/tarantool/SqlProtoUtils.java | 11 +- .../org/tarantool/TarantoolClientImpl.java | 12 +- .../org/tarantool/TarantoolClusterClient.java | 22 ++- .../org/tarantool/TarantoolConnection.java | 27 ++- .../tarantool/server/BinaryProtoUtils.java | 183 ++++++++++-------- ...ackage.java => TarantoolBinaryPacket.java} | 7 +- .../tarantool/ClientReconnectClusterIT.java | 2 + .../jdbc/JdbcExceptionHandlingTest.java | 6 +- 9 files changed, 155 insertions(+), 127 deletions(-) rename src/main/java/org/tarantool/server/{TarantoolBinaryPackage.java => TarantoolBinaryPacket.java} (83%) diff --git a/src/main/java/org/tarantool/JDBCBridge.java b/src/main/java/org/tarantool/JDBCBridge.java index e650c43e..c190310e 100644 --- a/src/main/java/org/tarantool/JDBCBridge.java +++ b/src/main/java/org/tarantool/JDBCBridge.java @@ -1,5 +1,8 @@ package org.tarantool; +import org.tarantool.jdbc.SQLResultSet; +import org.tarantool.server.TarantoolBinaryPacket; + import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; @@ -7,9 +10,6 @@ import java.util.ListIterator; import java.util.Map; -import org.tarantool.jdbc.SQLResultSet; -import org.tarantool.server.TarantoolBinaryPackage; - public class JDBCBridge { public static final JDBCBridge EMPTY = new JDBCBridge(Collections.emptyList(), Collections.>emptyList()); @@ -17,7 +17,7 @@ public class JDBCBridge { final Map columnsByName; final List> rows; - protected JDBCBridge(TarantoolBinaryPackage pack) { + protected JDBCBridge(TarantoolBinaryPacket pack) { this(SqlProtoUtils.getSQLMetadata(pack), SqlProtoUtils.getSQLData(pack)); } @@ -31,7 +31,7 @@ protected JDBCBridge(List sqlMetadata, List fields, List> values) { } public static Object execute(TarantoolConnection connection, String sql, Object ... params) { - TarantoolBinaryPackage pack = connection.sql(sql, params); + TarantoolBinaryPacket pack = connection.sql(sql, params); Long rowCount = SqlProtoUtils.getSqlRowCount(pack); if(rowCount == null) { return new SQLResultSet(new JDBCBridge(pack)); diff --git a/src/main/java/org/tarantool/SqlProtoUtils.java b/src/main/java/org/tarantool/SqlProtoUtils.java index 69fdf572..ccd0694c 100644 --- a/src/main/java/org/tarantool/SqlProtoUtils.java +++ b/src/main/java/org/tarantool/SqlProtoUtils.java @@ -1,17 +1,16 @@ package org.tarantool; -import org.tarantool.server.TarantoolBinaryPackage; +import org.tarantool.server.TarantoolBinaryPacket; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; public abstract class SqlProtoUtils { - public static List> readSqlResult(TarantoolBinaryPackage pack) { + public static List> readSqlResult(TarantoolBinaryPacket pack) { List> data = (List>) pack.getBody().get(Key.DATA.getId()); List> values = new ArrayList>(data.size()); @@ -26,12 +25,12 @@ public static List> readSqlResult(TarantoolBinaryPackage pac return values; } - public static List> getSQLData(TarantoolBinaryPackage pack) { + public static List> getSQLData(TarantoolBinaryPacket pack) { return (List>) pack.getBody().get(Key.DATA.getId()); } - public static List getSQLMetadata(TarantoolBinaryPackage pack) { + public static List getSQLMetadata(TarantoolBinaryPacket pack) { List> meta = (List>) pack.getBody().get(Key.SQL_METADATA.getId()); List values = new ArrayList(meta.size()); for (Map c : meta) { @@ -40,7 +39,7 @@ public static List getSQLMetadata(TarantoolBinaryPack return values; } - public static Long getSqlRowCount(TarantoolBinaryPackage pack) { + public static Long getSqlRowCount(TarantoolBinaryPacket pack) { Map info = (Map) pack.getBody().get(Key.SQL_INFO.getId()); Number rowCount; if (info != null && (rowCount = ((Number) info.get(Key.SQL_ROW_COUNT.getId()))) != null) { diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index e981dd2a..f492d361 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -1,7 +1,7 @@ package org.tarantool; import org.tarantool.server.BinaryProtoUtils; -import org.tarantool.server.TarantoolBinaryPackage; +import org.tarantool.server.TarantoolBinaryPacket; import org.tarantool.server.TarantoolInstanceConnection; import java.io.IOException; @@ -376,7 +376,7 @@ protected void readThread() { try { while (!Thread.currentThread().isInterrupted()) { try { - TarantoolBinaryPackage pack = readFromInstance(); + TarantoolBinaryPacket pack = readFromInstance(); CompletableFuture future = getFuture(pack); @@ -393,11 +393,11 @@ protected void readThread() { } } - protected CompletableFuture getFuture(TarantoolBinaryPackage pack) { + protected CompletableFuture getFuture(TarantoolBinaryPacket pack) { return futures.remove(pack.getSync()); } - protected TarantoolBinaryPackage readFromInstance() throws IOException, InterruptedException { + protected TarantoolBinaryPacket readFromInstance() throws IOException, InterruptedException { return BinaryProtoUtils.readPacket(currConnection.getReadChannel()); } @@ -445,7 +445,7 @@ protected void fail(CompletableFuture q, Exception e) { q.completeExceptionally(e); } - protected void complete(TarantoolBinaryPackage pack, CompletableFuture q) { + protected void complete(TarantoolBinaryPacket pack, CompletableFuture q) { if (q != null) { long code = pack.getCode(); if (code == 0) { @@ -463,7 +463,7 @@ protected void complete(TarantoolBinaryPackage pack, CompletableFuture q) { } } - protected void completeSql(CompletableFuture q, TarantoolBinaryPackage pack) { + protected void completeSql(CompletableFuture q, TarantoolBinaryPacket pack) { Long rowCount = SqlProtoUtils.getSqlRowCount(pack); if (rowCount!=null) { ((CompletableFuture) q).complete(rowCount); diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index 1949420a..7cc06ade 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -2,11 +2,19 @@ import org.tarantool.cluster.ClusterTopologyDiscoverer; import org.tarantool.cluster.ClusterTopologyFromShardDiscovererImpl; -import org.tarantool.server.*; - -import java.io.*; -import java.nio.channels.*; -import java.util.*; +import org.tarantool.server.BinaryProtoUtils; +import org.tarantool.server.TarantoolBinaryPacket; +import org.tarantool.server.TarantoolInstanceConnection; +import org.tarantool.server.TarantoolInstanceInfo; + +import java.io.IOException; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; @@ -155,7 +163,7 @@ protected void connect(InstanceConnectionProvider communicationProvider) throws } @Override - protected TarantoolBinaryPackage readFromInstance() throws IOException, InterruptedException { + protected TarantoolBinaryPacket readFromInstance() throws IOException, InterruptedException { readSelector.select(); @@ -169,7 +177,7 @@ protected TarantoolBinaryPackage readFromInstance() throws IOException, Interrup } @Override - protected CompletableFuture getFuture(TarantoolBinaryPackage pack) { + protected CompletableFuture getFuture(TarantoolBinaryPacket pack) { Long sync = pack.getSync(); if (!futuresSentToOldConnection.isEmpty()) { CompletableFuture oldConnectionFuture = futuresSentToOldConnection.remove(sync); diff --git a/src/main/java/org/tarantool/TarantoolConnection.java b/src/main/java/org/tarantool/TarantoolConnection.java index b3f2ca99..9bf08943 100644 --- a/src/main/java/org/tarantool/TarantoolConnection.java +++ b/src/main/java/org/tarantool/TarantoolConnection.java @@ -1,11 +1,13 @@ package org.tarantool; -import org.tarantool.server.*; +import org.tarantool.server.BinaryProtoUtils; +import org.tarantool.server.TarantoolBinaryPacket; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.*; +import java.net.Socket; +import java.net.SocketException; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; @@ -25,18 +27,25 @@ public TarantoolConnection(String username, String password, Socket socket) thro @Override protected List exec(Code code, Object... args) { - TarantoolBinaryPackage responsePackage = writeAndRead(code, args); + TarantoolBinaryPacket responsePackage = writeAndRead(code, args); return (List) responsePackage.getBody().get(Key.DATA.getId()); } - protected TarantoolBinaryPackage writeAndRead(Code code, Object... args) { + protected TarantoolBinaryPacket writeAndRead(Code code, Object... args) { try { - ByteBuffer packet = BinaryProtoUtils.createPacket(code, syncId.incrementAndGet(), null, args); + ByteBuffer packet = BinaryProtoUtils.createPacket( + initialRequestSize, + msgPackLite, + code, + syncId.incrementAndGet(), + null, + args + ); out.write(packet.array(), 0, packet.remaining()); out.flush(); - TarantoolBinaryPackage responsePackage = BinaryProtoUtils.readPacket(in); + TarantoolBinaryPacket responsePackage = BinaryProtoUtils.readPacket(in); Map headers = responsePackage.getHeaders(); Map body = responsePackage.getBody(); @@ -75,17 +84,17 @@ public void close() { @Override public Long update(String sql, Object... bind) { - TarantoolBinaryPackage pack = sql(sql, bind); + TarantoolBinaryPacket pack = sql(sql, bind); return SqlProtoUtils.getSqlRowCount(pack); } @Override public List> query(String sql, Object... bind) { - TarantoolBinaryPackage pack = sql(sql, bind); + TarantoolBinaryPacket pack = sql(sql, bind); return SqlProtoUtils.readSqlResult(pack); } - protected TarantoolBinaryPackage sql(String sql, Object[] bind) { + protected TarantoolBinaryPacket sql(String sql, Object[] bind) { return writeAndRead(Code.EXECUTE, Key.SQL_TEXT, sql, Key.SQL_BIND, bind); } diff --git a/src/main/java/org/tarantool/server/BinaryProtoUtils.java b/src/main/java/org/tarantool/server/BinaryProtoUtils.java index 4e0acb86..a7022e08 100644 --- a/src/main/java/org/tarantool/server/BinaryProtoUtils.java +++ b/src/main/java/org/tarantool/server/BinaryProtoUtils.java @@ -1,17 +1,19 @@ package org.tarantool.server; import org.tarantool.Base64; -import org.tarantool.ByteBufferInputStream; import org.tarantool.Code; import org.tarantool.CommunicationException; -import org.tarantool.CountInputStream; import org.tarantool.CountInputStreamImpl; import org.tarantool.Key; import org.tarantool.MsgPackLite; import org.tarantool.TarantoolException; -import java.io.*; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.Socket; +import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; @@ -26,27 +28,35 @@ public abstract class BinaryProtoUtils { private final static int DEFAULT_INITIAL_REQUEST_SIZE = 4096; - public static final String WELCOME = "Tarantool "; - - public static TarantoolBinaryPackage readPacket(InputStream inputStream) throws IOException { - int size = inputStream.read(); + private static final String WELCOME = "Tarantool "; + /** + * Reads tarantool binary protocol's packet from {@code inputStream} + * + * @param inputStream ready to use input stream + * @return Nonnull instance of packet. + * @throws IOException in case of any io-error. + */ + public static TarantoolBinaryPacket readPacket(InputStream inputStream) throws IOException { CountInputStreamImpl msgStream = new CountInputStreamImpl(inputStream); - Map headers = (Map) getMsgPackLite().unpack(msgStream); + int size = ((Number) getMsgPackLite().unpack(msgStream)).intValue(); + long mark = msgStream.getBytesRead(); + + Map headers = (Map) getMsgPackLite().unpack(msgStream); Map body = null; - if (msgStream.getBytesRead() < size) { + if (msgStream.getBytesRead() - mark < size) { body = (Map) getMsgPackLite().unpack(msgStream); } - return new TarantoolBinaryPackage(headers, body); + return new TarantoolBinaryPacket(headers, body); } /** * Connects to a tarantool node described by {@code socket}. Performs an authentication if required * - * @param socket a socket channel to tarantool node + * @param socket a socket channel to a tarantool node * @param username auth username * @param password auth password * @return object with information about a connection/ @@ -54,19 +64,16 @@ public static TarantoolBinaryPackage readPacket(InputStream inputStream) throws * @throws CommunicationException when welcome string is invalid * @throws TarantoolException in case of failed authentication */ - public static TarantoolInstanceConnectionMeta connect(Socket socket, String username, String password) throws IOException { + public static TarantoolInstanceConnectionMeta connect(Socket socket, + String username, + String password) throws IOException { byte[] inputBytes = new byte[64]; InputStream inputStream = socket.getInputStream(); inputStream.read(inputBytes); String firstLine = new String(inputBytes); - if (!firstLine.startsWith(WELCOME)) { - String errMsg = "Failed to connect to node " + socket.getRemoteSocketAddress().toString() + ":" + - " Welcome message should starts with tarantool but starts with '" + firstLine + "'"; - throw new CommunicationException(errMsg, new IllegalStateException("Invalid welcome packet")); - } - + assertCorrectWelcome(firstLine, socket.getRemoteSocketAddress()); String serverVersion = firstLine.substring(WELCOME.length()); inputStream.read(inputBytes); @@ -78,19 +85,15 @@ public static TarantoolInstanceConnectionMeta connect(Socket socket, String user os.write(authPacket.array(), 0, authPacket.remaining()); os.flush(); - TarantoolBinaryPackage responsePackage = readPacket(socket.getInputStream()); - Long code = (Long) responsePackage.getHeaders().get(Key.CODE.getId()); - if (code != 0) { - Object error = responsePackage.getBody().get(Key.ERROR.getId()); - throw new TarantoolException(code, error instanceof String ? (String) error : new String((byte[]) error)); - } + TarantoolBinaryPacket responsePackage = readPacket(socket.getInputStream()); + assertNoErrCode(responsePackage); } return new TarantoolInstanceConnectionMeta(salt, serverVersion); } /** - * Connects to a tarantool node described by {@code socketChannel}. Performs an authentication if required + * Connects to a tarantool node described by {@code socketChannel}. Performs an authentication if required. * * @param channel a socket channel to tarantool node. The channel have to be in blocking mode * @param username auth username @@ -100,35 +103,48 @@ public static TarantoolInstanceConnectionMeta connect(Socket socket, String user * @throws CommunicationException when welcome string is invalid * @throws TarantoolException in case of failed authentication */ - public static TarantoolInstanceConnectionMeta connect(SocketChannel channel, String username, String password) throws IOException { + public static TarantoolInstanceConnectionMeta connect(SocketChannel channel, + String username, + String password) throws IOException { ByteBuffer welcomeBytes = ByteBuffer.wrap(new byte[64]); channel.read(welcomeBytes); String firstLine = new String(welcomeBytes.array()); - if (!firstLine.startsWith(WELCOME)) { - String errMsg = "Failed to connect to node " + channel.getRemoteAddress().toString() + ":" + - " Welcome message should starts with tarantool but starts with '" + firstLine + "'"; - throw new CommunicationException(errMsg, new IllegalStateException("Invalid welcome packet")); - } + assertCorrectWelcome(firstLine, channel.getRemoteAddress()); String serverVersion = firstLine.substring(WELCOME.length()); welcomeBytes.clear(); channel.read(welcomeBytes); String salt = new String(welcomeBytes.array()); + if (username != null && password != null) { ByteBuffer authPacket = createAuthPacket(username, password, salt); writeFully(channel, authPacket); - TarantoolBinaryPackage authResponse = readPacket(((ReadableByteChannel) channel)); - Long code = (Long) authResponse.getHeaders().get(Key.CODE.getId()); - if (code != 0) { - Object error = authResponse.getBody().get(Key.ERROR.getId()); - throw new TarantoolException(code, error instanceof String ? (String) error : new String((byte[]) error)); - } + + TarantoolBinaryPacket authResponse = readPacket(channel); + assertNoErrCode(authResponse); } return new TarantoolInstanceConnectionMeta(salt, serverVersion); } + private static void assertCorrectWelcome(String firstLine, SocketAddress remoteAddress) { + if (!firstLine.startsWith(WELCOME)) { + String errMsg = "Failed to connect to node " + remoteAddress.toString() + ":" + + " Welcome message should starts with tarantool but starts with '" + firstLine + "'"; + throw new CommunicationException(errMsg, new IllegalStateException("Invalid welcome packet")); + } + } + + private static void assertNoErrCode(TarantoolBinaryPacket authResponse) { + Long code = (Long) authResponse.getHeaders().get(Key.CODE.getId()); + if (code != 0) { + Object error = authResponse.getBody().get(Key.ERROR.getId()); + String errorMsg = error instanceof String ? (String) error : new String((byte[]) error); + throw new TarantoolException(code, errorMsg); + } + } + public static void writeFully(OutputStream stream, ByteBuffer buffer) throws IOException { stream.write(buffer.array()); stream.flush(); @@ -149,12 +165,13 @@ public static void writeFully(SocketChannel channel, ByteBuffer buffer) throws I * Reads a tarantool's binary protocol package from the reader * * @param bufferReader readable channel that have to be in blocking mode - * @return - * @throws IOException - * @throws + * or instance of {@link ReadableViaSelectorChannel} + * @return tarantool binary protocol message wrapped by instance of {@link TarantoolBinaryPacket}. + * @throws IOException if any IO-error occurred during read from the channel + * @throws CommunicationException input stream bytes consitute msg pack message in wrong format. * @throws java.nio.channels.NonReadableChannelException – If this channel was not opened for reading */ - public static TarantoolBinaryPackage readPacket(ReadableByteChannel bufferReader) + public static TarantoolBinaryPacket readPacket(ReadableByteChannel bufferReader) throws CommunicationException, IOException { ByteBuffer buffer = ByteBuffer.allocate(LENGTH_OF_SIZE_MESSAGE); @@ -172,7 +189,8 @@ public static TarantoolBinaryPackage readPacket(ReadableByteChannel bufferReader if (!(unpackedHeaders instanceof Map)) { //noinspection ConstantConditions throw new CommunicationException("Error while unpacking headers of tarantool response: " + - "expected type Map but was " + unpackedHeaders != null ? unpackedHeaders.getClass().toString() : "null"); + "expected type Map but was " + + unpackedHeaders != null ? unpackedHeaders.getClass().toString() : "null"); } //noinspection unchecked (checked above) Map headers = (Map) unpackedHeaders; @@ -183,47 +201,19 @@ public static TarantoolBinaryPackage readPacket(ReadableByteChannel bufferReader if (!(unpackedBody instanceof Map)) { //noinspection ConstantConditions throw new CommunicationException("Error while unpacking body of tarantool response: " + - "expected type Map but was " + unpackedBody != null ? unpackedBody.getClass().toString() : "null"); - } - //noinspection unchecked (checked above) - body = (Map) unpackedBody; - } - - return new TarantoolBinaryPackage(headers, body); - } - - @Deprecated - private static TarantoolBinaryPackage readPacket(CountInputStream inputStream) throws IOException { - int size = ((Number) getMsgPackLite().unpack(inputStream)).intValue(); - - long mark = inputStream.getBytesRead(); - - Object unpackedHeaders = getMsgPackLite().unpack(inputStream); - if (!(unpackedHeaders instanceof Map)) { - //noinspection ConstantConditions - throw new CommunicationException("Error while unpacking headers of tarantool response: " + - "expected type Map but was " + unpackedHeaders != null ? unpackedHeaders.getClass().toString() : "null"); - } - //noinspection unchecked (checked above) - Map headers = (Map) unpackedHeaders; - - Map body = null; - if (inputStream.getBytesRead() - mark < size) { - Object unpackedBody = getMsgPackLite().unpack(inputStream); - if (!(unpackedBody instanceof Map)) { - //noinspection ConstantConditions - throw new CommunicationException("Error while unpacking body of tarantool response: " + - "expected type Map but was " + unpackedBody != null ? unpackedBody.getClass().toString() : "null"); + "expected type Map but was " + + unpackedBody != null ? unpackedBody.getClass().toString() : "null"); } //noinspection unchecked (checked above) body = (Map) unpackedBody; } - return new TarantoolBinaryPackage(headers, body); + return new TarantoolBinaryPacket(headers, body); } - - public static ByteBuffer createAuthPacket(String username, final String password, String salt) throws IOException { + public static ByteBuffer createAuthPacket(String username, + final String password, + String salt) throws IOException { final MessageDigest sha1; try { sha1 = MessageDigest.getInstance("SHA-1"); @@ -247,22 +237,33 @@ public static ByteBuffer createAuthPacket(String username, final String password } auth.add(p); - // this was the default implementation -// return createPacket(initialRequestSize, Code.AUTH, 0L, null, Key.USER_NAME, username, Key.TUPLE, auth); - return createPacket(DEFAULT_INITIAL_REQUEST_SIZE, Code.AUTH, 0L, null, Key.USER_NAME, username, Key.TUPLE, auth); + return createPacket(DEFAULT_INITIAL_REQUEST_SIZE, Code.AUTH, 0L, null, + Key.USER_NAME, username, Key.TUPLE, auth); } public static ByteBuffer createPacket(Code code, Long syncId, Long schemaId, Object... args) throws IOException { return createPacket(DEFAULT_INITIAL_REQUEST_SIZE, code, syncId, schemaId, args); } - public static ByteBuffer createPacket(int initialRequestSize, Code code, Long syncId, Long schemaId, Object... args) throws IOException { + public static ByteBuffer createPacket(int initialRequestSize, + Code code, + Long syncId, + Long schemaId, + Object... args) throws IOException { + return createPacket(initialRequestSize, getMsgPackLite(), code, syncId, schemaId, args); + } + public static ByteBuffer createPacket(int initialRequestSize, + MsgPackLite msgPackLite, + Code code, + Long syncId, + Long schemaId, + Object... args) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(initialRequestSize); bos.write(new byte[5]); DataOutputStream ds = new DataOutputStream(bos); - Map header = new EnumMap(Key.class); - Map body = new EnumMap(Key.class); + Map header = new EnumMap<>(Key.class); + Map body = new EnumMap<>(Key.class); header.put(Key.CODE, code); header.put(Key.SYNC, syncId); if (schemaId != null) { @@ -274,16 +275,26 @@ public static ByteBuffer createPacket(int initialRequestSize, Code code, Long sy body.put((Key) args[i], value); } } - getMsgPackLite().pack(header, ds); - getMsgPackLite().pack(body, ds); + msgPackLite.pack(header, ds); + msgPackLite.pack(body, ds); ds.flush(); - ByteBuffer buffer = ByteBuffer.wrap(bos.toByteArray(), 0, bos.size()); + ByteBuffer buffer = bos.toByteBuffer(); buffer.put(0, (byte) 0xce); buffer.putInt(1, bos.size() - 5); return buffer; } + private static class ByteArrayOutputStream extends java.io.ByteArrayOutputStream { + public ByteArrayOutputStream(int size) { + super(size); + } + + ByteBuffer toByteBuffer() { + return ByteBuffer.wrap(buf, 0, count); + } + } + private static MsgPackLite getMsgPackLite() { return MsgPackLite.INSTANCE; } -} +} \ No newline at end of file diff --git a/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java b/src/main/java/org/tarantool/server/TarantoolBinaryPacket.java similarity index 83% rename from src/main/java/org/tarantool/server/TarantoolBinaryPackage.java rename to src/main/java/org/tarantool/server/TarantoolBinaryPacket.java index 8b52816b..e6421b6f 100644 --- a/src/main/java/org/tarantool/server/TarantoolBinaryPackage.java +++ b/src/main/java/org/tarantool/server/TarantoolBinaryPacket.java @@ -2,19 +2,18 @@ import org.tarantool.Key; -import java.util.Collections; import java.util.Map; -public class TarantoolBinaryPackage { +public class TarantoolBinaryPacket { private final Map headers; private final Map body; - public TarantoolBinaryPackage(Map headers, Map body) { + public TarantoolBinaryPacket(Map headers, Map body) { this.headers = headers; this.body = body; } - public TarantoolBinaryPackage(Map headers) { + public TarantoolBinaryPacket(Map headers) { this.headers = headers; body = null; } diff --git a/src/test/java/org/tarantool/ClientReconnectClusterIT.java b/src/test/java/org/tarantool/ClientReconnectClusterIT.java index 7d552186..2c8abac7 100644 --- a/src/test/java/org/tarantool/ClientReconnectClusterIT.java +++ b/src/test/java/org/tarantool/ClientReconnectClusterIT.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; @@ -118,6 +119,7 @@ private TarantoolClusterClient makeClient(String...addrs) { @Test + @Disabled("Incomplete implementation") void testUpdateNodeList() { control.start(SRV1); control.start(SRV2); diff --git a/src/test/java/org/tarantool/jdbc/JdbcExceptionHandlingTest.java b/src/test/java/org/tarantool/jdbc/JdbcExceptionHandlingTest.java index b680ce0f..7fc6493e 100644 --- a/src/test/java/org/tarantool/jdbc/JdbcExceptionHandlingTest.java +++ b/src/test/java/org/tarantool/jdbc/JdbcExceptionHandlingTest.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.function.ThrowingConsumer; import org.tarantool.CommunicationException; import org.tarantool.TarantoolConnection; -import org.tarantool.server.TarantoolBinaryPackage; +import org.tarantool.server.TarantoolBinaryPacket; import java.io.IOException; import java.net.InetSocketAddress; @@ -33,8 +33,8 @@ import static org.mockito.Mockito.verify; import static org.tarantool.jdbc.SQLDatabaseMetadata.FORMAT_IDX; import static org.tarantool.jdbc.SQLDatabaseMetadata.INDEX_FORMAT_IDX; -import static org.tarantool.jdbc.SQLDatabaseMetadata.SPACE_ID_IDX; import static org.tarantool.jdbc.SQLDatabaseMetadata.SPACES_MAX; +import static org.tarantool.jdbc.SQLDatabaseMetadata.SPACE_ID_IDX; import static org.tarantool.jdbc.SQLDatabaseMetadata._VINDEX; import static org.tarantool.jdbc.SQLDatabaseMetadata._VSPACE; import static org.tarantool.jdbc.SQLDriver.PROP_SOCKET_TIMEOUT; @@ -298,7 +298,7 @@ class TestTarantoolConnection extends TarantoolConnection { super(null, null, mock(Socket.class)); } @Override - protected TarantoolBinaryPackage sql(String sql, Object[] bind) { + protected TarantoolBinaryPacket sql(String sql, Object[] bind) { return super.sql(sql, bind); } } From df1929550309097528e9ac07b34437ce3a160f10 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Thu, 14 Mar 2019 03:48:08 +0300 Subject: [PATCH 29/30] Move readPacket method into TarantoolInstanceConnection --- .../org/tarantool/TestTarantoolClient.java | 4 +-- .../org/tarantool/TarantoolClientImpl.java | 7 ++-- .../org/tarantool/TarantoolClusterClient.java | 5 ++- .../server/TarantoolInstanceConnection.java | 12 ++++--- .../tarantool/ClientReconnectClusterIT.java | 32 ++++++++++++++++--- 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/it/java/org/tarantool/TestTarantoolClient.java b/src/it/java/org/tarantool/TestTarantoolClient.java index a9294fe9..c45fac92 100644 --- a/src/it/java/org/tarantool/TestTarantoolClient.java +++ b/src/it/java/org/tarantool/TestTarantoolClient.java @@ -1,6 +1,6 @@ package org.tarantool; -import org.tarantool.server.TarantoolBinaryPackage; +import org.tarantool.server.TarantoolBinaryPacket; import java.io.IOException; import java.nio.ByteBuffer; @@ -71,7 +71,7 @@ protected void reconnect(int retry, Throwable lastError) { } @Override - protected void complete(TarantoolBinaryPackage pack, CompletableFuture q) { + protected void complete(TarantoolBinaryPacket pack, CompletableFuture q) { super.complete(pack, q); Long code = pack.getCode(); if (code != 0) { diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java index f492d361..1e1eff10 100644 --- a/src/main/java/org/tarantool/TarantoolClientImpl.java +++ b/src/main/java/org/tarantool/TarantoolClientImpl.java @@ -398,7 +398,8 @@ protected CompletableFuture getFuture(TarantoolBinaryPacket pack) { } protected TarantoolBinaryPacket readFromInstance() throws IOException, InterruptedException { - return BinaryProtoUtils.readPacket(currConnection.getReadChannel()); +// return BinaryProtoUtils.readPacket(currConnection.getReadChannel()); + return currConnection.readPacket(); } protected void writeThread() { @@ -436,8 +437,8 @@ protected void writeThread() { protected void sendToInstance(ByteBuffer writerBuffer) throws IOException { -// communicationProvider.writeBuffer(writerBuffer); - BinaryProtoUtils.writeFully(currConnection.getChannel(), writerBuffer); +// BinaryProtoUtils.writeFully(currConnection.getChannel(), writerBuffer); + currConnection.writeBuffer(writerBuffer); } diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java index 7cc06ade..de119af3 100644 --- a/src/main/java/org/tarantool/TarantoolClusterClient.java +++ b/src/main/java/org/tarantool/TarantoolClusterClient.java @@ -104,6 +104,7 @@ protected Collection refreshServerList() { if (state.isAtState(StateHelper.ALIVE)) { stopIO(); + //todo add wait for reconnect here futuresSentToOldConnection.values() .forEach(f -> { @@ -170,10 +171,8 @@ protected TarantoolBinaryPacket readFromInstance() throws IOException, Interrupt SelectionKey selectedKey = readSelector.selectedKeys().iterator().next(); TarantoolInstanceConnection connection = (TarantoolInstanceConnection) selectedKey.attachment(); - ReadableByteChannel readChannel = connection - .getReadChannel(); - return BinaryProtoUtils.readPacket(readChannel); + return connection.readPacket(); } @Override diff --git a/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java b/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java index 2a514625..8ab6a23a 100644 --- a/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java +++ b/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java @@ -4,7 +4,7 @@ import java.io.Closeable; import java.io.IOException; -import java.nio.channels.ReadableByteChannel; +import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class TarantoolInstanceConnection implements Closeable { @@ -74,13 +74,17 @@ public SocketChannel getChannel() { return channel; } - public ReadableByteChannel getReadChannel() { - return readChannel; + public void writeBuffer(ByteBuffer writerBuffer) throws IOException { + BinaryProtoUtils.writeFully(getChannel(), writerBuffer); + } + + public TarantoolBinaryPacket readPacket() throws IOException { + return BinaryProtoUtils.readPacket(readChannel); } private void closeConnection() { try { - readChannel.close();//also closes the channel + readChannel.close();//also closes this.channel } catch (IOException ignored) { } diff --git a/src/test/java/org/tarantool/ClientReconnectClusterIT.java b/src/test/java/org/tarantool/ClientReconnectClusterIT.java index 2c8abac7..67046bb4 100644 --- a/src/test/java/org/tarantool/ClientReconnectClusterIT.java +++ b/src/test/java/org/tarantool/ClientReconnectClusterIT.java @@ -5,10 +5,16 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.tarantool.cluster.ClusterTopologyDiscoverer; +import org.tarantool.server.TarantoolInstanceInfo; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -129,21 +135,37 @@ void testUpdateNodeList() { control.waitStarted(SRV2); control.waitStarted(SRV3); + + String testSchemaCreateScript = "return box.schema.space.create('rr_test').id, " + + "box.space.rr_test:create_index('primary').id"; + control.executeCommand(testSchemaCreateScript, SRV1); + String srv1_address = "localhost:" + PORTS[0]; String srv2_address = "127.0.0.1:" + PORTS[1]; String srv3_address = "localhost:" + PORTS[2]; + + String INFO_FUNCTION_NAME = "returnAddrsExceptSrv1"; + String INFO_FUNCTION_SCRIPT = + "function " + INFO_FUNCTION_NAME + "() return {'" + srv2_address + "', '" + srv3_address + "'} end"; + + control.executeCommand(INFO_FUNCTION_SCRIPT, SRV1); + control.waitReplication(SRV1, TIMEOUT); + final TarantoolClusterClient client = makeClient( srv1_address, srv2_address); - - List ids = client.syncOps().eval( - "return box.schema.space.create('rr_test').id, " + - "box.space.rr_test:create_index('primary').id"); + testSchemaCreateScript); -// client.ref //todo + List newInstances = Stream.of(srv2_address, srv3_address) + .map(TarantoolInstanceInfo::create) + .collect(Collectors.toList()); + + ClusterTopologyDiscoverer discovererMock = Mockito.mock(ClusterTopologyDiscoverer.class); + Mockito.when(discovererMock.discoverTarantoolInstances(Matchers.anyInt())).thenReturn(newInstances); + } } From 85b89944ffae40b1491a80c34fe961356ee67e92 Mon Sep 17 00:00:00 2001 From: dponomarev Date: Thu, 14 Mar 2019 03:53:38 +0300 Subject: [PATCH 30/30] add a todo --- .../java/org/tarantool/server/TarantoolInstanceConnection.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java b/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java index 8ab6a23a..611c3cfa 100644 --- a/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java +++ b/src/main/java/org/tarantool/server/TarantoolInstanceConnection.java @@ -58,6 +58,7 @@ public static TarantoolInstanceConnection connect(TarantoolInstanceInfo tarantoo return new TarantoolInstanceConnection(tarantoolInstanceInfo, meta, channel); } catch (IOException e) { + //todo add toString method to TarantoolInstanceConnection to describe failed attempt to connect properly throw new IOException("IOException occurred while connecting to node " + tarantoolInstanceInfo, e); } }