24
24
import java .util .LinkedList ;
25
25
import java .util .List ;
26
26
import java .util .ServiceLoader ;
27
+ import java .util .concurrent .BlockingQueue ;
27
28
import java .util .concurrent .ConcurrentHashMap ;
28
- import java .util .concurrent .TimeUnit ;
29
+ import java .util .concurrent .LinkedBlockingQueue ;
30
+ import java .util .concurrent .atomic .AtomicBoolean ;
29
31
30
32
import org .neo4j .driver .internal .connector .socket .SocketConnector ;
31
33
import org .neo4j .driver .internal .spi .Connection ;
32
34
import org .neo4j .driver .internal .spi .ConnectionPool ;
33
35
import org .neo4j .driver .internal .spi .Connector ;
34
36
import org .neo4j .driver .internal .util .Clock ;
35
- import org .neo4j .driver .internal .util .Consumer ;
36
37
import org .neo4j .driver .v1 .AuthToken ;
37
38
import org .neo4j .driver .v1 .Config ;
38
39
import org .neo4j .driver .v1 .exceptions .ClientException ;
41
42
import static java .lang .String .format ;
42
43
43
44
/**
44
- * A basic connection pool that optimizes for threads being long-lived, acquiring/releasing many connections.
45
- * It uses a global queue as a fallback pool, but tries to avoid coordination by storing connections in a ThreadLocal.
45
+ * The pool is designed to buffer certain amount of free sessions into session pool. When closing a session, we first
46
+ * try to return the session into the session pool, however if we failed to return it back, either because the pool
47
+ * is full or the pool is being cleaned on driver.close, then we directly close the connection attached with the
48
+ * session.
46
49
*
47
- * Safety is achieved by tracking thread locals getting garbage collected, returning connections to the global pool
48
- * when this happens .
50
+ * The session is NOT meant to be thread safe, each thread should have an independent session and close it (return to
51
+ * pool) when the work with the session has been done .
49
52
*
50
- * If threads are long-lived, this pool will achieve linearly scalable performance with overhead equivalent to a
51
- * hash-map lookup per acquire.
52
- *
53
- * If threads are short-lived, this pool is not ideal.
53
+ * The driver is thread safe. Each thread could try to get a session from the pool and then return it to the pool
54
+ * at the same time.
54
55
*/
55
56
public class InternalConnectionPool implements ConnectionPool
56
57
{
@@ -62,36 +63,26 @@ public class InternalConnectionPool implements ConnectionPool
62
63
/**
63
64
* Pools, organized by URL.
64
65
*/
65
- private final ConcurrentHashMap <URI ,ThreadCachingPool <PooledConnection >> pools = new ConcurrentHashMap <>();
66
-
67
- /**
68
- * Connections that fail this criteria will be disposed of.
69
- */
70
- private final ValidationStrategy <PooledConnection > connectionValidation ;
66
+ private final ConcurrentHashMap <URI ,BlockingQueue <PooledConnection >> pools = new ConcurrentHashMap <>();
71
67
72
68
private final AuthToken authToken ;
73
- /**
74
- * Timeout in milliseconds if there are no available sessions.
75
- */
76
- private final long acquireSessionTimeout ;
77
-
78
69
private final Clock clock ;
79
70
private final Config config ;
80
71
72
+ /** Shutdown flag */
73
+ private final AtomicBoolean stopped = new AtomicBoolean ( false );
74
+
81
75
public InternalConnectionPool ( Config config , AuthToken authToken )
82
76
{
83
- this ( loadConnectors (), Clock .SYSTEM , config , authToken ,
84
- Long .getLong ( "neo4j.driver.acquireSessionTimeout" , 30_000 ) );
77
+ this ( loadConnectors (), Clock .SYSTEM , config , authToken );
85
78
}
86
79
87
80
public InternalConnectionPool ( Collection <Connector > conns , Clock clock , Config config ,
88
- AuthToken authToken , long acquireTimeout )
81
+ AuthToken authToken )
89
82
{
90
83
this .authToken = authToken ;
91
- this .acquireSessionTimeout = acquireTimeout ;
92
84
this .config = config ;
93
85
this .clock = clock ;
94
- this .connectionValidation = new PooledConnectionValidator ( config .idleTimeBeforeConnectionTest () );
95
86
for ( Connector connector : conns )
96
87
{
97
88
for ( String s : connector .supportedSchemes () )
@@ -104,37 +95,37 @@ public InternalConnectionPool( Collection<Connector> conns, Clock clock, Config
104
95
@ Override
105
96
public Connection acquire ( URI sessionURI )
106
97
{
107
- try
98
+ if ( stopped . get () )
108
99
{
109
- Connection conn = pool ( sessionURI ).acquire ( acquireSessionTimeout , TimeUnit .MILLISECONDS );
110
- if ( conn == null )
100
+ throw new IllegalStateException ( "Pool has been closed, cannot acquire new values." );
101
+ }
102
+ BlockingQueue <PooledConnection > connections = pool ( sessionURI );
103
+ PooledConnection conn = connections .poll ();
104
+ if ( conn == null )
105
+ {
106
+ Connector connector = connectors .get ( sessionURI .getScheme () );
107
+ if ( connector == null )
111
108
{
112
109
throw new ClientException (
113
- "Failed to acquire a session with Neo4j " +
114
- "as all the connections in the connection pool are already occupied by other sessions. " +
115
- "Please close unused session and retry. " +
116
- "Current Pool size: " + config .connectionPoolSize () +
117
- ". If your application requires running more sessions concurrently than the current pool " +
118
- "size, you should create a driver with a larger connection pool size." );
110
+ format ( "Unsupported URI scheme: '%s' in url: '%s'. Supported transports are: '%s'." ,
111
+ sessionURI .getScheme (), sessionURI , connectorSchemes () ) );
119
112
}
120
- return conn ;
121
- }
122
- catch ( InterruptedException e )
123
- {
124
- throw new ClientException ( "Interrupted while waiting for a connection to Neo4j." );
113
+ conn = new PooledConnection (connector .connect ( sessionURI , config , authToken ), new
114
+ PooledConnectionReleaseConsumer ( connections , stopped , config ), clock );
125
115
}
116
+ conn .updateUsageTimestamp ();
117
+ return conn ;
126
118
}
127
119
128
- private ThreadCachingPool <PooledConnection > pool ( URI sessionURI )
120
+ private BlockingQueue <PooledConnection > pool ( URI sessionURI )
129
121
{
130
- ThreadCachingPool <PooledConnection > pool = pools .get ( sessionURI );
122
+ BlockingQueue <PooledConnection > pool = pools .get ( sessionURI );
131
123
if ( pool == null )
132
124
{
133
- pool = newPool ( sessionURI );
125
+ pool = new LinkedBlockingQueue <>( config . maxIdleConnectionPoolSize () );
134
126
if ( pools .putIfAbsent ( sessionURI , pool ) != null )
135
127
{
136
128
// We lost a race to create the pool, dispose of the one we created, and recurse
137
- pool .close ();
138
129
return pool ( sessionURI );
139
130
}
140
131
}
@@ -161,48 +152,30 @@ private static Collection<Connector> loadConnectors()
161
152
@ Override
162
153
public void close () throws Neo4jException
163
154
{
164
- for ( ThreadCachingPool < PooledConnection > pool : pools . values ( ) )
155
+ if ( ! stopped . compareAndSet ( false , true ) )
165
156
{
166
- pool .close ();
157
+ // already closed or some other thread already started close
158
+ return ;
167
159
}
168
- pools .clear ();
169
- }
170
-
171
- private String connectorSchemes ()
172
- {
173
- return Arrays .toString ( connectors .keySet ().toArray ( new String [connectors .keySet ().size ()] ) );
174
- }
175
-
176
- private ThreadCachingPool <PooledConnection > newPool ( final URI uri )
177
- {
178
160
179
- return new ThreadCachingPool <>( config . connectionPoolSize (), new Allocator < PooledConnection >( )
161
+ for ( BlockingQueue < PooledConnection > pool : pools . values () )
180
162
{
181
- @ Override
182
- public PooledConnection allocate ( Consumer <PooledConnection > release )
163
+ while ( !pool .isEmpty () )
183
164
{
184
- Connector connector = connectors . get ( uri . getScheme () );
185
- if ( connector = = null )
165
+ PooledConnection conn = pool . poll ( );
166
+ if ( conn ! = null )
186
167
{
187
- throw new ClientException (
188
- format ( "Unsupported URI scheme: '%s' in url: '%s'. Supported transports are: '%s'." ,
189
- uri .getScheme (), uri , connectorSchemes () ) );
168
+ //close the underlying connection without adding it back to the queue
169
+ conn .dispose ();
190
170
}
191
- Connection conn = connector .connect ( uri , config , authToken );
192
- return new PooledConnection ( conn , release );
193
- }
194
-
195
- @ Override
196
- public void onDispose ( PooledConnection pooledConnection )
197
- {
198
- pooledConnection .dispose ();
199
171
}
172
+ }
200
173
201
- @ Override
202
- public void onAcquire ( PooledConnection pooledConnection )
203
- {
174
+ pools .clear ();
175
+ }
204
176
205
- }
206
- }, connectionValidation , clock );
177
+ private String connectorSchemes ()
178
+ {
179
+ return Arrays .toString ( connectors .keySet ().toArray ( new String [connectors .keySet ().size ()] ) );
207
180
}
208
181
}
0 commit comments