Skip to content

Commit 60048f2

Browse files
committed
jdbc: add connection timeout configuration and handling
Added connection property `socketTimeout` to allow user control over network timeout before actual connection is returned by the driver. This is only done for default socket provider. The default timeout is left to be infinite. Implemented `Connection.setNetworkTimeout` API to make it possible to change the maximum amount of time to wait for server replies after the connection is established. The connection that has timed out is forced to close. New subsequent operations requested on such connection must fail right away. The corresponding checks are embedded into relevant APIs. Closes tarantool#38
1 parent db5680b commit 60048f2

13 files changed

+1114
-172
lines changed

src/main/java/org/tarantool/TarantoolConnection.java

+21
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.io.InputStream;
55
import java.io.OutputStream;
66
import java.net.Socket;
7+
import java.net.SocketException;
78
import java.nio.ByteBuffer;
89
import java.util.List;
910
import java.util.Map;
@@ -80,4 +81,24 @@ protected void sql(String sql, Object[] bind) {
8081
public boolean isClosed() {
8182
return socket.isClosed();
8283
}
84+
85+
/**
86+
* Sets given timeout value on underlying socket.
87+
*
88+
* @param timeout Timeout in milliseconds.
89+
* @throws SocketException If failed.
90+
*/
91+
public void setSocketTimeout(int timeout) throws SocketException {
92+
socket.setSoTimeout(timeout);
93+
}
94+
95+
/**
96+
* Retrieves timeout value from underlying socket.
97+
*
98+
* @return Timeout in milliseconds.
99+
* @throws SocketException If failed.
100+
*/
101+
public int getSocketTimeout() throws SocketException {
102+
return socket.getSoTimeout();
103+
}
83104
}

src/main/java/org/tarantool/jdbc/SQLConnection.java

+173-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package org.tarantool.jdbc;
22

3+
import java.io.IOException;
4+
import java.net.InetSocketAddress;
5+
import java.net.Socket;
6+
import java.net.SocketException;
37
import java.sql.Array;
48
import java.sql.Blob;
59
import java.sql.CallableStatement;
@@ -8,6 +12,7 @@
812
import java.sql.DatabaseMetaData;
913
import java.sql.NClob;
1014
import java.sql.PreparedStatement;
15+
import java.sql.ResultSet;
1116
import java.sql.SQLClientInfoException;
1217
import java.sql.SQLException;
1318
import java.sql.SQLFeatureNotSupportedException;
@@ -16,32 +21,98 @@
1621
import java.sql.Savepoint;
1722
import java.sql.Statement;
1823
import java.sql.Struct;
24+
import java.util.Arrays;
25+
import java.util.List;
1926
import java.util.Map;
2027
import java.util.Properties;
2128
import java.util.concurrent.Executor;
2229

30+
import org.tarantool.CommunicationException;
31+
import org.tarantool.JDBCBridge;
2332
import org.tarantool.TarantoolConnection;
2433

34+
import static org.tarantool.jdbc.SQLDriver.PROP_HOST;
35+
import static org.tarantool.jdbc.SQLDriver.PROP_PASSWORD;
36+
import static org.tarantool.jdbc.SQLDriver.PROP_PORT;
37+
import static org.tarantool.jdbc.SQLDriver.PROP_SOCKET_TIMEOUT;
38+
import static org.tarantool.jdbc.SQLDriver.PROP_USER;
39+
2540
@SuppressWarnings("Since15")
2641
public class SQLConnection implements Connection {
27-
final TarantoolConnection connection;
42+
private final TarantoolConnection connection;
2843
final String url;
2944
final Properties properties;
3045

31-
public SQLConnection(TarantoolConnection connection, String url, Properties properties) {
32-
this.connection = connection;
46+
SQLConnection(String url, Properties properties) throws SQLException {
3347
this.url = url;
3448
this.properties = properties;
49+
50+
String user = properties.getProperty(PROP_USER);
51+
String pass = properties.getProperty(PROP_PASSWORD);
52+
Socket socket = null;
53+
try {
54+
socket = getConnectedSocket();
55+
this.connection = makeConnection(user, pass, socket);
56+
} catch (Exception e) {
57+
if (socket != null) {
58+
try {
59+
socket.close();
60+
} catch (IOException ignored) {
61+
// No-op.
62+
}
63+
}
64+
if (e instanceof SQLException)
65+
throw (SQLException)e;
66+
throw new SQLException("Couldn't initiate connection using " + SQLDriver.diagProperties(properties), e);
67+
}
68+
}
69+
70+
protected Socket getConnectedSocket() throws SQLException {
71+
Socket socket = makeSocket();
72+
int timeout = Integer.parseInt(properties.getProperty(PROP_SOCKET_TIMEOUT));
73+
String host = properties.getProperty(PROP_HOST);
74+
int port = Integer.parseInt(properties.getProperty(PROP_PORT));
75+
try {
76+
socket.connect(new InetSocketAddress(host, port), timeout);
77+
} catch (IOException e) {
78+
throw new SQLException("Couldn't connect to " + host + ":" + port, e);
79+
}
80+
// Setup socket further.
81+
if (timeout > 0) {
82+
try {
83+
socket.setSoTimeout(timeout);
84+
} catch (SocketException e) {
85+
try {
86+
socket.close();
87+
} catch (IOException ignored) {
88+
// No-op.
89+
}
90+
throw new SQLException("Couldn't set socket timeout. timeout=" + timeout, e);
91+
}
92+
}
93+
return socket;
94+
}
95+
96+
protected Socket makeSocket() {
97+
return new Socket();
98+
}
99+
100+
protected TarantoolConnection makeConnection(String user, String pass, Socket socket) throws IOException {
101+
return new TarantoolConnection(user, pass, socket) {{
102+
msgPackLite = SQLMsgPackLite.INSTANCE;
103+
}};
35104
}
36105

37106
@Override
38107
public Statement createStatement() throws SQLException {
39-
return new SQLStatement(connection, this);
108+
checkNotClosed();
109+
return new SQLStatement(this);
40110
}
41111

42112
@Override
43113
public PreparedStatement prepareStatement(String sql) throws SQLException {
44-
return new SQLPreparedStatement(connection, this, sql);
114+
checkNotClosed();
115+
return new SQLPreparedStatement(this, sql);
45116
}
46117

47118
@Override
@@ -89,6 +160,7 @@ public boolean isClosed() throws SQLException {
89160

90161
@Override
91162
public DatabaseMetaData getMetaData() throws SQLException {
163+
checkNotClosed();
92164
return new SQLDatabaseMetadata(this);
93165
}
94166

@@ -293,15 +365,28 @@ public void abort(Executor executor) throws SQLException {
293365

294366
@Override
295367
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
296-
throw new SQLFeatureNotSupportedException();
368+
checkNotClosed();
369+
370+
if (milliseconds < 0)
371+
throw new SQLException("Network timeout cannot be negative.");
372+
373+
try {
374+
connection.setSocketTimeout(milliseconds);
375+
} catch (SocketException e) {
376+
throw new SQLException("Failed to set socket timeout: timeout=" + milliseconds, e);
377+
}
297378
}
298379

299380
@Override
300381
public int getNetworkTimeout() throws SQLException {
301-
throw new SQLFeatureNotSupportedException();
382+
checkNotClosed();
383+
try {
384+
return connection.getSocketTimeout();
385+
} catch (SocketException e) {
386+
throw new SQLException("Failed to retrieve socket timeout", e);
387+
}
302388
}
303389

304-
305390
@Override
306391
public <T> T unwrap(Class<T> iface) throws SQLException {
307392
throw new SQLFeatureNotSupportedException();
@@ -311,4 +396,84 @@ public <T> T unwrap(Class<T> iface) throws SQLException {
311396
public boolean isWrapperFor(Class<?> iface) throws SQLException {
312397
throw new SQLFeatureNotSupportedException();
313398
}
399+
400+
protected Object execute(String sql, Object ... args) throws SQLException {
401+
checkNotClosed();
402+
try {
403+
return JDBCBridge.execute(connection, sql, args);
404+
} catch (Exception e) {
405+
handleException(e);
406+
throw new SQLException(formatError(sql, args), e);
407+
}
408+
}
409+
410+
protected ResultSet executeQuery(String sql, Object ... args) throws SQLException {
411+
checkNotClosed();
412+
try {
413+
return new SQLResultSet(JDBCBridge.query(connection, sql, args));
414+
} catch (Exception e) {
415+
handleException(e);
416+
throw new SQLException(formatError(sql, args), e);
417+
}
418+
}
419+
420+
protected int executeUpdate(String sql, Object ... args) throws SQLException {
421+
checkNotClosed();
422+
try {
423+
return JDBCBridge.update(connection, sql, args);
424+
} catch (Exception e) {
425+
handleException(e);
426+
throw new SQLException(formatError(sql, args), e);
427+
}
428+
}
429+
430+
protected List<?> nativeSelect(Integer space, Integer index, List<?> key, int offset, int limit, int iterator)
431+
throws SQLException {
432+
checkNotClosed();
433+
try {
434+
return connection.select(space, index, key, offset, limit, iterator);
435+
} catch (Exception e) {
436+
handleException(e);
437+
throw new SQLException(e);
438+
}
439+
}
440+
441+
protected String getServerVersion() {
442+
return connection.getServerVersion();
443+
}
444+
445+
/**
446+
* @throws SQLException If connection is closed.
447+
*/
448+
protected void checkNotClosed() throws SQLException {
449+
if (isClosed())
450+
throw new SQLException("Connection is closed.");
451+
}
452+
453+
/**
454+
* Inspects passed exception and closes the connection if appropriate.
455+
*
456+
* @param e Exception to process.
457+
*/
458+
private void handleException(Exception e) {
459+
if (CommunicationException.class.isAssignableFrom(e.getClass()) ||
460+
IOException.class.isAssignableFrom(e.getClass())) {
461+
try {
462+
close();
463+
} catch (SQLException ignored) {
464+
// No-op.
465+
}
466+
}
467+
}
468+
469+
/**
470+
* Provides error message that contains parameters of failed SQL statement.
471+
*
472+
* @param sql SQL Text.
473+
* @param params Parameters of the SQL statement.
474+
* @return Formatted error message.
475+
*/
476+
private static String formatError(String sql, Object ... params) {
477+
return "Failed to execute SQL: " + sql + ", params: " + Arrays.deepToString(params);
478+
}
314479
}

0 commit comments

Comments
 (0)