Skip to content

Commit af0f355

Browse files
authored
Merge pull request #398 from lutovich/1.5-maxConnectionLifetime
Added ability to enforce max connection lifetime
2 parents 4a77c8d + 4c868a6 commit af0f355

20 files changed

+545
-68
lines changed

driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ protected ConnectionPool createConnectionPool( AuthToken authToken, SecurityPlan
177177

178178
ConnectionSettings connectionSettings = new ConnectionSettings( authToken, config.connectionTimeoutMillis() );
179179
PoolSettings poolSettings = new PoolSettings( config.maxIdleConnectionPoolSize(),
180-
config.idleTimeBeforeConnectionTest() );
180+
config.idleTimeBeforeConnectionTest(), config.maxConnectionLifetime() );
181181
Connector connector = createConnector( connectionSettings, securityPlan, config.logging() );
182182

183183
return new SocketConnectionPool( poolSettings, connector, createClock(), config.logging() );

driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingPooledConnection.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,12 @@ public BoltServerAddress boltServerAddress()
221221
return delegate.boltServerAddress();
222222
}
223223

224+
@Override
225+
public long creationTimestamp()
226+
{
227+
return delegate.creationTimestamp();
228+
}
229+
224230
@Override
225231
public long lastUsedTimestamp()
226232
{

driver/src/main/java/org/neo4j/driver/internal/net/pooling/BlockingPooledConnectionQueue.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public PooledConnection acquire( Supplier<PooledConnection> supplier )
107107
return connection;
108108
}
109109

110-
void disposeBroken( PooledConnection connection )
110+
void dispose( PooledConnection connection )
111111
{
112112
acquiredConnections.remove( connection );
113113
disposeSafely( connection );

driver/src/main/java/org/neo4j/driver/internal/net/pooling/PoolSettings.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,21 @@
2121
public class PoolSettings
2222
{
2323
public static final int NO_IDLE_CONNECTION_TEST = -1;
24+
public static final int INFINITE_CONNECTION_LIFETIME = -1;
2425

2526
public static final int DEFAULT_MAX_IDLE_CONNECTION_POOL_SIZE = 10;
2627
public static final int DEFAULT_IDLE_TIME_BEFORE_CONNECTION_TEST = NO_IDLE_CONNECTION_TEST;
28+
public static final int DEFAULT_MAX_CONNECTION_LIFETIME = INFINITE_CONNECTION_LIFETIME;
2729

2830
private final int maxIdleConnectionPoolSize;
2931
private final long idleTimeBeforeConnectionTest;
32+
private final long maxConnectionLifetime;
3033

31-
public PoolSettings( int maxIdleConnectionPoolSize, long idleTimeBeforeConnectionTest )
34+
public PoolSettings( int maxIdleConnectionPoolSize, long idleTimeBeforeConnectionTest, long maxConnectionLifetime )
3235
{
3336
this.maxIdleConnectionPoolSize = maxIdleConnectionPoolSize;
3437
this.idleTimeBeforeConnectionTest = idleTimeBeforeConnectionTest;
38+
this.maxConnectionLifetime = maxConnectionLifetime;
3539
}
3640

3741
public int maxIdleConnectionPoolSize()
@@ -53,4 +57,19 @@ public boolean idleTimeBeforeConnectionTestConfigured()
5357
{
5458
return idleTimeBeforeConnectionTest >= 0;
5559
}
60+
61+
public long maxConnectionLifetime()
62+
{
63+
if ( !maxConnectionLifetimeConfigured() )
64+
{
65+
throw new IllegalStateException(
66+
"Max connection lifetime is not configured: " + maxConnectionLifetime );
67+
}
68+
return maxConnectionLifetime;
69+
}
70+
71+
public boolean maxConnectionLifetimeConfigured()
72+
{
73+
return maxConnectionLifetime > 0;
74+
}
5675
}

driver/src/main/java/org/neo4j/driver/internal/net/pooling/PooledConnectionReleaseConsumer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public void accept( PooledConnection pooledConnection )
4747
}
4848
else
4949
{
50-
connections.disposeBroken( pooledConnection );
50+
connections.dispose( pooledConnection );
5151
}
5252
}
5353
}

driver/src/main/java/org/neo4j/driver/internal/net/pooling/PooledSocketConnection.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,16 @@ public class PooledSocketConnection implements PooledConnection
6060
private boolean unrecoverableErrorsOccurred = false;
6161
private SessionResourcesHandler resourcesHandler;
6262
private final Clock clock;
63+
64+
private final long creationTimestamp;
6365
private long lastUsedTimestamp;
6466

6567
public PooledSocketConnection( Connection delegate, Consumer<PooledConnection> release, Clock clock )
6668
{
6769
this.delegate = delegate;
6870
this.release = release;
6971
this.clock = clock;
72+
this.creationTimestamp = clock.millis();
7073
updateLastUsedTimestamp();
7174
}
7275

@@ -280,6 +283,12 @@ public void setResourcesHandler( SessionResourcesHandler resourcesHandler )
280283
this.resourcesHandler = resourcesHandler;
281284
}
282285

286+
@Override
287+
public long creationTimestamp()
288+
{
289+
return creationTimestamp;
290+
}
291+
283292
@Override
284293
public long lastUsedTimestamp()
285294
{
@@ -315,6 +324,6 @@ private boolean isClientOrTransientError( RuntimeException e )
315324

316325
private void updateLastUsedTimestamp()
317326
{
318-
this.lastUsedTimestamp = clock.millis();
327+
lastUsedTimestamp = clock.millis();
319328
}
320329
}

driver/src/main/java/org/neo4j/driver/internal/net/pooling/SocketConnectionPool.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ private PooledConnection acquireConnection( BoltServerAddress address,
149149
// dispose previous connection that can't be acquired
150150
if ( connection != null )
151151
{
152-
connectionQueue.disposeBroken( connection );
152+
connectionQueue.dispose( connection );
153153
}
154154

155155
connection = connectionQueue.acquire( connectionSupplier );
@@ -162,18 +162,27 @@ private PooledConnection acquireConnection( BoltServerAddress address,
162162

163163
private boolean canBeAcquired( PooledConnection connection, boolean connectionCreated )
164164
{
165-
if ( poolSettings.idleTimeBeforeConnectionTestConfigured() )
165+
if ( connectionCreated )
166+
{
167+
return true;
168+
}
169+
170+
if ( poolSettings.maxConnectionLifetimeConfigured() )
166171
{
167-
if ( connectionCreated )
172+
if ( isTooOld( connection ) )
168173
{
169-
return true;
174+
return false;
170175
}
176+
}
171177

178+
if ( poolSettings.idleTimeBeforeConnectionTestConfigured() )
179+
{
172180
if ( hasBeenIdleForTooLong( connection ) )
173181
{
174182
return connectionValidator.isConnected( connection );
175183
}
176184
}
185+
177186
return true;
178187
}
179188

@@ -183,6 +192,12 @@ private boolean hasBeenIdleForTooLong( PooledConnection connection )
183192
return idleTime > poolSettings.idleTimeBeforeConnectionTest();
184193
}
185194

195+
private boolean isTooOld( PooledConnection connection )
196+
{
197+
long lifetime = clock.millis() - connection.creationTimestamp();
198+
return lifetime > poolSettings.maxConnectionLifetime();
199+
}
200+
186201
private void assertNotClosed( BoltServerAddress address, BlockingPooledConnectionQueue connections )
187202
{
188203
if ( closed.get() )

driver/src/main/java/org/neo4j/driver/internal/spi/PooledConnection.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ public interface PooledConnection extends Connection
4040
*/
4141
boolean hasUnrecoverableErrors();
4242

43+
/**
44+
* Timestamp of when this connection was created. This timestamp should never change.
45+
*
46+
* @return timestamp as returned by {@link Clock#millis()}.
47+
*/
48+
long creationTimestamp();
49+
4350
/**
4451
* Timestamp of when this connection was used. This timestamp is updated when connection is returned to the pool.
4552
*

driver/src/main/java/org/neo4j/driver/v1/Config.java

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,8 @@ public class Config
5858

5959
private final int maxIdleConnectionPoolSize;
6060

61-
/**
62-
* Connections that have been idle in the pool longer than this threshold will
63-
* be tested for validity before being returned to the user.
64-
*/
6561
private final long idleTimeBeforeConnectionTest;
62+
private final long maxConnectionLifetime;
6663

6764
/** Indicator for encrypted traffic */
6865
private final boolean encrypted;
@@ -83,6 +80,7 @@ private Config( ConfigBuilder builder)
8380
this.logLeakedSessions = builder.logLeakedSessions;
8481

8582
this.idleTimeBeforeConnectionTest = builder.idleTimeBeforeConnectionTest;
83+
this.maxConnectionLifetime = builder.maxConnectionLifetime;
8684
this.maxIdleConnectionPoolSize = builder.maxIdleConnectionPoolSize;
8785

8886
this.encrypted = builder.encrypted;
@@ -143,6 +141,16 @@ public long idleTimeBeforeConnectionTest()
143141
return idleTimeBeforeConnectionTest;
144142
}
145143

144+
/**
145+
* Pooled connections older than this threshold will be closed and removed from the pool.
146+
*
147+
* @return maximum lifetime in milliseconds
148+
*/
149+
public long maxConnectionLifetime()
150+
{
151+
return maxConnectionLifetime;
152+
}
153+
146154
/**
147155
* @return the configured connection timeout value in milliseconds.
148156
*/
@@ -223,6 +231,7 @@ public static class ConfigBuilder
223231
private boolean logLeakedSessions;
224232
private int maxIdleConnectionPoolSize = PoolSettings.DEFAULT_MAX_IDLE_CONNECTION_POOL_SIZE;
225233
private long idleTimeBeforeConnectionTest = PoolSettings.DEFAULT_IDLE_TIME_BEFORE_CONNECTION_TEST;
234+
private long maxConnectionLifetime = PoolSettings.DEFAULT_MAX_CONNECTION_LIFETIME;
226235
private boolean encrypted = true;
227236
private TrustStrategy trustStrategy = trustAllCertificates();
228237
private LoadBalancingStrategy loadBalancingStrategy = LoadBalancingStrategy.LEAST_CONNECTED;
@@ -348,7 +357,7 @@ public ConfigBuilder withSessionLivenessCheckTimeout( long timeout )
348357
* Value {@code 0} means connections will always be tested for
349358
* validity and negative values mean connections will never be tested.
350359
*
351-
* @param value the minimum idle time in milliseconds
360+
* @param value the minimum idle time
352361
* @param unit the unit in which the duration is given
353362
* @return this builder
354363
*/
@@ -358,6 +367,32 @@ public ConfigBuilder withConnectionLivenessCheckTimeout( long value, TimeUnit un
358367
return this;
359368
}
360369

370+
/**
371+
* Pooled connections older than this threshold will be closed and removed from the pool. Such discarding
372+
* happens during connection acquisition so that new session is never backed by an old connection.
373+
* <p>
374+
* Setting this option to a low value will cause a high connection churn and might result in a performance hit.
375+
* <p>
376+
* It is recommended to set maximum lifetime to a slightly smaller value than the one configured in network
377+
* equipment (load balancer, proxy, firewall, etc. can also limit maximum connection lifetime).
378+
* <p>
379+
* Setting can also be used in combination with {@link #withConnectionLivenessCheckTimeout(long, TimeUnit)}. In
380+
* this case, it is recommended to set liveness check to a value smaller than network equipment has and maximum
381+
* lifetime to a reasonably large value to "renew" connections once in a while.
382+
* <p>
383+
* No maximum lifetime limit is imposed by default. Zero and negative values result in lifetime not being
384+
* checked.
385+
*
386+
* @param value the maximum connection lifetime
387+
* @param unit the unit in which the duration is given
388+
* @return this builder
389+
*/
390+
public ConfigBuilder withMaxConnectionLifetime( long value, TimeUnit unit )
391+
{
392+
this.maxConnectionLifetime = unit.toMillis( value );
393+
return this;
394+
}
395+
361396
/**
362397
* Configure the {@link EncryptionLevel} to use, use this to control wether the driver uses TLS encryption or not.
363398
* @param level the TLS level to use

driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingPooledConnectionErrorHandlingTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import static org.mockito.Mockito.when;
6060
import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING;
6161
import static org.neo4j.driver.internal.net.pooling.PoolSettings.DEFAULT_MAX_IDLE_CONNECTION_POOL_SIZE;
62+
import static org.neo4j.driver.internal.net.pooling.PoolSettings.INFINITE_CONNECTION_LIFETIME;
6263
import static org.neo4j.driver.internal.net.pooling.PoolSettings.NO_IDLE_CONNECTION_TEST;
6364
import static org.neo4j.driver.internal.spi.Collector.NO_OP;
6465
import static org.neo4j.driver.internal.util.Matchers.containsReader;
@@ -351,7 +352,8 @@ private static RoutingTable newRoutingTable( ClusterComposition clusterCompositi
351352
private static ConnectionPool newConnectionPool( Connector connector, BoltServerAddress... addresses )
352353
{
353354
int maxIdleConnections = DEFAULT_MAX_IDLE_CONNECTION_POOL_SIZE;
354-
PoolSettings settings = new PoolSettings( maxIdleConnections, NO_IDLE_CONNECTION_TEST );
355+
PoolSettings settings = new PoolSettings( maxIdleConnections, NO_IDLE_CONNECTION_TEST,
356+
INFINITE_CONNECTION_LIFETIME );
355357
SocketConnectionPool pool = new SocketConnectionPool( settings, connector, Clock.SYSTEM, DEV_NULL_LOGGING );
356358

357359
// force pool to create and memorize some connections
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) 2002-2017 "Neo Technology,"
3+
* Network Engine for Objects in Lund AB [http://neotechnology.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.neo4j.driver.internal.cluster;
20+
21+
import org.junit.Test;
22+
import org.junit.runner.RunWith;
23+
import org.mockito.InjectMocks;
24+
import org.mockito.Mock;
25+
import org.mockito.runners.MockitoJUnitRunner;
26+
27+
import org.neo4j.driver.internal.spi.PooledConnection;
28+
29+
import static org.junit.Assert.assertEquals;
30+
import static org.mockito.Mockito.when;
31+
32+
@RunWith( MockitoJUnitRunner.class )
33+
public class RoutingPooledConnectionTest
34+
{
35+
@Mock
36+
private PooledConnection pooledConnection;
37+
@InjectMocks
38+
private RoutingPooledConnection routingPooledConnection;
39+
40+
@Test
41+
public void shouldExposeCreationTimestamp()
42+
{
43+
when( pooledConnection.creationTimestamp() ).thenReturn( 42L );
44+
45+
long timestamp = routingPooledConnection.creationTimestamp();
46+
47+
assertEquals( 42L, timestamp );
48+
}
49+
50+
@Test
51+
public void shouldExposeLastUsedTimestamp()
52+
{
53+
when( pooledConnection.lastUsedTimestamp() ).thenReturn( 42L );
54+
55+
long timestamp = routingPooledConnection.lastUsedTimestamp();
56+
57+
assertEquals( 42L, timestamp );
58+
}
59+
}

driver/src/test/java/org/neo4j/driver/internal/net/pooling/BlockingPooledConnectionQueueTest.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,15 +287,33 @@ public void shouldReportActiveConnections()
287287

288288
@Test
289289
@SuppressWarnings( "unchecked" )
290-
public void shouldDisposeBrokenConnections()
290+
public void shouldDisposeConnections()
291291
{
292292
BlockingPooledConnectionQueue queue = newConnectionQueue( 5 );
293293

294294
queue.offer( mock( PooledConnection.class ) );
295295
PooledConnection connection = queue.acquire( mock( Supplier.class ) );
296296
assertEquals( 1, queue.activeConnections() );
297297

298-
queue.disposeBroken( connection );
298+
queue.dispose( connection );
299+
assertEquals( 0, queue.activeConnections() );
300+
verify( connection ).dispose();
301+
}
302+
303+
@Test
304+
@SuppressWarnings( "unchecked" )
305+
public void shouldDisposeConnectionsThatThrowOnDisposal()
306+
{
307+
BlockingPooledConnectionQueue queue = newConnectionQueue( 5 );
308+
309+
PooledConnection pooledConnection = mock( PooledConnection.class );
310+
doThrow( new RuntimeException() ).when( pooledConnection ).dispose();
311+
312+
queue.offer( pooledConnection );
313+
PooledConnection connection = queue.acquire( mock( Supplier.class ) );
314+
assertEquals( 1, queue.activeConnections() );
315+
316+
queue.dispose( connection );
299317
assertEquals( 0, queue.activeConnections() );
300318
verify( connection ).dispose();
301319
}

0 commit comments

Comments
 (0)