Skip to content

Commit b8ff396

Browse files
authored
Merge pull request #468 from lutovich/1.4-revert-timeout
Revert "Use connect timeout in Bolt and TLS handshake"
2 parents b1076c6 + 4066ccf commit b8ff396

File tree

6 files changed

+36
-340
lines changed

6 files changed

+36
-340
lines changed

driver/src/main/java/org/neo4j/driver/internal/net/ChannelFactory.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,31 @@
2222
import java.net.ConnectException;
2323
import java.net.Socket;
2424
import java.net.SocketTimeoutException;
25+
import java.net.StandardSocketOptions;
2526
import java.nio.channels.ByteChannel;
27+
import java.nio.channels.SocketChannel;
2628

2729
import org.neo4j.driver.internal.security.SecurityPlan;
2830
import org.neo4j.driver.internal.security.TLSSocketChannel;
2931
import org.neo4j.driver.v1.Logger;
3032

3133
class ChannelFactory
3234
{
33-
static ByteChannel create( Socket socket, BoltServerAddress address, SecurityPlan securityPlan, int timeoutMillis,
34-
Logger log ) throws IOException
35+
static ByteChannel create( BoltServerAddress address, SecurityPlan securityPlan, int timeoutMillis, Logger log )
36+
throws IOException
3537
{
36-
connect( socket, address, timeoutMillis );
38+
SocketChannel soChannel = SocketChannel.open();
39+
soChannel.setOption( StandardSocketOptions.SO_REUSEADDR, true );
40+
soChannel.setOption( StandardSocketOptions.SO_KEEPALIVE, true );
41+
connect( soChannel, address, timeoutMillis );
3742

38-
ByteChannel channel = new UnencryptedSocketChannel( socket );
43+
ByteChannel channel = soChannel;
3944

4045
if ( securityPlan.requiresEncryption() )
4146
{
4247
try
4348
{
44-
channel = TLSSocketChannel.create( address, securityPlan, channel, log );
49+
channel = TLSSocketChannel.create( address, securityPlan, soChannel, log );
4550
}
4651
catch ( Exception e )
4752
{
@@ -65,8 +70,10 @@ static ByteChannel create( Socket socket, BoltServerAddress address, SecurityPla
6570
return channel;
6671
}
6772

68-
private static void connect( Socket socket, BoltServerAddress address, int timeoutMillis ) throws IOException
73+
private static void connect( SocketChannel soChannel, BoltServerAddress address, int timeoutMillis )
74+
throws IOException
6975
{
76+
Socket socket = soChannel.socket();
7077
try
7178
{
7279
socket.connect( address.toSocketAddress(), timeoutMillis );

driver/src/main/java/org/neo4j/driver/internal/net/SocketClient.java

Lines changed: 2 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,14 @@
2020

2121
import java.io.IOException;
2222
import java.net.ConnectException;
23-
import java.net.Socket;
24-
import java.net.SocketAddress;
25-
import java.net.SocketTimeoutException;
2623
import java.nio.ByteBuffer;
2724
import java.nio.channels.ByteChannel;
2825
import java.util.Queue;
2926

3027
import org.neo4j.driver.internal.messaging.Message;
3128
import org.neo4j.driver.internal.messaging.MessageFormat;
3229
import org.neo4j.driver.internal.security.SecurityPlan;
33-
import org.neo4j.driver.internal.security.TLSSocketChannel;
3430
import org.neo4j.driver.internal.util.BytePrinter;
35-
import org.neo4j.driver.v1.Config;
3631
import org.neo4j.driver.v1.Logger;
3732
import org.neo4j.driver.v1.exceptions.ClientException;
3833
import org.neo4j.driver.v1.exceptions.ServiceUnavailableException;
@@ -123,31 +118,22 @@ void blockingWrite( ByteBuffer buf ) throws IOException
123118

124119
public void start()
125120
{
126-
Socket socket = null;
127-
boolean connected = false;
128121
try
129122
{
130123
logger.debug( "Connecting to %s, secure: %s", address, securityPlan.requiresEncryption() );
131124
if( channel == null )
132125
{
133-
socket = newSocket( timeoutMillis );
134-
setChannel( ChannelFactory.create( socket, address, securityPlan, timeoutMillis, logger ) );
126+
setChannel( ChannelFactory.create( address, securityPlan, timeoutMillis, logger ) );
135127
logger.debug( "Connected to %s, secure: %s", address, securityPlan.requiresEncryption() );
136128
}
137129

138130
logger.debug( "Negotiating protocol with %s", address );
139131
SocketProtocol protocol = negotiateProtocol();
140132
setProtocol( protocol );
141133
logger.debug( "Selected protocol %s with %s", protocol.getClass(), address );
142-
143-
// reset read timeout (SO_TIMEOUT) to the original value of zero
144-
// we do not want to permanently limit amount of time driver waits for database to execute query
145-
socket.setSoTimeout( 0 );
146-
connected = true;
147134
}
148-
catch ( ConnectException | SocketTimeoutException e )
135+
catch ( ConnectException e )
149136
{
150-
// unable to connect socket or TLS/Bolt handshake took too much time
151137
throw new ServiceUnavailableException( format(
152138
"Unable to connect to %s, ensure the database is running and that there is a " +
153139
"working network connection to it.", address ), e );
@@ -156,19 +142,6 @@ public void start()
156142
{
157143
throw new ServiceUnavailableException( "Unable to process request: " + e.getMessage(), e );
158144
}
159-
finally
160-
{
161-
if ( !connected && socket != null )
162-
{
163-
try
164-
{
165-
socket.close();
166-
}
167-
catch ( Throwable ignore )
168-
{
169-
}
170-
}
171-
}
172145
}
173146

174147
public void updateProtocol( String serverVersion )
@@ -328,35 +301,4 @@ public BoltServerAddress address()
328301
{
329302
return address;
330303
}
331-
332-
/**
333-
* Creates new {@link Socket} object with {@link Socket#setSoTimeout(int) read timeout} set to the given value.
334-
* Connection to bolt server includes:
335-
* <ol>
336-
* <li>TCP connect via {@link Socket#connect(SocketAddress, int)}</li>
337-
* <li>Optional TLS handshake using {@link TLSSocketChannel}</li>
338-
* <li>Bolt handshake</li>
339-
* </ol>
340-
* We do not want any of these steps to hang infinitely if server does not respond and thus:
341-
* <ol>
342-
* <li>Use {@link Socket#connect(SocketAddress, int)} with timeout, as configured in
343-
* {@link Config#connectionTimeoutMillis()}</li>
344-
* <li>Initially set {@link Socket#setSoTimeout(int) read timeout} on the socket. Same connection-timeout value
345-
* from {@link Config#connectionTimeoutMillis()} is used. This way blocking reads during TLS and Bolt handshakes
346-
* have limited waiting time</li>
347-
* </ol>
348-
*
349-
* @param configuredConnectTimeout user-defined connection timeout to be initially used as read timeout.
350-
* @return new socket.
351-
* @throws IOException when creation or configuration of the socket fails.
352-
*/
353-
private static Socket newSocket( int configuredConnectTimeout ) throws IOException
354-
{
355-
Socket socket = new Socket();
356-
socket.setReuseAddress( true );
357-
socket.setKeepAlive( true );
358-
// set read timeout initially
359-
socket.setSoTimeout( configuredConnectTimeout );
360-
return socket;
361-
}
362304
}

driver/src/main/java/org/neo4j/driver/internal/net/UnencryptedSocketChannel.java

Lines changed: 0 additions & 65 deletions
This file was deleted.

driver/src/test/java/org/neo4j/driver/internal/net/SocketClientTest.java

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,24 @@
1818
*/
1919
package org.neo4j.driver.internal.net;
2020

21+
import org.junit.Ignore;
2122
import org.junit.Rule;
2223
import org.junit.Test;
2324
import org.junit.rules.ExpectedException;
2425

2526
import java.io.IOException;
2627
import java.net.ServerSocket;
27-
import java.net.SocketTimeoutException;
2828
import java.nio.ByteBuffer;
2929
import java.nio.channels.ByteChannel;
3030
import java.util.ArrayList;
3131
import java.util.List;
3232

3333
import org.neo4j.driver.internal.security.SecurityPlan;
34+
import org.neo4j.driver.v1.exceptions.ClientException;
3435
import org.neo4j.driver.v1.exceptions.ServiceUnavailableException;
3536

3637
import static org.hamcrest.CoreMatchers.equalTo;
37-
import static org.hamcrest.Matchers.instanceOf;
38-
import static org.junit.Assert.assertThat;
39-
import static org.junit.Assert.fail;
38+
import static org.hamcrest.MatcherAssert.assertThat;
4039
import static org.mockito.Matchers.any;
4140
import static org.mockito.Mockito.mock;
4241
import static org.mockito.Mockito.when;
@@ -50,16 +49,24 @@ public class SocketClientTest
5049
@Rule
5150
public ExpectedException exception = ExpectedException.none();
5251

52+
// TODO: This is not possible with blocking NIO channels, unless we use inputStreams, but then we can't use
53+
// off-heap buffers. We need to swap to use selectors, which would allow us to time out.
5354
@Test
54-
public void shouldFailWhenProtocolNegotiationTakesTooLong() throws Exception
55+
@Ignore
56+
public void testNetworkTimeout() throws Throwable
5557
{
56-
testReadTimeoutOnConnect( SecurityPlan.insecure() );
57-
}
58+
// Given a server that will never reply
59+
ServerSocket server = new ServerSocket( 0 );
60+
BoltServerAddress address = new BoltServerAddress( "localhost", server.getLocalPort() );
5861

59-
@Test
60-
public void shouldFailWhenTLSHandshakeTakesTooLong() throws Exception
61-
{
62-
testReadTimeoutOnConnect( SecurityPlan.forAllCertificates() );
62+
SocketClient client = dummyClient( address );
63+
64+
// Expect
65+
exception.expect( ClientException.class );
66+
exception.expectMessage( "database took longer than network timeout (100ms) to reply." );
67+
68+
// When
69+
client.start();
6370
}
6471

6572
@Test
@@ -183,26 +190,6 @@ public void shouldFailIfConnectionFailsWhileWriting() throws IOException
183190
client.blockingWrite( buffer );
184191
}
185192

186-
private static void testReadTimeoutOnConnect( SecurityPlan securityPlan ) throws IOException
187-
{
188-
try ( ServerSocket server = new ServerSocket( 0 ) ) // server that does not reply
189-
{
190-
int timeoutMillis = 1_000;
191-
BoltServerAddress address = new BoltServerAddress( "localhost", server.getLocalPort() );
192-
SocketClient client = new SocketClient( address, securityPlan, timeoutMillis, DEV_NULL_LOGGER );
193-
194-
try
195-
{
196-
client.start();
197-
fail( "Exception expected" );
198-
}
199-
catch ( ServiceUnavailableException e )
200-
{
201-
assertThat( e.getCause(), instanceOf( SocketTimeoutException.class ) );
202-
}
203-
}
204-
}
205-
206193
private static class ByteAtATimeChannel implements ByteChannel
207194
{
208195

0 commit comments

Comments
 (0)