diff --git a/pom.xml b/pom.xml
index 76d7974e..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
+
+
+
+
+
+
@@ -134,6 +193,12 @@
${junit.jupiter.version}
test
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit.jupiter.version}
+ test
+
org.mockito
mockito-all
@@ -146,6 +211,12 @@
1.23
test
+
+ org.openjdk.jmh
+ jmh-core
+ ${jmh.version}
+ test
+
diff --git a/src/it/java/org/tarantool/TestTarantoolClient.java b/src/it/java/org/tarantool/TestTarantoolClient.java
index 24ec0e39..c45fac92 100644
--- a/src/it/java/org/tarantool/TestTarantoolClient.java
+++ b/src/it/java/org/tarantool/TestTarantoolClient.java
@@ -1,9 +1,9 @@
package org.tarantool;
+import org.tarantool.server.TarantoolBinaryPacket;
+
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;
@@ -33,8 +33,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(InstanceConnectionProvider nodeComm, TarantoolClientConfig options) {
+ super(nodeComm, options);
Thread t = new Thread(new Runnable() {
@Override
public void run() {
@@ -55,16 +55,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 +71,14 @@ protected void reconnect(int retry, Throwable lastError) {
}
@Override
- protected void complete(long code, CompletableFuture> q) {
- super.complete(code, q);
+ protected void complete(TarantoolBinaryPacket 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 {
@@ -102,21 +92,12 @@ public static void main(String[] args) throws IOException, InterruptedException,
config.sharedBufferSize = 128;
//config.sharedBufferSize = 0;
- SocketChannelProvider socketChannelProvider = new SocketChannelProvider() {
- @Override
- public SocketChannel get(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);
+
+ InstanceConnectionProvider nodeComm =
+ new SingleInstanceConnectionProvider("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/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/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/InstanceConnectionProvider.java b/src/main/java/org/tarantool/InstanceConnectionProvider.java
new file mode 100644
index 00000000..e6c40109
--- /dev/null
+++ b/src/main/java/org/tarantool/InstanceConnectionProvider.java
@@ -0,0 +1,13 @@
+package org.tarantool;
+
+import org.tarantool.server.*;
+
+import java.io.*;
+import java.nio.*;
+
+public interface InstanceConnectionProvider {
+
+ TarantoolInstanceConnection connect() throws IOException;
+
+ String getDescription();
+}
diff --git a/src/main/java/org/tarantool/JDBCBridge.java b/src/main/java/org/tarantool/JDBCBridge.java
index b31af64d..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,8 +10,6 @@
import java.util.ListIterator;
import java.util.Map;
-import org.tarantool.jdbc.SQLResultSet;
-
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(TarantoolBinaryPacket 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();
+ TarantoolBinaryPacket 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/RoundRobinInstanceConnectionProvider.java b/src/main/java/org/tarantool/RoundRobinInstanceConnectionProvider.java
new file mode 100644
index 00000000..a492de25
--- /dev/null
+++ b/src/main/java/org/tarantool/RoundRobinInstanceConnectionProvider.java
@@ -0,0 +1,137 @@
+package org.tarantool;
+
+import org.tarantool.server.*;
+
+import java.io.*;
+import java.util.*;
+import java.util.stream.*;
+
+public class RoundRobinInstanceConnectionProvider implements InstanceConnectionProvider {
+
+ /** Timeout to establish socket connection with an individual server. */
+ private final int timeout; // 0 is infinite.
+
+ private final String clusterUsername;
+ private final String clusterPassword;
+
+ private TarantoolInstanceConnection currentConnection;
+
+ private TarantoolInstanceInfo[] nodes;
+ private int pos = 0;
+
+ 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");
+ }
+
+ clusterUsername = username;
+ clusterPassword = password;
+
+ setNodes(slaveHosts);
+ }
+
+ 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 instanceAddresses) {
+ if (instanceAddresses == null) {
+ throw new IllegalArgumentException("instanceAddresses can not be null");
+ }
+
+ this.nodes = (TarantoolInstanceInfo[]) instanceAddresses.toArray();
+ pos = 0;
+ }
+
+
+ /**
+ * @return Non-empty list of round-robined nodes
+ */
+ public TarantoolInstanceInfo[] getNodes() {
+ return nodes;
+ }
+
+ /**
+ * 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;
+ 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());
+ }
+
+ /**
+ * @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;
+ }
+
+
+ @Override
+ public TarantoolInstanceConnection connect() {
+ currentConnection = connectNextNode();
+ return currentConnection;
+ }
+
+ @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 d16c6bf4..e2d77787 100644
--- a/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java
+++ b/src/main/java/org/tarantool/RoundRobinSocketProviderImpl.java
@@ -1,49 +1,71 @@
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.*;
+import java.util.List;
/**
* 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 TarantoolInstanceInfo[] nodes;
+
+ /** Current position within {@link #nodes} array. */
private int pos;
/**
* 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, String username, String password) {
+ if (slaveHosts == null || slaveHosts.length < 1) {
+ throw new IllegalArgumentException("slave hosts is null ot empty");
+ }
- this.addrs = Arrays.copyOf(addrs, addrs.length);
+ updateNodes(slaveHosts, username, password);
+ }
- sockAddrs = new InetSocketAddress[this.addrs.length];
+ 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;
+ }
- for (int i = 0; i < this.addrs.length; i++) {
- sockAddrs[i] = parseAddress(this.addrs[i]);
+
+ 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 Configured addresses in a form of [host]:[port].
+ * @return Non-empty list of round-robined nodes
*/
- public String[] getAddresses() {
- return this.addrs;
+ public TarantoolInstanceInfo[] getNodes() {
+ return nodes;
}
/**
@@ -75,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.
@@ -98,17 +120,15 @@ public int getRetriesLimit() {
/** {@inheritDoc} */
@Override
- public SocketChannel get(int retryNumber, Throwable lastError) {
- if (areRetriesExhausted(retryNumber)) {
- throw new CommunicationException("Connection retries exceeded.", lastError);
- }
+ public SocketChannel get() {
int attempts = getAddressCount();
long deadline = System.currentTimeMillis() + timeout * attempts;
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,42 +161,20 @@ 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;
}
- /**
- * 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 TarantoolInstanceInfo 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
new file mode 100644
index 00000000..119def39
--- /dev/null
+++ b/src/main/java/org/tarantool/SimpleSocketChannelProvider.java
@@ -0,0 +1,30 @@
+package org.tarantool;
+
+import org.tarantool.server.*;
+
+import java.io.*;
+import java.net.*;
+import java.nio.channels.*;
+
+public class SimpleSocketChannelProvider implements SocketChannelProvider{
+
+ private final TarantoolInstanceInfo tarantoolInstanceInfo;
+
+ public SimpleSocketChannelProvider(InetSocketAddress socketAddress, String username, String password) {
+ this.tarantoolInstanceInfo = TarantoolInstanceInfo.create(socketAddress, username, password);
+ }
+
+ public SimpleSocketChannelProvider(String address, String username, String password) {
+ this.tarantoolInstanceInfo = TarantoolInstanceInfo.create(address, username, password);
+ }
+
+ @Override
+ public SocketChannel get() {
+ try {
+ return SocketChannel.open(tarantoolInstanceInfo.getSocketAddress());
+ } catch (IOException e) {
+ String msg = "Exception occurred while connecting to instance " + tarantoolInstanceInfo;
+ throw new CommunicationException(msg, e);
+ }
+ }
+}
diff --git a/src/main/java/org/tarantool/SingleInstanceConnectionProvider.java b/src/main/java/org/tarantool/SingleInstanceConnectionProvider.java
new file mode 100644
index 00000000..e6a9742c
--- /dev/null
+++ b/src/main/java/org/tarantool/SingleInstanceConnectionProvider.java
@@ -0,0 +1,37 @@
+package org.tarantool;
+
+import org.tarantool.server.TarantoolInstanceConnection;
+import org.tarantool.server.TarantoolInstanceInfo;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+public class SingleInstanceConnectionProvider implements InstanceConnectionProvider {
+
+ private final TarantoolInstanceInfo tarantoolInstanceInfo;
+
+ private TarantoolInstanceConnection nodeConnection;
+
+ public SingleInstanceConnectionProvider(InetSocketAddress socketAddress, String username, String password) {
+ this.tarantoolInstanceInfo = TarantoolInstanceInfo.create(socketAddress, username, password);
+ }
+
+ public SingleInstanceConnectionProvider(String address, String username, String password) {
+ this.tarantoolInstanceInfo = TarantoolInstanceInfo.create(address, username, password);
+ }
+
+ @Override
+ public TarantoolInstanceConnection connect() throws IOException {
+ nodeConnection = TarantoolInstanceConnection.connect(tarantoolInstanceInfo);
+ return nodeConnection;
+ }
+
+ @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 09112dec..49224667 100644
--- a/src/main/java/org/tarantool/SocketChannelProvider.java
+++ b/src/main/java/org/tarantool/SocketChannelProvider.java
@@ -7,9 +7,7 @@ public interface SocketChannelProvider {
/**
* 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 get();
}
diff --git a/src/main/java/org/tarantool/SqlProtoUtils.java b/src/main/java/org/tarantool/SqlProtoUtils.java
new file mode 100644
index 00000000..ccd0694c
--- /dev/null
+++ b/src/main/java/org/tarantool/SqlProtoUtils.java
@@ -0,0 +1,50 @@
+package org.tarantool;
+
+import org.tarantool.server.TarantoolBinaryPacket;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public abstract class SqlProtoUtils {
+
+
+ public static List