Skip to content

2.0.3: Cursor, Statement, and Connection finalizers raise unraisable DatabaseError after connection loss #75

Closed

Description

@HNJAMeindersma

Description

When a Firebird connection is interrupted or times out while a cursor is streaming records, firebird-driver 2.0.3 correctly raises a DatabaseError for the original connection failure.

My application catches that exception and explicitly attempts to close the cursor, roll back the transaction, close the transaction manager, and close the connection.

However, if one or more of these cleanup operations fail because the connection is already unavailable, the affected driver objects remain in a partially open state. When they are subsequently garbage-collected, their __del__() methods attempt the same cleanup again and raise additional DatabaseError exceptions.

Because these exceptions occur inside object finalizers, they cannot be caught by my application's normal try/except blocks. Python reports them through sys.unraisablehook as:

Exception ignored in: <function Cursor.__del__ ...>
Exception ignored in: <function Statement.__del__ ...>
Exception ignored in: <function Connection.__del__ ...>

This still occurs with firebird-driver 2.0.3.

Environment

  • Operating system: Windows 11
  • Python: 3.12.10
  • firebird-driver: 2.0.3
  • Firebird server: 3.0.x
  • Multiprocessing start method: spawn
  • Connection failure type: connection timeout or interrupted network connection
  • Observed SQLSTATE: 08006
  • Observed SQLCODE: -902
  • Observed GDS codes:
    • 335544726, error reading data from the connection
    • 335544727, error writing data to the connection

Observed behavior

The original connection failure is caught normally:

Firebird database error while streaming table 'xxxxx':
Error reading data from the connection.;
SQLSTATE='08006';
SQLCODE=-902;
GDS=(335544726,)

Explicit cursor cleanup then also fails and is caught by the application:

Failed to close the Firebird streaming cursor:
Error writing data to the connection.;
SQLSTATE='08006';
SQLCODE=-902;
GDS=(335544727,)

After the application has caught both errors, garbage collection produces additional tracebacks:

Exception ignored in: <function Cursor.__del__ at 0x...>
Traceback (most recent call last):
  File "...firebird\driver\core.py", line 3363, in __del__
    self.close()
  File "...firebird\driver\core.py", line 4111, in close
    self._stmt.free()
  File "...firebird\driver\core.py", line 3043, in free
    self._istmt.free()
  File "...firebird\driver\interfaces.py", line 830, in free
    self._check()
  File "...firebird\driver\interfaces.py", line 141, in _check
    raise self.__report(DatabaseError, self.status.get_errors())
firebird.driver.types.DatabaseError:
    Error writing data to the connection.

The same behavior occurs for the connection:

Exception ignored in: <function Connection.__del__ at 0x...>
Traceback (most recent call last):
  File "...firebird\driver\core.py", line 1800, in __del__
    self._close()
  File "...firebird\driver\core.py", line 1832, in _close
    self.main_transaction._finish(DefaultAction.ROLLBACK)
  File "...firebird\driver\core.py", line 2632, in _finish
    self.rollback()
  File "...firebird\driver\core.py", line 2716, in rollback
    self._tra.rollback()
  File "...firebird\driver\interfaces.py", line 466, in rollback
    self._check()
  File "...firebird\driver\interfaces.py", line 141, in _check
    raise self.__report(DatabaseError, self.status.get_errors())
firebird.driver.types.DatabaseError:
    connection shutdown

A statement finalizer produces another traceback:

Exception ignored in: <function Statement.__del__ at 0x...>
Traceback (most recent call last):
  File "...firebird\driver\core.py", line 3024, in __del__
    self.free()
  File "...firebird\driver\core.py", line 3043, in free
    self._istmt.free()
  File "...firebird\driver\interfaces.py", line 830, in free
    self._check()
  File "...firebird\driver\interfaces.py", line 141, in _check
    raise self.__report(DatabaseError, self.status.get_errors())
firebird.driver.types.DatabaseError:
    connection shutdown

Depending on which driver objects remain alive, the same issue may also affect TransactionManager.__del__().

Activity

  1. pcisar commented on Oct 7, 2026

    @pcisar
    Contributor

    __del__ finalizers are very tricky beasts. I did my best to prevent such situations, but it's a "whack-a-mole" all around as systems keep inventing new ways how to break. When designing the new driver, I faced a major dilemma regarding whether to implement them, as I knew it was technically impossible to guarantee that Firebird API calls would not fail catastrophically in the event of a severe system disruption.

    The Firebird API requires everything to be properly terminated during normal operation in order to release server resources. However, it does not strictly enforce this if the system simply "quietly disappears"; the server will eventually detect this and release the resources itself.

    Consequently, failing to close the connection during an application crash or a normal shutdown is essentially harmless for Firebird; thus, a finalizer is not strictly necessary.

    However, failing to properly release resources during routine operation is a serious issue, as it can overwhelm the server. For this reason, the driver provides a context manager for every object that holds server-side resources. I considered having the finalizer simply emit a warning instead of releasing the resources—since such a failure essentially represents a programming error—but I was advised that there are special cases where a context manager cannot be used and proper resource release might be overlooked for other reasons. So, I ultimately implemented finalizers. I regret that decision to this day.

    I will certainly fix any error related to finalizers that originates primarily on the Python side. However, I certainly cannot handle every scenario where the connection to the Firebird API or server breaks down in a way that is simply impossible to predict or manage at the driver level. I realize that the consequences in such cases range from unpleasant to fatal. Frankly, I do not know how to handle such situations within the driver other than by having the user disable the finalizers of the affected objects.

    So, I could add bool property finalize with True default to affected classes that would trun __del__ to NOOP when False. Setting it could even propagate to all managed objects (i.e. Connection -> Transactions -> Statements.
    Would that be an acceptable solution to your problem?

  2. HNJAMeindersma commented on Oct 28, 2026

    @HNJAMeindersma
    Author

    For me it's not about the server side or releasing open connection/resources on the server. I'm sure that these will be released after a certain timeout. It's about the python client side. When, for example, a connection loss happends, I expect the driver to report on this through raising an exception and then clean itself up somewhat "nicely" (on the client side only).

    If I compare connection loss behaviour to the PostgreSQL python client (psycopg) then that seems to go allot more cleanly.

    Since I'm a user of your module and do not know the inner workings of it, it is hard to tell how an acceptable solution would be designed. I can only tell what would be expected or desireable behaviour.

    My assumption would be that any __del__ or garbage collection will not also trigger its own exceptions when another has already been raised by a, for example, connection loss. It should still clean itself up, but without trying to do so with a connection towards the server.

  3. added a commit that references this issue on Oct 29, 2026
  4. pcisar commented on Oct 29, 2026

    @pcisar
    Contributor

    Could you please check the fix?

  5. HNJAMeindersma commented on Oct 30, 2026

    @HNJAMeindersma
    Author

    firebird-base: 2.0.4
    firebird-driver: 2.0.3

    2026-10-30 11:28:39 | DEBUG    | PID 38956  | CENSORED       | root | Starting streaming records from application database...
    2026-10-30 11:30:11 | DEBUG    | PID 38956  | CENSORED       | root | Processing record # 10,000
    2026-10-30 11:31:54 | DEBUG    | PID 38956  | CENSORED       | root | Processing record # 20,000
    2026-10-30 11:32:34 | WARNING  | PID 38956  | CENSORED       | root | Failed to close Firebird streaming cursor for table 'CENSORED': DatabaseError: Error writing data to the connection.
    2026-10-30 11:32:34 | WARNING  | PID 38956  | CENSORED       | root | Failed to roll back Firebird streaming transaction for table 'CENSORED': DatabaseError: Error writing data to the connection.
    2026-10-30 11:32:34 | WARNING  | PID 38956  | CENSORED       | root | Failed to close Firebird streaming transaction for table 'CENSORED': DatabaseError: Error writing data to the connection.
    2026-10-30 11:32:34 | ERROR    | PID 38956  | CENSORED       | root | Firebird database error while streaming rows from a Firebird table (table_name='CENSORED'): DatabaseError: Error readingdata from the connection.; SQLSTATE='08006'; SQLCODE=-902; GDS=(335544726,)
    2026-10-30 11:32:34 | INFO     | PID 38956  | CENSORED       | root | Removing database logging handler and closing logging database connection for current process
    Exception ignored in: <function Cursor.__del__ at 0x0000022A06DBE340>
    Traceback (most recent call last):
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 3363, in __del__
        self.close()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 4111, in close
        self._stmt.free()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 3043, in free
        self._istmt.free()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\interfaces.py", line 830, in free
        self._check()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\interfaces.py", line 141, in _check
        raise self.__report(DatabaseError, self.status.get_errors())
    firebird.driver.types.DatabaseError: Error writing data to the connection.
    Exception ignored in: <function Connection.__del__ at 0x0000022A06DB8860>
    Traceback (most recent call last):
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 1800, in __del__
        self._close()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 1832, in _close
        self.main_transaction._finish(DefaultAction.ROLLBACK)
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 2632, in _finish
        self.rollback()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 2716, in rollback
        self._tra.rollback()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\interfaces.py", line 466, in rollback
        self._check()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\interfaces.py", line 141, in _check
        raise self.__report(DatabaseError, self.status.get_errors())
    firebird.driver.types.DatabaseError: connection shutdown
    Exception ignored in: <function Statement.__del__ at 0x0000022A06DBCB80>
    Traceback (most recent call last):
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 3024, in __del__
        self.free()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 3043, in free
        self._istmt.free()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\interfaces.py", line 830, in free
        self._check()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\interfaces.py", line 141, in _check
        raise self.__report(DatabaseError, self.status.get_errors())
    firebird.driver.types.DatabaseError: connection shutdown
    2026-10-30 11:32:34 | WARNING  | PID 12892  | MainProcess    | root | Process 38956 for table 'CENSORED' exited with code 2. Retry 1/3 is scheduled in 20.0 seconds
    

    firebird-base: 2.0.4
    firebird-driver: 2.1.0

    2026-10-30 11:38:02 | DEBUG    | PID 18876  | CENSORED       | root | Starting streaming records from application database...
    2026-10-30 11:39:35 | DEBUG    | PID 18876  | CENSORED       | root | Processing record # 10,000
    2026-10-30 11:41:18 | DEBUG    | PID 18876  | CENSORED       | root | Processing record # 20,000
    2026-10-30 11:41:54 | WARNING  | PID 18876  | CENSORED       | root | Failed to close Firebird streaming cursor for table 'CENSORED': DatabaseError: Error writing data to the connection.
    2026-10-30 11:41:54 | WARNING  | PID 18876  | CENSORED       | root | Failed to roll back Firebird streaming transaction for table 'CENSORED': DatabaseError: Error writing data to the connection.
    2026-10-30 11:41:54 | WARNING  | PID 18876  | CENSORED       | root | Failed to close Firebird streaming transaction for table 'CENSORED': DatabaseError: Error writing data to the connection.
    2026-10-30 11:41:54 | ERROR    | PID 18876  | CENSORED       | root | Firebird database error while streaming rows from a Firebird table (table_name='CENSORED'): DatabaseError: Error readingdata from the connection.; SQLSTATE='08006'; SQLCODE=-902; GDS=(335544726,)
    2026-10-30 11:41:54 | INFO     | PID 18876  | CENSORED       | root | Removing database logging handler and closing logging database connection for current process
    Exception ignored in: <function Cursor.__del__ at 0x000002893FCDECA0>
    Traceback (most recent call last):
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 3356, in __del__
        self.close()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 4103, in close
        self._stmt.free()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 3040, in free
        self._istmt.free()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\interfaces.py", line 830, in free
        self._check()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\interfaces.py", line 141, in _check
        raise self.__report(DatabaseError, self.status.get_errors())
    firebird.driver.types.DatabaseError: Error writing data to the connection.
    Exception ignored in: <function Connection.__del__ at 0x000002893FCD91C0>
    Traceback (most recent call last):
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 1799, in __del__
        self._close()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 1831, in _close
        self.main_transaction._finish(DefaultAction.ROLLBACK)
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 2629, in _finish
        self.rollback()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 2713, in rollback
        self._tra.rollback()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\interfaces.py", line 466, in rollback
        self._check()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\interfaces.py", line 141, in _check
        raise self.__report(DatabaseError, self.status.get_errors())
    firebird.driver.types.DatabaseError: connection shutdown
    Exception ignored in: <function Statement.__del__ at 0x000002893FCDD4E0>
    Traceback (most recent call last):
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 3021, in __del__
        self.free()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\core.py", line 3040, in free
        self._istmt.free()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\interfaces.py", line 830, in free
        self._check()
      File "C:\CENSORED\.venv\Lib\site-packages\firebird\driver\interfaces.py", line 141, in _check
        raise self.__report(DatabaseError, self.status.get_errors())
    firebird.driver.types.DatabaseError: connection shutdown
    2026-10-30 11:41:55 | WARNING  | PID 20736  | MainProcess    | root | Process 18876 for table 'CENSORED' exited with code 2. Retry 1/3 is scheduled in 20.0 seconds
    

    firebird-base: 2.0.4
    firebird-driver: 2.1.1.dev0

    2026-10-30 11:18:11 | DEBUG    | PID 32028  | CENSORED       | root | Starting streaming records from application database...
    2026-10-30 11:19:44 | DEBUG    | PID 32028  | CENSORED       | root | Processing record # 10,000
    2026-10-30 11:21:28 | DEBUG    | PID 32028  | CENSORED       | root | Processing record # 20,000
    2026-10-30 11:22:44 | WARNING  | PID 32028  | CENSORED       | root | Failed to close Firebird streaming cursor for table 'CENSORED': DatabaseError: Error writing data to the connection.
    2026-10-30 11:22:44 | WARNING  | PID 32028  | CENSORED       | root | Failed to roll back Firebird streaming transaction for table 'CENSORED': DatabaseError: Error writing data to the connection.
    2026-10-30 11:22:44 | WARNING  | PID 32028  | CENSORED       | root | Failed to close Firebird streaming transaction for table 'CENSORED': DatabaseError: Error writing data to the connection.
    2026-10-30 11:22:44 | ERROR    | PID 32028  | CENSORED       | root | Firebird database error while streaming rows from a Firebird table (table_name='CENSORED'): DatabaseError: Error reading data from the connection.; SQLSTATE='08006'; SQLCODE=-902; GDS=(335544726,)
    2026-10-30 11:22:44 | INFO     | PID 32028  | CENSORED       | root | Removing database logging handler and closing logging database connection for current process
    2026-10-30 11:22:44 | WARNING  | PID 17496  | MainProcess    | root | Process 32028 for table 'CENSORED' exited with code 2. Retry 1/3 is scheduled in 20.0 seconds
    

    Tested it using a firewall block rule on the local network side after my process hit ~20.000 rows. On version 2.1.1.dev0 no additional uncatched exceptions are raised. All WARNING and ERROR logger messages come from my code and are supposted to be there in this form. I'm happy with your changes/fix!

  6. pcisar commented on Oct 30, 2026

    @pcisar
    Contributor

    See version 2.1.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions