Skip to content

Commit 4e4a3da

Browse files
Fixed bug resulting in explicit request boundaries to aid planned
database maintenance not being sent when using connection pools with asyncio; pooled connections that are no longer needed are now closed normally if possible instead of simply having the socket disconnected (#393).
1 parent 0a46264 commit 4e4a3da

File tree

14 files changed

+374
-239
lines changed

14 files changed

+374
-239
lines changed

doc/src/release_notes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ Thin Mode Changes
2525
#) Fixed bug when connecting with asyncio using the parameter ``https_proxy``.
2626
#) Fixed regression when connecting where only the host specified by the
2727
``https_proxy`` parameter can successfully perform name resolution.
28+
#) Fixed bug resulting in explicit request boundaries to aid planned database
29+
maintenance not being sent when using connection pools with asyncio.
30+
#) Pooled connections that are no longer needed are now closed normally if
31+
possible instead of simply having the socket disconnected
32+
(`issue 393 <https://github.com/oracle/python-oracledb/issues/393>`__).
2833

2934
Thick Mode Changes
3035
++++++++++++++++++

src/oracledb/base_impl.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,7 @@ cdef class ConnectParamsImpl:
586586
cdef str _get_token(self)
587587
cdef object _get_public_instance(self)
588588
cdef object _get_token_expires(self, str token)
589+
cdef bint _get_uses_drcp(self)
589590
cdef str _get_wallet_password(self)
590591
cdef int _parse_connect_string(self, str connect_string) except -1
591592
cdef int _set_access_token(self, object val, int error_num) except -1

src/oracledb/connection.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,7 @@ def __init__(
610610
"""
611611

612612
super().__init__()
613+
self._pool = pool
613614

614615
# determine if thin mode is being used
615616
with driver_mode.get_manager() as mode_mgr:
@@ -664,17 +665,29 @@ def __init__(
664665

665666
def __del__(self):
666667
if self._impl is not None:
667-
self._impl.close(in_del=True)
668-
self._impl = None
668+
self._close(in_del=True)
669669

670670
def __enter__(self):
671671
self._verify_connected()
672672
return self
673673

674674
def __exit__(self, exc_type, exc_value, exc_tb):
675675
if self._impl is not None:
676-
self._impl.close(in_del=True)
677-
self._impl = None
676+
self._close()
677+
678+
def _close(self, in_del=False):
679+
"""
680+
Closes the connection and makes it unusable for further operations. An
681+
Error exception will be raised if any operation is attempted with this
682+
connection after this method completes successfully.
683+
"""
684+
if self._pool is not None:
685+
pool_impl = self._pool._impl
686+
if pool_impl is not None:
687+
pool_impl.return_connection(self._impl, in_del)
688+
else:
689+
self._impl.close(in_del)
690+
self._impl = None
678691

679692
def _create_queue(self, impl):
680693
"""
@@ -743,8 +756,7 @@ def close(self) -> None:
743756
connection after this method completes successfully.
744757
"""
745758
self._verify_connected()
746-
self._impl.close()
747-
self._impl = None
759+
self._close()
748760

749761
def commit(self) -> None:
750762
"""
@@ -1574,6 +1586,7 @@ def __init__(
15741586
directly but only indirectly through async_connect().
15751587
"""
15761588
super().__init__()
1589+
self._pool = pool
15771590
self._connect_coroutine = self._connect(dsn, pool, params, kwargs)
15781591

15791592
def __await__(self):
@@ -1590,8 +1603,21 @@ async def __aenter__(self):
15901603

15911604
async def __aexit__(self, *exc_info):
15921605
if self._impl is not None:
1593-
await self._impl.close()
1594-
self._impl = None
1606+
await self._close()
1607+
1608+
async def _close(self, in_del=False):
1609+
"""
1610+
Closes the connection and makes it unusable for further operations. An
1611+
Error exception will be raised if any operation is attempted with this
1612+
connection after this method completes successfully.
1613+
"""
1614+
if self._pool is not None:
1615+
pool_impl = self._pool._impl
1616+
if pool_impl is not None:
1617+
await pool_impl.return_connection(self._impl, in_del)
1618+
else:
1619+
await self._impl.close(in_del)
1620+
self._impl = None
15951621

15961622
async def _connect(self, dsn, pool, params, kwargs):
15971623
"""
@@ -1718,8 +1744,7 @@ async def close(self) -> None:
17181744
Closes the connection.
17191745
"""
17201746
self._verify_connected()
1721-
await self._impl.close()
1722-
self._impl = None
1747+
await self._close()
17231748

17241749
async def commit(self) -> None:
17251750
"""

src/oracledb/impl/base/connect_params.pyx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,17 @@ cdef class ConnectParamsImpl:
294294
header = json.loads(base64.b64decode(header_seg))
295295
return datetime.datetime.utcfromtimestamp(header["exp"])
296296

297+
cdef bint _get_uses_drcp(self):
298+
"""
299+
Returns a boolean indicating if any of the descriptions associated with
300+
the parameters make use of DRCP.
301+
"""
302+
cdef Description description
303+
for description in self.description_list.children:
304+
if description.server_type == "pooled":
305+
return True
306+
return False
307+
297308
cdef str _get_wallet_password(self):
298309
"""
299310
Returns the wallet password, after removing the obfuscation.

src/oracledb/impl/base/pool.pyx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ cdef class BasePoolImpl:
8989
def reconfigure(self, uint32_t min, uint32_t max, uint32_t increment):
9090
errors._raise_not_supported("reconfiguring a pool")
9191

92+
def return_connection(self, BaseConnImpl conn_impl, in_del=False):
93+
errors._raise_not_supported("returning a connection to a pool")
94+
9295
def set_getmode(self, uint8_t value):
9396
errors._raise_not_supported("setting the 'get' mode of a pool")
9497

src/oracledb/impl/thick/pool.pyx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,12 @@ cdef class ThickPoolImpl(BasePoolImpl):
320320
self.max = max
321321
self.increment = increment
322322

323+
def return_connection(self, ThickConnImpl conn_impl, bint in_del=False):
324+
"""
325+
Internal method for returning a connection to the pool.
326+
"""
327+
conn_impl.close(in_del)
328+
323329
def set_getmode(self, uint8_t value):
324330
"""
325331
Internal method for setting the method by which connections are

src/oracledb/impl/thin/connection.pyx

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ cdef class BaseThinConnImpl(BaseConnImpl):
4646
bint _client_identifier_modified
4747
str _module
4848
bint _module_modified
49-
BaseThinPoolImpl _pool
5049
bytes _ltxid
5150
str _current_schema
5251
bint _current_schema_modified
@@ -70,6 +69,7 @@ cdef class BaseThinConnImpl(BaseConnImpl):
7069
int _dbobject_type_cache_num
7170
bytes _combo_key
7271
str _connection_id
72+
bint _is_pooled
7373
bint _is_pool_extra
7474
bytes _transaction_context
7575
uint8_t pipeline_mode
@@ -174,11 +174,6 @@ cdef class BaseThinConnImpl(BaseConnImpl):
174174
message.context = self._transaction_context
175175
return message
176176

177-
cdef int _force_close(self) except -1:
178-
self._pool = None
179-
self._clear_dbobject_type_cache()
180-
self._protocol._force_close()
181-
182177
cdef Statement _get_statement(self, str sql = None,
183178
bint cache_statement = False):
184179
"""
@@ -198,11 +193,8 @@ cdef class BaseThinConnImpl(BaseConnImpl):
198193
self._drcp_enabled = description.server_type == "pooled"
199194
if self._cclass is None:
200195
self._cclass = description.cclass
201-
if self._cclass is None and self._pool is not None \
202-
and self._drcp_enabled:
203-
gen_uuid = uuid.uuid4()
204-
self._cclass = f"DPY:{base64.b64encode(gen_uuid.bytes).decode()}"
205-
params._default_description.cclass = self._cclass
196+
if self._cclass is None:
197+
self._cclass = params._default_description.cclass
206198

207199
cdef int _post_connect_phase_two(self, ConnectParamsImpl params) except -1:
208200
"""
@@ -350,6 +342,13 @@ cdef class ThinConnImpl(BaseThinConnImpl):
350342
BaseThinConnImpl.__init__(self, dsn, params)
351343
self._protocol = Protocol()
352344

345+
cdef int _close(self):
346+
"""
347+
Internal method for closing the connection.
348+
"""
349+
cdef Protocol protocol = <Protocol> self._protocol
350+
protocol._close(self)
351+
353352
cdef int _connect_with_address(self, Address address,
354353
Description description,
355354
ConnectParamsImpl params,
@@ -441,9 +440,12 @@ cdef class ThinConnImpl(BaseThinConnImpl):
441440
protocol._process_single_message(message)
442441

443442
def close(self, bint in_del=False):
443+
"""
444+
Internal method for closing the connection to the database.
445+
"""
444446
cdef Protocol protocol = <Protocol> self._protocol
445447
try:
446-
protocol._close(self)
448+
protocol.close(self, in_del)
447449
except (ssl.SSLError, exceptions.DatabaseError):
448450
pass
449451

@@ -455,17 +457,17 @@ cdef class ThinConnImpl(BaseThinConnImpl):
455457
protocol._process_single_message(message)
456458

457459
def connect(self, ConnectParamsImpl params):
458-
# specify that binding a string to a LOB value is possible in thin
459-
# mode without the use of asyncio (will be removed in a future release)
460-
self._allow_bind_str_to_lob = True
461-
460+
cdef Protocol protocol = <Protocol> self._protocol
462461
try:
463462
self._pre_connect(params)
464463
self._connect_with_params(params)
465464
self._post_connect_phase_two(params)
466465
except:
467-
self._force_close()
466+
protocol._disconnect()
468467
raise
468+
# specify that binding a string to a LOB value is possible in thin
469+
# mode without the use of asyncio (will be removed in a future release)
470+
self._allow_bind_str_to_lob = True
469471

470472
def create_queue_impl(self):
471473
return ThinQueueImpl.__new__(ThinQueueImpl)
@@ -954,7 +956,7 @@ cdef class AsyncThinConnImpl(BaseThinConnImpl):
954956
"""
955957
cdef BaseAsyncProtocol protocol = <BaseAsyncProtocol> self._protocol
956958
try:
957-
await protocol._close(self)
959+
await protocol.close(self, in_del)
958960
except (ssl.SSLError, exceptions.DatabaseError):
959961
pass
960962

@@ -979,7 +981,7 @@ cdef class AsyncThinConnImpl(BaseThinConnImpl):
979981
await self._connect_with_params(params)
980982
self._post_connect_phase_two(params)
981983
except:
982-
self._force_close()
984+
protocol._disconnect()
983985
raise
984986

985987
def create_queue_impl(self):

src/oracledb/impl/thin/messages/auth.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ cdef class AuthMessage(Message):
271271
# to a pool
272272
if description.purity == PURITY_DEFAULT \
273273
and self.conn_impl._drcp_enabled:
274-
if self.conn_impl._pool is None:
274+
if self.conn_impl._is_pooled:
275275
self.purity = PURITY_NEW
276276
else:
277277
self.purity = PURITY_SELF

src/oracledb/impl/thin/messages/base.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ cdef class Message:
7272
code=self.error_info.num,
7373
offset=self.error_info.pos)
7474
if error.is_session_dead:
75-
self.conn_impl._protocol._force_close()
75+
self.conn_impl._protocol._disconnect()
7676
raise error.exc_type(error)
7777

7878
cdef int _initialize(self, BaseThinConnImpl conn_impl) except -1:

0 commit comments

Comments
 (0)