FirebirdSQL / fdb

Firebird Driver for Python
https://www.firebirdsql.org/en/devel-python-driver/
Other
60 stars 26 forks source link

fdb.connect() spoils POSIX signal handlers #16

Open o3bvv opened 4 years ago

o3bvv commented 4 years ago

Hello again,

I'm trying to investigate an issue with behavior signal handlers during and after invocation of fdb.connect().

I have a handler for SIGINT and SIGTERM which is used for application's graceful shutdown, e.g.:

import signal

def handler(signal_no, stack_frame):
   report_exit_request()
   notify_exit_subscribers()

signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGTERM, handler)

The handler works perfectly before the call to fdb.connect(): it captures signals, reports the exit request, notifies subscribers and the main thread waits till all shut down work is finished:

2020-05-26 19:27:06.192580 +0000 [I] [27634 main] app: run… (version='1.0.0')
2020-05-26 19:27:06.193394 +0000 [D] [27634 main] state: loading…
2020-05-26 19:27:06.196915 +0000 [D] [27634 main] state: validating…
2020-05-26 19:27:06.198532 +0000 [D] [27634 main] state: loaded (version='1.0.0')
^C2020-05-26 19:27:07.293636 +0000 [D] [27634 main] exit handler: got exit request
2020-05-26 19:27:16.204282 +0000 [D] [27634 main] state: saving…
2020-05-26 19:27:16.207451 +0000 [D] [27634 main] state: saved
2020-05-26 19:27:16.207780 +0000 [I] [27634 main] app: done

But if fdb.connect() is invoked, the thread is killed immediately after receiving a signal:

2020-05-26 19:30:23.119293 +0000 [I] [27725 main] app: run… (version='1.0.0')
2020-05-26 19:30:23.119877 +0000 [D] [27725 main] state: loading…
2020-05-26 19:30:23.121167 +0000 [D] [27725 main] state: validating…
2020-05-26 19:30:23.122865 +0000 [D] [27725 main] state: loaded (version='1.0.0')
2020-05-26 19:30:23.123084 +0000 [D] [27725 main] db: connecting…
2020-05-26 19:30:23.123252 +0000 [D] [27725 main] db: connection attempt #1…
2020-05-26 19:30:23.264796 +0000 [D] [27725 main] db: connected (firebird_version='2.5.9.27139', ods_version=11.1, sql_dialect=1)
^C2020-05-26 19:30:25.173235 +0000 [D] [27725 main] exit handler: got exit request

As you can see, exit handler was invoked, but the app was killed before finishing (no log messages, as before). It's just a matter of chance that the handler's message managed to propagate to stdout via logger.

If the connection takes too long and the app gets a signal during connection, the thread just bails:

2020-05-26 19:34:07.990052 +0000 [I] [27755 main] app: run… (version='1.0.0')
2020-05-26 19:34:07.990657 +0000 [D] [27755 main] state: loading…
2020-05-26 19:34:07.993211 +0000 [D] [27755 main] state: validating…
2020-05-26 19:34:07.994736 +0000 [D] [27755 main] state: loaded (version='1.0.0')
2020-05-26 19:34:07.994903 +0000 [D] [27755 main] db: connecting…
2020-05-26 19:34:07.995045 +0000 [D] [27755 main] db: connection attempt #1…
^C

I've investigated signal handlers and masks before and after the call to fdb.connect() and they are the same. The rest of the app works as expected.

Noteworthy, setting signal handlers after the call to fdb.connect() once again makes the app to behave as expected. However, this cannot be considered as a workaround, as the app bails during the invocation of fdb.connect().

I've tried to investigate C++ sources of the driver and Python sources of its wrapper, but I couldn't spot any signal management inside them so far.

Could you please provide any suggestions regarding possible root cause?

My OS is macOS 10.15.1 and the driver is provided by FirebirdCS-2.5.9-27139-x86_64.pkg.

pcisar commented 4 years ago

FDB does nothing with POSIX signal handlers, BUT it uses ctypes to work with Firebird client library that certainly does something to finalize so/ddl gracefuly. The library initialization is deferred until first request that needs it, i.e. connect, create_database or service.connect, so you can choose the client library to use and to allow fdb to be imported (or even installed) without Firebird installed. You can force the library initialization using load_api() call. See https://fdb.readthedocs.io/en/latest/reference.html#fdb.load_api

Try using load_api() before you install the signal handlers.

o3bvv commented 4 years ago

Yes, that's exactly what I've thought about and tried to do.

I've called fbd.load_api() before setting up the signal handler and I've commented out that call inside fbd.connect(): the app reacts as expected.

But calling fbd.connect() (even with disabled internal call to load_api()) still spoils things up and sending a signal to the app kills it immediately.

I've just checked the work on a windows server and it's fine, though:

paste.png

Of cource, on windows SetConsoleCtrlHandler() is used instead of signal(), but in my case it's the target platform, so I'm fine with postponing the issue at unix-like systems. Maybe it's a Python's bug, like this one: https://bugs.python.org/issue27889

I'd appreciate if you notify me in case you find a resolution for this or similar issue.

Thanks for your responses!