Skip to content

Commit adbfb90

Browse files
Fix lost notification and potential deadlock
1 parent 3238b37 commit adbfb90

4 files changed

Lines changed: 22 additions & 14 deletions

File tree

src/cpp/daemon/py_monero_daemon.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,17 @@ class PyMoneroDaemonListener : public monero_daemon_listener {
2121

2222
class PyMoneroBlockNotifier : public PyMoneroDaemonListener {
2323
public:
24-
boost::mutex* temp;
25-
boost::condition_variable* cv;
26-
PyMoneroBlockNotifier(boost::mutex* temp, boost::condition_variable* cv) { this->temp = temp; this->cv = cv; }
24+
PyMoneroBlockNotifier(boost::mutex* temp, boost::condition_variable* cv, bool* ready) { this->temp = temp; this->cv = cv; this->ready = ready; }
2725
void on_block_header(const std::shared_ptr<monero::monero_block_header>& header) override {
26+
boost::mutex::scoped_lock lock(*temp);
2827
m_last_header = header;
28+
*ready = true;
2929
cv->notify_one();
3030
}
31+
private:
32+
boost::mutex* temp;
33+
boost::condition_variable* cv;
34+
bool* ready;
3135
};
3236

3337
class PyMoneroDaemon {

src/cpp/daemon/py_monero_daemon_rpc.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -616,19 +616,20 @@ void PyMoneroDaemonRpc::stop() {
616616
}
617617

618618
std::shared_ptr<monero::monero_block_header> PyMoneroDaemonRpc::wait_for_next_block_header() {
619-
// use mutex and condition variable to wait for block
619+
// use mutex and condition variable with predicate to wait for block
620620
boost::mutex temp;
621621
boost::condition_variable cv;
622+
bool ready = false;
622623

623624
// create listener which notifies condition variable when block is added
624-
auto block_listener = std::make_shared<PyMoneroBlockNotifier>(&temp, &cv);
625+
auto block_listener = std::make_shared<PyMoneroBlockNotifier>(&temp, &cv, &ready);
625626

626627
// register the listener
627628
add_listener(block_listener);
628629

629630
// wait until condition variable is notified
630631
boost::mutex::scoped_lock lock(temp);
631-
cv.wait(lock);
632+
cv.wait(lock, [&]() { return ready; });
632633

633634
// unregister the listener
634635
remove_listener(block_listener);

tests/test_monero_connection_manager.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class TestMoneroConnectionManager:
2121
"""Proxy used to simulate offline servers"""
2222

2323
_cm: MoneroConnectionManager | None = None
24+
"""Connection manager test instance."""
2425

2526
#region Fixtures
2627

@@ -122,7 +123,7 @@ def test_connection_manager(self, connection_manager: MoneroConnectionManager, c
122123

123124
# auto connect to the best available connection
124125
connection_manager.start_polling(Utils.SYNC_PERIOD_IN_MS)
125-
listener.wait_for_change(Utils.SYNC_PERIOD_IN_MS, "Waiting for auto connect to best available connection")
126+
listener.wait_for_change(num_expected_changes + 1, Utils.SYNC_PERIOD_IN_MS, f"Waiting for auto connect to best available connection")
126127
assert connection_manager.is_connected()
127128
connection = connection_manager.get_connection()
128129
assert connection is not None
@@ -166,7 +167,7 @@ def test_connection_manager(self, connection_manager: MoneroConnectionManager, c
166167
continue
167168
conn.proxy_uri = self.OFFLINE_PROXY_URI
168169

169-
listener.wait_for_change(Utils.SYNC_PERIOD_IN_MS, "Simulating priotizized servers shut down")
170+
listener.wait_for_change(num_expected_changes + 1, Utils.SYNC_PERIOD_IN_MS, "Simulating priotizized servers shut down")
170171
assert connection_manager.is_connected() is False, f"{connection_manager.get_connection().serialize()}"
171172
connection = connection_manager.get_connection()
172173

@@ -330,7 +331,7 @@ def test_connection_manager(self, connection_manager: MoneroConnectionManager, c
330331
poll_type=MoneroConnectionPollType.CURRENT
331332
)
332333

333-
listener.wait_for_change(Utils.SYNC_PERIOD_IN_MS, "Polling current connection")
334+
listener.wait_for_change(num_expected_changes + 1, Utils.SYNC_PERIOD_IN_MS, "Polling current connection")
334335
assert connection_manager.is_connected() is True
335336
num_expected_changes += 1
336337
assert num_expected_changes == listener.num_changed_connections
@@ -340,7 +341,7 @@ def test_connection_manager(self, connection_manager: MoneroConnectionManager, c
340341
num_expected_changes += 1
341342
assert num_expected_changes == listener.num_changed_connections
342343
connection_manager.start_polling(period_ms=Utils.SYNC_PERIOD_IN_MS, poll_type=MoneroConnectionPollType.ALL)
343-
listener.wait_for_change(Utils.SYNC_PERIOD_IN_MS, "Polling all connections")
344+
listener.wait_for_change(num_expected_changes + 1, Utils.SYNC_PERIOD_IN_MS, "Polling all connections")
344345
assert connection_manager.is_connected() is True
345346
num_expected_changes += 1
346347
assert num_expected_changes == listener.num_changed_connections
@@ -351,7 +352,7 @@ def test_connection_manager(self, connection_manager: MoneroConnectionManager, c
351352
for con in ordered_connections:
352353
con.proxy_uri = self.OFFLINE_PROXY_URI
353354

354-
listener.wait_for_change(Utils.SYNC_PERIOD_IN_MS, "Simulating total shut down")
355+
listener.wait_for_change(num_expected_changes + 1, Utils.SYNC_PERIOD_IN_MS, "Simulating total shut down")
355356
assert connection.is_online() is False, f"Expected offline connection: {connection.serialize()}"
356357
num_expected_changes += 1
357358
assert num_expected_changes == listener.num_changed_connections

tests/utils/connection_change_collector.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,21 @@ def on_connection_changed(self, connection: Optional[MoneroRpcConnection]) -> No
3434
logger.debug(f"Collecting connection change: {conn_str}")
3535
self.changed_connections.append(connection)
3636

37-
def wait_for_change(self, interval_ms: int = 5000, custom_message: str = "Waiting for connection change") -> Optional[MoneroRpcConnection]:
37+
def wait_for_change(self, expected_num_changes: int, interval_ms: int = 5000, custom_message: str = "Waiting for connection change") -> Optional[MoneroRpcConnection]:
3838
"""
3939
Wait until a connection change occurs.
4040
41+
:param int expected_num_changes: expected number of connection changes to wait for.
4142
:param int interval_ms: custom check interval in milliseconds (default 5000).
4243
:param str custom_message: custom message to show in debug during wait.
4344
:returns MoneroRpcConnection | None: changed connection.
4445
"""
4546

4647
last_num_changes: int = self.num_changed_connections
47-
while self.num_changed_connections <= last_num_changes:
48-
logger.debug(f"{custom_message} (connections {last_num_changes})...")
48+
while expected_num_changes > last_num_changes:
49+
logger.debug(f"{custom_message} (changes {last_num_changes}/{expected_num_changes})...")
4950
GenUtils.wait_for(interval_ms)
51+
last_num_changes = self.num_changed_connections
5052

5153
logger.debug(f"Connection changed (connections {self.num_changed_connections}).")
5254

0 commit comments

Comments
 (0)