Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle Ctrl-C better #231

Closed
dvarrazzo opened this issue Feb 22, 2022 · 7 comments
Closed

Handle Ctrl-C better #231

dvarrazzo opened this issue Feb 22, 2022 · 7 comments

Comments

@dvarrazzo
Copy link
Member

Currently, if Ctrl-C is pressed during a long query, the connection is left in a broken state.

>>> import psycopg
>>> cnn = psycopg.connect()

>>> cnn.execute("select pg_sleep(5)")
^CTraceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/piro/dev/psycopg3/psycopg/psycopg/connection.py", line 808, in execute
    return cur.execute(query, params, prepare=prepare)
  File "/home/piro/dev/psycopg3/psycopg/psycopg/cursor.py", line 557, in execute
    self._conn.wait(
  File "/home/piro/dev/psycopg3/psycopg/psycopg/connection.py", line 860, in wait
    return waiting.wait(gen, self.pgconn.socket, timeout=timeout)
  File "/home/piro/dev/psycopg3/psycopg/psycopg/waiting.py", line 229, in wait_epoll
    fileevs = epoll.poll(timeout)
KeyboardInterrupt

>>> cnn.execute("select 1")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/piro/dev/psycopg3/psycopg/psycopg/connection.py", line 811, in execute
    raise ex.with_traceback(None)
psycopg.OperationalError: sending query failed: another command is already in progress

What pgcli does with psycopg2 makes sense: send a cancel and wait for the response. We should probably do the same by default in our wait functions, or maybe just out of them, because inside them we don't really have access to the PGconn structure.

dvarrazzo added a commit that referenced this issue Feb 22, 2022
On KeyboardInterrupt, send a cancel to the server and keep waiting for
the result of the cancel, then re-raise KeyboardInterrupt.

Only fixed on sync connections. Left a failing test for async
connections; the test fails with an output from the script such as:

    error ignored in rollback on <psycopg.AsyncConnection [ACTIVE] ...>: sending query failed: another command is already in progress
    Traceback (most recent call last):
      File "<string>", line 27, in <module>
      File "/usr/lib/python3.8/asyncio/runners.py", line 44, in run
        return loop.run_until_complete(main)
      File "/usr/lib/python3.8/asyncio/base_events.py", line 603, in run_until_complete
        self.run_forever()
      File "/usr/lib/python3.8/asyncio/base_events.py", line 570, in run_forever
        self._run_once()
      File "/usr/lib/python3.8/asyncio/base_events.py", line 1823, in _run_once
        event_list = self._selector.select(timeout)
      File "/usr/lib/python3.8/selectors.py", line 468, in select
        fd_event_list = self._selector.poll(timeout, max_ev)
    KeyboardInterrupt

And the except branch in AsyncConnection.wait() is not reached.

See #231
dvarrazzo added a commit that referenced this issue Feb 22, 2022
On KeyboardInterrupt, send a cancel to the server and keep waiting for
the result of the cancel, which is expected to raise a QueryCanceled,
then re-raise KeyboardInterrupt.

Before this, the connection was left in ACTIVE state, so it couldn't be rolled
back.

Only fixed on sync connections. Left a failing test for async
connections; the test fails with an output from the script such as:

    error ignored in rollback on <psycopg.AsyncConnection [ACTIVE] ...>:
    sending query failed: another command is already in progress
    Traceback (most recent call last):
      File "<string>", line 27, in <module>
      File "/usr/lib/python3.8/asyncio/runners.py", line 44, in run
        return loop.run_until_complete(main)
      File "/usr/lib/python3.8/asyncio/base_events.py", line 603, in run_until_complete
        self.run_forever()
      File "/usr/lib/python3.8/asyncio/base_events.py", line 570, in run_forever
        self._run_once()
      File "/usr/lib/python3.8/asyncio/base_events.py", line 1823, in _run_once
        event_list = self._selector.select(timeout)
      File "/usr/lib/python3.8/selectors.py", line 468, in select
        fd_event_list = self._selector.poll(timeout, max_ev)
    KeyboardInterrupt

And the except branch in `AsyncConnection.wait()` is not reached.

See #231
@dvarrazzo
Copy link
Member Author

Fixed for sync connections, but the same fix doesn't work for async ones. It seems that the Ctrl-C is trapped by asyncio.run() and never reaches the handler in AsyncConnection.wait(). Enabling the xfailed test tests/test_concurrency_async.py::test_ctrl_c there will be an output such as:

error ignored in rollback on <psycopg.AsyncConnection [ACTIVE] ...>: sending query failed: another command is already in progress
Traceback (most recent call last):
  File "<string>", line 27, in <module>
  File "/usr/lib/python3.8/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.8/asyncio/base_events.py", line 603, in run_until_complete
    self.run_forever()
  File "/usr/lib/python3.8/asyncio/base_events.py", line 570, in run_forever
    self._run_once()
  File "/usr/lib/python3.8/asyncio/base_events.py", line 1823, in _run_once
    event_list = self._selector.select(timeout)
  File "/usr/lib/python3.8/selectors.py", line 468, in select
    fd_event_list = self._selector.poll(timeout, max_ev)
KeyboardInterrupt

@sekrause
Copy link

Which Python version are you using for development? I'm asking because of this bug which was fixed in Python 3.8: https://bugs.python.org/issue23057

@dvarrazzo
Copy link
Member Author

I am personally developing on Python 3.8 on Linux, and the CI runs the tests on Python 3.6 to 3.10 on Linux, macOS, Windows.

At the moment, this MR has problem:

The BPO issue you link seems about signals+windows but also signals+linux don't seem to obey as I hope yet

dvarrazzo added a commit that referenced this issue Mar 2, 2022
On KeyboardInterrupt, send a cancel to the server and keep waiting for
the result of the cancel, which is expected to raise a QueryCanceled,
then re-raise KeyboardInterrupt.

Before this, the connection was left in ACTIVE state, so it couldn't be rolled
back.

Only fixed on sync connections. Left a failing test for async
connections; the test fails with an output from the script such as:

    error ignored in rollback on <psycopg.AsyncConnection [ACTIVE] ...>:
    sending query failed: another command is already in progress
    Traceback (most recent call last):
      File "<string>", line 27, in <module>
      File "/usr/lib/python3.8/asyncio/runners.py", line 44, in run
        return loop.run_until_complete(main)
      File "/usr/lib/python3.8/asyncio/base_events.py", line 603, in run_until_complete
        self.run_forever()
      File "/usr/lib/python3.8/asyncio/base_events.py", line 570, in run_forever
        self._run_once()
      File "/usr/lib/python3.8/asyncio/base_events.py", line 1823, in _run_once
        event_list = self._selector.select(timeout)
      File "/usr/lib/python3.8/selectors.py", line 468, in select
        fd_event_list = self._selector.poll(timeout, max_ev)
    KeyboardInterrupt

And the except branch in `AsyncConnection.wait()` is not reached.

See #231
dvarrazzo added a commit that referenced this issue Mar 2, 2022
On KeyboardInterrupt, send a cancel to the server and keep waiting for
the result of the cancel, which is expected to raise a QueryCanceled,
then re-raise KeyboardInterrupt.

Before this, the connection was left in ACTIVE state, so it couldn't be rolled
back.

Only fixed on sync connections. Left a failing test for async
connections; the test fails with an output from the script such as:

    error ignored in rollback on <psycopg.AsyncConnection [ACTIVE] ...>:
    sending query failed: another command is already in progress
    Traceback (most recent call last):
      File "<string>", line 27, in <module>
      File "/usr/lib/python3.8/asyncio/runners.py", line 44, in run
        return loop.run_until_complete(main)
      File "/usr/lib/python3.8/asyncio/base_events.py", line 603, in run_until_complete
        self.run_forever()
      File "/usr/lib/python3.8/asyncio/base_events.py", line 570, in run_forever
        self._run_once()
      File "/usr/lib/python3.8/asyncio/base_events.py", line 1823, in _run_once
        event_list = self._selector.select(timeout)
      File "/usr/lib/python3.8/selectors.py", line 468, in select
        fd_event_list = self._selector.poll(timeout, max_ev)
    KeyboardInterrupt

And the except branch in `AsyncConnection.wait()` is not reached.

See #231
dvarrazzo added a commit that referenced this issue Mar 2, 2022
On KeyboardInterrupt, send a cancel to the server and keep waiting for
the result of the cancel, which is expected to raise a QueryCanceled,
then re-raise KeyboardInterrupt.

Before this, the connection was left in ACTIVE state, so it couldn't be rolled
back.

Only fixed on sync connections. Left a failing test for async
connections; the test fails with an output from the script such as:

    error ignored in rollback on <psycopg.AsyncConnection [ACTIVE] ...>:
    sending query failed: another command is already in progress
    Traceback (most recent call last):
      File "<string>", line 27, in <module>
      File "/usr/lib/python3.8/asyncio/runners.py", line 44, in run
        return loop.run_until_complete(main)
      File "/usr/lib/python3.8/asyncio/base_events.py", line 603, in run_until_complete
        self.run_forever()
      File "/usr/lib/python3.8/asyncio/base_events.py", line 570, in run_forever
        self._run_once()
      File "/usr/lib/python3.8/asyncio/base_events.py", line 1823, in _run_once
        event_list = self._selector.select(timeout)
      File "/usr/lib/python3.8/selectors.py", line 468, in select
        fd_event_list = self._selector.poll(timeout, max_ev)
    KeyboardInterrupt

And the except branch in `AsyncConnection.wait()` is not reached.

See #231
@dvarrazzo
Copy link
Member Author

dvarrazzo commented Mar 13, 2022

Released in Psycopg 3.0.10. Currently untested on Windows (the test should be fixed) and not working in async mode, which I don't know if it can be solved.

@dlax
Copy link
Contributor

dlax commented Jun 8, 2022

https://mail.python.org/archives/list/python-dev@python.org/message/HTWTWS3M2PKQILSKN3V7UM73ZKOOQ33K/:

KeyboardInterrupt is generally not handled properly by asyncio, the normal
behavior here is that the code just exits with a traceback.

@dvarrazzo
Copy link
Member Author

@dlax Later on I discovered loop.add_signal_handler() which makes the test behave well (382c674).

@dlax
Copy link
Contributor

dlax commented Jun 8, 2022

Ah, thanks for the heads up; looks good then!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants