freedomofpress / securedrop-client

a Qt-based GUI for SecureDrop journalists 📰🗞️
GNU Affero General Public License v3.0
41 stars 39 forks source link

QSignalSpy.wait() doesn't behave like the docs suggest #1618

Open gonzalo-bulnes opened 1 year ago

gonzalo-bulnes commented 1 year ago

I think we've got a problem (Please find what I'm doing wrong! ><')

It seems like the wait() method of QSignalSpy in Python doesn't behave like the C++ docs suggest. Namely:

:apple: It doesn't return True when the signal was received at least once before the timeout. :apple: It doesn't return early once the signal was received (increasing the timeout of tests/test_qsignalspy.py::TestService::test_wait_without_assert to a few seconds makes that obvious - try it locally).

Minimal example: https://github.com/freedomofpress/securedrop-client/commit/a7f7868af8dc5b560daee9704398ade5e73a230b CI: https://app.circleci.com/pipelines/github/freedomofpress/securedrop-client/3043/workflows/42f33676-a8cf-47af-9b1f-c4b1a551f291/jobs/18248 Docs: https://doc.qt.io/qt-5/qsignalspy.html#wait

If this is indeed the case, then it is problematic for a few reasons:

If the wait() method was behaving as advertised:

Can someone take a look at the code above? I'd like to discuss it to confirm/infirm my suspicions :pear: :female-detective:.

gonzalo-bulnes commented 1 year ago

Note: It seems to me that asserting on spy.siValid() is pointless, since spying on an invalid signal already causes an error in Python.

gonzalo-bulnes commented 1 year ago

@cfm - we talked briefly about it today, I think my fears are founded.

@zekehuntergreen I think you may be interested in this conversation. It would clearly have implication on how we test some of the code we write. Some of the recommendations I made in #1604 might actually be ineffective, and we might only have been lucky so far. (I'm being less lucky in some new code that relies more heavily on that test pattern, that's how I noticed. #1615)

cfm commented 1 year ago

@gonzalo-bulnes, you may have already done all of this research, but just in case....

https://stackoverflow.com/a/55123043 seems to describe a similar race condition in which "QSignalSpy would catch the emitted signal before QSignalSpy::wait() was called". If QSignalSpy.wait() "starts an event loop that runs until the given signal is received" (my emphasis), then in the example—

https://github.com/freedomofpress/securedrop-client/blob/a7f7868af8dc5b560daee9704398ade5e73a230b/tests/test_qsignalspy.py#L43-L45

—neither QSignalSpy is wait()ing at the time of client.query.emit().

https://forum.qt.io/post/445592 suggests delaying the emission with a single-shot timer. That strikes me as just racing the race condition. :-(