Skip to content

Commit 8012b7e

Browse files
Merge pull request #47 from scivisum/bug/RD-49887_fix_deadlock
[RD-49887] Fix deadlock.
2 parents 3bb62a0 + faef148 commit 8012b7e

File tree

3 files changed

+36
-7
lines changed

3 files changed

+36
-7
lines changed

browserdebuggertools/wssessionmanager.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import socket
55
import time
66
import collections
7-
from threading import Thread, Lock, Event
7+
from threading import Thread, Lock, Event, RLock
88

99
from typing import Dict, Callable
1010

@@ -233,7 +233,7 @@ def __init__(self, port, host, timeout, domains=None):
233233
self._events_access_lock = Lock()
234234

235235
# Used to manage the health of the message producer
236-
self._message_producer_lock = Lock() # Lock making sure we don't create 2 ws connections
236+
self._message_producer_lock = RLock() # Lock making sure we don't create 2 ws connections
237237
self._last_not_ok = None
238238
self._message_producer_not_ok_count = 0
239239
self._send_queue = collections.deque()
@@ -255,6 +255,9 @@ def _check_message_producer(self):
255255
try:
256256
self._message_producer.health_check()
257257
except (websocket.WebSocketConnectionClosedException, WebSocketBlockedException):
258+
# If the current ws connection is only blocked, we better make sure we close it
259+
# before opening a new one.
260+
self._message_producer.close()
258261
self._increment_message_producer_not_ok()
259262
self._setup_ws_session()
260263

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
setup(
1414
name="browserdebuggertools",
15-
version="6.1.0",
15+
version="6.1.1",
1616
python_requires='>=3.8',
1717
packages=PACKAGES,
1818
install_requires=requires,

tests/integrationtests/test_wssessionmanager.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def send(self, data):
5353

5454

5555
class Test_WSSessionManager__execute(TestCase):
56-
""" It's very hard to make this test fail but it will catch major regressions
56+
""" It's very hard to make this test fail, but it will catch major regressions
5757
"""
5858
def setUp(self):
5959
self.pool = ThreadPool(10)
@@ -84,7 +84,7 @@ def _send(message):
8484

8585

8686
class Test_WSSessionManager_get_events(TestCase):
87-
""" It's very hard to make this test fail but it will catch major regressions
87+
""" It's very hard to make this test fail, but it will catch major regressions
8888
"""
8989

9090
def test_locked_get_events(self):
@@ -188,11 +188,36 @@ def close(self):
188188
pass
189189

190190

191+
class Test_WSSessionManager_blocked_with_domains(TestCase):
192+
""" We need this test to prove we have fixed the problem of a message producer being blocked.
193+
When it tried to recreate the websocket, it shouldn't deadlock.
194+
"""
195+
196+
def tearDown(self):
197+
self.session_manager.close()
198+
199+
def test_deadlock(self):
200+
201+
with patch.object(
202+
_WSMessageProducer, "_get_websocket",
203+
new=MagicMock(return_value=_DummyWebsocket())
204+
):
205+
206+
self.session_manager = WSSessionManager(
207+
1234, "localhost", 2, domains={"Network": {}}
208+
)
209+
# The first message producer and web socket are clocked.
210+
# When the ws session is recreated we get a new one which won't be blocked.
211+
self.session_manager._message_producer._BLOCKED_TIMEOUT = -1
212+
213+
self.session_manager.execute("Network", "enable")
214+
215+
191216
class Test_WSSessionManager_execute(TestCase):
192217

193218
@staticmethod
194219
def resetWS():
195-
""" flush_messages runs async so we only want the socket to start blocking when we're
220+
""" flush_messages runs async, so we only want the socket to start blocking when we're
196221
ready
197222
"""
198223
ExceptionThrowingWS.exceptions = 0
@@ -218,6 +243,7 @@ def test_thread_blocked_once(self):
218243
start = time.time()
219244
self.session_manager.execute("Network", "enable")
220245

246+
# todo wtf
221247
# We should find the execution result after 5 seconds because
222248
# we give the thread 5 seconds to poll before we consider it blocked,
223249
self.assertLess(time.time() - start, 10)
@@ -241,8 +267,8 @@ def test_thread_blocks_causes_timeout(self):
241267

242268
self.session_manager = WSSessionManager(1234, "localhost", 3)
243269
self.resetWS()
270+
start = time.time()
244271
with self.assertRaises(DevToolsTimeoutException):
245-
start = time.time()
246272
self.session_manager.execute("Network", "enable")
247273
self.assertLess(time.time() - start, 5)
248274

0 commit comments

Comments
 (0)