@@ -92,7 +92,7 @@ def __repr__(self):
92
92
93
93
class PoolConnectionHolder :
94
94
95
- __slots__ = ('_con' , '_pool' , '_loop' ,
95
+ __slots__ = ('_con' , '_pool' , '_loop' , '_proxy' ,
96
96
'_connect_args' , '_connect_kwargs' ,
97
97
'_max_queries' , '_setup' , '_init' ,
98
98
'_max_inactive_time' , '_in_use' ,
@@ -103,6 +103,7 @@ def __init__(self, pool, *, connect_args, connect_kwargs,
103
103
104
104
self ._pool = pool
105
105
self ._con = None
106
+ self ._proxy = None
106
107
107
108
self ._connect_args = connect_args
108
109
self ._connect_kwargs = connect_kwargs
@@ -111,7 +112,7 @@ def __init__(self, pool, *, connect_args, connect_kwargs,
111
112
self ._setup = setup
112
113
self ._init = init
113
114
self ._inactive_callback = None
114
- self ._in_use = False
115
+ self ._in_use = None # type: asyncio.Future
115
116
self ._timeout = None
116
117
117
118
async def connect (self ):
@@ -152,7 +153,7 @@ async def acquire(self) -> PoolConnectionProxy:
152
153
153
154
self ._maybe_cancel_inactive_callback ()
154
155
155
- proxy = PoolConnectionProxy (self , self ._con )
156
+ self . _proxy = proxy = PoolConnectionProxy (self , self ._con )
156
157
157
158
if self ._setup is not None :
158
159
try :
@@ -163,31 +164,29 @@ async def acquire(self) -> PoolConnectionProxy:
163
164
# we close it. A new connection will be created
164
165
# when `acquire` is called again.
165
166
try :
166
- proxy ._detach ()
167
167
# Use `close` to close the connection gracefully.
168
168
# An exception in `setup` isn't necessarily caused
169
169
# by an IO or a protocol error.
170
170
await self ._con .close ()
171
171
finally :
172
- self ._con = None
173
172
raise ex
174
173
175
- self ._in_use = True
174
+ self ._in_use = self ._pool ._loop .create_future ()
175
+
176
176
return proxy
177
177
178
178
async def release (self , timeout ):
179
- assert self ._in_use
180
- self ._in_use = False
181
- self ._timeout = None
179
+ assert self ._in_use is not None
182
180
183
181
if self ._con .is_closed ():
184
- self ._con = None
182
+ # When closing, pool connections perform the necessary
183
+ # cleanup, so we don't have to do anything else here.
184
+ return
185
185
186
- elif self ._con ._protocol .queries_count >= self ._max_queries :
187
- try :
188
- await self ._con .close (timeout = timeout )
189
- finally :
190
- self ._con = None
186
+ self ._timeout = None
187
+
188
+ if self ._con ._protocol .queries_count >= self ._max_queries :
189
+ await self ._con .close (timeout = timeout )
191
190
192
191
else :
193
192
try :
@@ -213,52 +212,71 @@ async def release(self, timeout):
213
212
# an IO error, so terminate the connection.
214
213
self ._con .terminate ()
215
214
finally :
216
- self ._con = None
217
215
raise ex
218
216
217
+ self ._release ()
218
+
219
219
assert self ._inactive_callback is None
220
220
if self ._max_inactive_time and self ._con is not None :
221
221
self ._inactive_callback = self ._pool ._loop .call_later (
222
222
self ._max_inactive_time , self ._deactivate_connection )
223
223
224
- async def close (self ):
225
- self ._maybe_cancel_inactive_callback ()
226
- if self ._con is None :
227
- return
228
- if self ._con .is_closed ():
229
- self ._con = None
224
+ async def wait_until_released (self ):
225
+ if self ._in_use is None :
230
226
return
227
+ else :
228
+ await self ._in_use
231
229
232
- try :
230
+ async def close (self ):
231
+ if self ._con is not None :
232
+ # Connection.close() will call _release_on_close() to
233
+ # finish holder cleanup.
233
234
await self ._con .close ()
234
- finally :
235
- self ._con = None
236
235
237
236
def terminate (self ):
238
- self ._maybe_cancel_inactive_callback ()
239
- if self ._con is None :
240
- return
241
- if self ._con .is_closed ():
242
- self ._con = None
243
- return
244
-
245
- try :
237
+ if self ._con is not None :
238
+ # Connection.terminate() will call _release_on_close() to
239
+ # finish holder cleanup.
246
240
self ._con .terminate ()
247
- finally :
248
- self ._con = None
249
241
250
242
def _maybe_cancel_inactive_callback (self ):
251
243
if self ._inactive_callback is not None :
252
244
self ._inactive_callback .cancel ()
253
245
self ._inactive_callback = None
254
246
255
247
def _deactivate_connection (self ):
256
- assert not self ._in_use
257
- if self ._con is None or self ._con .is_closed ():
258
- return
259
- self ._con .terminate ()
248
+ assert self ._in_use is None
249
+ if self ._con is not None :
250
+ self ._con .terminate ()
251
+ # Must call clear_connection, because _deactivate_connection
252
+ # is called when the connection is *not* checked out, and
253
+ # so terminate() above will not call the below.
254
+ self ._release_on_close ()
255
+
256
+ def _release_on_close (self ):
257
+ self ._maybe_cancel_inactive_callback ()
258
+ self ._release ()
260
259
self ._con = None
261
260
261
+ def _release (self ):
262
+ """Release this connection holder."""
263
+ if self ._in_use is None :
264
+ # The holder is not checked out.
265
+ return
266
+
267
+ if not self ._in_use .done ():
268
+ self ._in_use .set_result (None )
269
+ self ._in_use = None
270
+
271
+ # Let go of the connection proxy.
272
+ if self ._proxy is not None :
273
+ if self ._proxy ._con is not None :
274
+ self ._proxy ._detach ()
275
+ self ._proxy = None
276
+
277
+ # Put ourselves back to the pool queue.
278
+ self ._pool ._queue .put_nowait (self )
279
+
262
280
263
281
class Pool :
264
282
"""A connection pool.
@@ -273,7 +291,7 @@ class Pool:
273
291
274
292
__slots__ = ('_queue' , '_loop' , '_minsize' , '_maxsize' ,
275
293
'_working_addr' , '_working_config' , '_working_params' ,
276
- '_holders' , '_initialized' , '_closed' ,
294
+ '_holders' , '_initialized' , '_closing' , ' _closed' ,
277
295
'_connection_class' )
278
296
279
297
def __init__ (self , * connect_args ,
@@ -322,6 +340,7 @@ def __init__(self, *connect_args,
322
340
323
341
self ._connection_class = connection_class
324
342
343
+ self ._closing = False
325
344
self ._closed = False
326
345
327
346
for _ in range (max_size ):
@@ -468,7 +487,10 @@ async def _acquire_impl():
468
487
ch ._timeout = timeout
469
488
return proxy
470
489
490
+ if self ._closing :
491
+ raise exceptions .InterfaceError ('pool is closing' )
471
492
self ._check_init ()
493
+
472
494
if timeout is None :
473
495
return await _acquire_impl ()
474
496
else :
@@ -488,14 +510,6 @@ async def release(self, connection, *, timeout=None):
488
510
.. versionchanged:: 0.14.0
489
511
Added the *timeout* parameter.
490
512
"""
491
- async def _release_impl (ch : PoolConnectionHolder , timeout : float ):
492
- try :
493
- await ch .release (timeout )
494
- finally :
495
- self ._queue .put_nowait (ch )
496
-
497
- self ._check_init ()
498
-
499
513
if (type (connection ) is not PoolConnectionProxy or
500
514
connection ._holder ._pool is not self ):
501
515
raise exceptions .InterfaceError (
@@ -507,35 +521,64 @@ async def _release_impl(ch: PoolConnectionHolder, timeout: float):
507
521
# Already released, do nothing.
508
522
return
509
523
510
- con = connection ._detach ()
524
+ self ._check_init ()
525
+
526
+ con = connection ._con
511
527
con ._on_release ()
528
+ ch = connection ._holder
512
529
513
530
if timeout is None :
514
- timeout = connection . _holder ._timeout
531
+ timeout = ch ._timeout
515
532
516
533
# Use asyncio.shield() to guarantee that task cancellation
517
534
# does not prevent the connection from being returned to the
518
535
# pool properly.
519
- return await asyncio .shield (
520
- _release_impl (connection ._holder , timeout ), loop = self ._loop )
536
+ return await asyncio .shield (ch .release (timeout ), loop = self ._loop )
521
537
522
538
async def close (self ):
523
- """Gracefully close all connections in the pool."""
539
+ """Attempt to gracefully close all connections in the pool.
540
+
541
+ Wait until all pool connections are released, close them and
542
+ shut down the pool. If any error (including cancellation) occurs
543
+ in ``close()`` the pool will terminate by calling
544
+ :meth:'Pool.terminate() <pool.Pool.terminate>`.
545
+
546
+ .. versionchanged:: 0.16.0
547
+ ``close()`` now waits until all pool connections are released
548
+ before closing them and the pool. Errors raised in ``close()``
549
+ will cause immediate pool termination.
550
+ """
524
551
if self ._closed :
525
552
return
526
553
self ._check_init ()
527
- self ._closed = True
528
- coros = [ch .close () for ch in self ._holders ]
529
- await asyncio .gather (* coros , loop = self ._loop )
554
+
555
+ self ._closing = True
556
+
557
+ try :
558
+ release_coros = [
559
+ ch .wait_until_released () for ch in self ._holders ]
560
+ await asyncio .gather (* release_coros , loop = self ._loop )
561
+
562
+ close_coros = [
563
+ ch .close () for ch in self ._holders ]
564
+ await asyncio .gather (* close_coros , loop = self ._loop )
565
+
566
+ except Exception :
567
+ self .terminate ()
568
+ raise
569
+
570
+ finally :
571
+ self ._closed = True
572
+ self ._closing = False
530
573
531
574
def terminate (self ):
532
575
"""Terminate all connections in the pool."""
533
576
if self ._closed :
534
577
return
535
578
self ._check_init ()
536
- self ._closed = True
537
579
for ch in self ._holders :
538
580
ch .terminate ()
581
+ self ._closed = True
539
582
540
583
def _check_init (self ):
541
584
if not self ._initialized :
0 commit comments