Skip to content

Commit

Permalink
Fix error message when idle time is exceeded by the connection.
Browse files Browse the repository at this point in the history
  • Loading branch information
anthony-tuininga committed Aug 20, 2024
1 parent c513952 commit 1ede3a4
Show file tree
Hide file tree
Showing 8 changed files with 45 additions and 24 deletions.
4 changes: 4 additions & 0 deletions doc/src/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Thin Mode Changes
(`issue 368 <https://github.com/oracle/python-oracledb/issues/368>`__).
#) Fixed bug resulting in an inability to connect to Oracle Database 23ai
instances which have fast authentication disabled.
#) Fixed error message when idle time is exceeded by a connection. The error
``DPY-4033: the database closed the connection because the connection's
idle time has been exceeded`` is now raised when this situation is
detected.
#) Reworked connection string parser:

- Fixed parsing an :ref:`Easy Connect <easyconnect>` string starting
Expand Down
5 changes: 5 additions & 0 deletions src/oracledb/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ def _raise_not_supported(feature: str) -> None:
ERR_IFILE_CYCLE_DETECTED = 4030
ERR_INVALID_VECTOR = 4031
ERR_INVALID_SSL_VERSION = 4032
ERR_EXCEEDED_IDLE_TIME = 4033

# error numbers that result in InternalError
ERR_MESSAGE_TYPE_UNKNOWN = 5000
Expand Down Expand Up @@ -496,6 +497,10 @@ def _raise_not_supported(feature: str) -> None:
ERR_DUPLICATED_PARAMETER: (
'"{deprecated_name}" and "{new_name}" cannot be specified together'
),
ERR_EXCEEDED_IDLE_TIME: (
"the database closed the connection because the connection's idle "
"time has been exceeded"
),
ERR_EXECUTE_MODE_ONLY_FOR_DML: (
'parameters "batcherrors" and "arraydmlrowcounts" may only be '
"true when used with insert, update, delete and merge statements"
Expand Down
2 changes: 1 addition & 1 deletion src/oracledb/impl/thin/connection.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ cdef class BaseThinConnImpl(BaseConnImpl):

def get_is_healthy(self):
return self._protocol._transport is not None \
and not self._protocol._read_buf._session_needs_to_be_closed
and self._protocol._read_buf._pending_error_num == 0

def get_ltxid(self):
return self._ltxid or b''
Expand Down
1 change: 1 addition & 0 deletions src/oracledb/impl/thin/constants.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ cdef enum:
TNS_ERR_NO_DATA_FOUND = 1403
TNS_ERR_SESSION_SHUTDOWN = 12572
TNS_ERR_ARRAY_DML_ERRORS = 24381
TNS_ERR_EXCEEDED_IDLE_TIME = 2396

# message types
cdef enum:
Expand Down
3 changes: 1 addition & 2 deletions src/oracledb/impl/thin/messages.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ cdef class Message:
to avoid overhead using the constructor, a special hook method is used
instead.
"""
if conn_impl._protocol._transport is None:
errors._raise_err(errors.ERR_NOT_CONNECTED)
conn_impl._protocol._read_buf._check_connected()
self.conn_impl = conn_impl
self.message_type = TNS_MSG_TYPE_FUNCTION
self.error_info = _OracleErrorInfo.__new__(_OracleErrorInfo)
Expand Down
39 changes: 26 additions & 13 deletions src/oracledb/impl/thin/packet.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,8 @@ cdef class ReadBuffer(Buffer):
cdef:
ssize_t _saved_packet_pos, _next_packet_pos, _saved_pos
ChunkedBytesBuffer _chunked_bytes_buf
bint _session_needs_to_be_closed
const char_type _split_data[255]
uint32_t _pending_error_num
Packet _current_packet
Transport _transport
list _saved_packets
Expand All @@ -214,6 +214,23 @@ cdef class ReadBuffer(Buffer):
self._caps = caps
self._chunked_bytes_buf = ChunkedBytesBuffer()

cdef int _check_connected(self):
"""
Checks to see if the transport is connected and throws the appropriate
exception if not.
"""
if self._pending_error_num != 0:
if self._transport is not None:
self._transport.disconnect()
self._transport = None
if self._pending_error_num == TNS_ERR_EXCEEDED_IDLE_TIME:
errors._raise_err(errors.ERR_EXCEEDED_IDLE_TIME)
else:
errors._raise_err(errors.ERR_UNSUPPORTED_INBAND_NOTIFICATION,
err_num=self._pending_error_num)
elif self._transport is None:
errors._raise_err(errors.ERR_NOT_CONNECTED)

cdef int _get_int_length_and_sign(self, uint8_t *length,
bint *is_negative,
uint8_t max_length) except -1:
Expand Down Expand Up @@ -311,7 +328,6 @@ cdef class ReadBuffer(Buffer):
"""
cdef:
uint16_t control_type
uint32_t error_num
Buffer buf
buf = Buffer.__new__(Buffer)
buf._populate_from_bytes(packet.buf)
Expand All @@ -321,16 +337,11 @@ cdef class ReadBuffer(Buffer):
self._caps.supports_oob = False
elif control_type == TNS_CONTROL_TYPE_INBAND_NOTIFICATION:
buf.skip_raw_bytes(4) # skip first integer
buf.read_uint32(&error_num)
if error_num == TNS_ERR_SESSION_SHUTDOWN \
or error_num == TNS_ERR_INBAND_MESSAGE:
self._session_needs_to_be_closed = True
else:
errors._raise_err(errors.ERR_UNSUPPORTED_INBAND_NOTIFICATION,
err_num=error_num)
buf.read_uint32(&self._pending_error_num)

cdef int _process_packet(self, Packet packet,
bint *notify_waiter) except -1:
bint *notify_waiter,
bint check_connected) except -1:
"""
Processes a packet. If the packet is a control packet it is processed
immediately and discarded; otherwise, it is added to the list of saved
Expand All @@ -341,6 +352,8 @@ cdef class ReadBuffer(Buffer):
if packet.packet_type == TNS_PACKET_TYPE_CONTROL:
self._process_control_packet(packet)
notify_waiter[0] = False
if check_connected:
self._check_connected()
else:
self._saved_packets.append(packet)
notify_waiter[0] = \
Expand Down Expand Up @@ -379,7 +392,7 @@ cdef class ReadBuffer(Buffer):
if self._current_packet.packet_type == TNS_PACKET_TYPE_DATA:
self.read_uint16(&data_flags)
if data_flags == TNS_DATA_FLAGS_EOF:
self._session_needs_to_be_closed = True
self._pending_error_num = TNS_ERR_SESSION_SHUTDOWN

cdef int notify_packet_received(self) except -1:
"""
Expand Down Expand Up @@ -618,7 +631,7 @@ cdef class ReadBuffer(Buffer):
bint notify_waiter
Packet packet
packet = self._transport.read_packet()
self._process_packet(packet, &notify_waiter)
self._process_packet(packet, &notify_waiter, False)
if notify_waiter:
self._start_packet()

Expand Down Expand Up @@ -698,7 +711,7 @@ cdef class ReadBuffer(Buffer):
raise OutOfPackets()
while True:
packet = self._transport.read_packet()
self._process_packet(packet, &notify_waiter)
self._process_packet(packet, &notify_waiter, True)
if notify_waiter:
break
self._start_packet()
Expand Down
4 changes: 2 additions & 2 deletions src/oracledb/impl/thin/pool.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -846,12 +846,12 @@ cdef class PooledConnRequest:
double elapsed_time
bint has_data_ready
if not buf._transport._is_async:
while not buf._session_needs_to_be_closed:
while buf._pending_error_num == 0:
buf._transport.has_data_ready(&has_data_ready)
if not has_data_ready:
break
buf.check_control_packet()
if buf._session_needs_to_be_closed:
if buf._pending_error_num != 0:
self.pool_impl._drop_conn_impl(conn_impl)
self._open_count -= 1
else:
Expand Down
11 changes: 5 additions & 6 deletions src/oracledb/impl/thin/protocol.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ cdef class BaseProtocol:
"""
buf.start_request(TNS_PACKET_TYPE_DATA, 0, TNS_DATA_FLAGS_EOF)
buf.end_request()
self._transport.disconnect()
self._transport = None
self._force_close()

cdef int _force_close(self) except -1:
"""
Expand All @@ -100,7 +99,7 @@ cdef class BaseProtocol:
established.
"""
conn_impl.warning = auth_message.warning
self._read_buf._session_needs_to_be_closed = False
self._read_buf._pending_error_num = 0
self._in_connect = False

cdef int _release_drcp_session(self, BaseThinConnImpl conn_impl,
Expand Down Expand Up @@ -163,7 +162,7 @@ cdef class Protocol(BaseProtocol):

# if the session was marked as needing to be closed, force it
# closed immediately (unless it was already closed)
if self._read_buf._session_needs_to_be_closed \
if self._read_buf._pending_error_num != 0 \
and self._transport is not None:
self._force_close()

Expand Down Expand Up @@ -520,7 +519,7 @@ cdef class BaseAsyncProtocol(BaseProtocol):

# if the session was marked as needing to be closed, force it
# closed immediately (unless it was already closed)
if self._read_buf._session_needs_to_be_closed \
if self._read_buf._pending_error_num != 0 \
and self._transport is not None:
self._force_close()

Expand Down Expand Up @@ -886,7 +885,7 @@ cdef class BaseAsyncProtocol(BaseProtocol):
Packet packet
packet = self._transport.extract_packet(data)
while packet is not None:
self._read_buf._process_packet(packet, &notify_waiter)
self._read_buf._process_packet(packet, &notify_waiter, False)
if notify_waiter:
self._read_buf.notify_packet_received()
packet = self._transport.extract_packet()
Expand Down

0 comments on commit 1ede3a4

Please sign in to comment.