Merge ~twom/launchpad:thread-state-not-in-state into launchpad:master

Proposed by Tom Wardill
Status: Merged
Approved by: Tom Wardill
Approved revision: a10fc2cb90ef7f2aed909316af1ce64eaebbef00
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~twom/launchpad:thread-state-not-in-state
Merge into: launchpad:master
Diff against target: 87 lines (+43/-1)
2 files modified
lib/lp/services/librarian/client.py (+3/-1)
lib/lp/services/librarian/tests/test_client.py (+40/-0)
Reviewer Review Type Date Requested Status
Thiago F. Pappacena (community) Approve
Ioana Lasc (community) Approve
Review via email: mp+389327@code.launchpad.net

Commit message

Move timeout out of local thread state

Description of the change

Keeping the timeout in the thread local (created in __init__) meant that if the _checkError method was called from another thread, the timeout value was missing.
Move it to a class attribute and add a test to ensure it's fixed.

Original work on the timeout test fix by ~pappacena in https://code.launchpad.net/~pappacena/launchpad/+git/launchpad/+merge/389162

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) wrote :

looks good to me

review: Approve
Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

LGTM, and cool test! Thank you!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/services/librarian/client.py b/lib/lp/services/librarian/client.py
2index 737d9e4..219d80d 100644
3--- a/lib/lp/services/librarian/client.py
4+++ b/lib/lp/services/librarian/client.py
5@@ -83,6 +83,8 @@ def compose_url(base_url, alias_path):
6 class FileUploadClient:
7 """Simple blocking client for uploading to the librarian."""
8
9+ s_poll_timeout = 0
10+
11 def __init__(self):
12 # This class is registered as a utility, which means an instance of
13 # it will be shared between threads. The easiest way of making this
14@@ -115,7 +117,7 @@ class FileUploadClient:
15 del self.state.f
16
17 def _checkError(self):
18- poll_result = self.state.s_poll.poll(0)
19+ poll_result = self.state.s_poll.poll(self.s_poll_timeout)
20 if poll_result:
21 fileno, event = poll_result[0]
22 # Accepts any event that contains input data. Even if we
23diff --git a/lib/lp/services/librarian/tests/test_client.py b/lib/lp/services/librarian/tests/test_client.py
24index e00a86e..9c671cb 100644
25--- a/lib/lp/services/librarian/tests/test_client.py
26+++ b/lib/lp/services/librarian/tests/test_client.py
27@@ -46,6 +46,27 @@ from lp.testing.layers import (
28 from lp.testing.views import create_webservice_error_view
29
30
31+class PropagatingThread(threading.Thread):
32+ """Thread class that propagates errors to the parent."""
33+ # https://stackoverflow.com/a/31614591
34+ def run(self):
35+ self.exc = None
36+ try:
37+ if hasattr(self, '_Thread__target'):
38+ # Thread uses name mangling prior to Python 3.
39+ self.ret = self._Thread__target(
40+ *self._Thread__args, **self._Thread__kwargs)
41+ else:
42+ self.ret = self._target(*self._args, **self._kwargs)
43+ except BaseException as e:
44+ self.exc = e
45+
46+ def join(self):
47+ super(PropagatingThread, self).join()
48+ if self.exc:
49+ raise self.exc
50+
51+
52 class InstrumentedLibrarianClient(LibrarianClient):
53
54 def __init__(self, *args, **kwargs):
55@@ -252,6 +273,16 @@ class LibrarianClientTestCase(TestCase):
56 'librarian', upload_host=upload_host, upload_port=upload_port)
57
58 client = LibrarianClient()
59+ # Artificially increases timeout to avoid race condition.
60+ # The fake server is running in another thread, and we are sure it
61+ # will (eventually) reply with an error message. So, let's just wait
62+ # until that message arrives.
63+ client.s_poll_timeout = 120
64+
65+ # Please, note the mismatch between file size (7) and its actual size
66+ # of the content (6). This is intentional, to make sure we are raising
67+ # the error coming from the fake server (and not the local check
68+ # right after, while uploading the file).
69 self.assertRaisesRegex(
70 UploadFailed, 'Server said early: STORE 7 sample.txt',
71 client.addFile, 'sample.txt', 7, StringIO('sample'), 'text/plain')
72@@ -467,6 +498,15 @@ class LibrarianClientTestCase(TestCase):
73
74 client_module._File = _File
75
76+ def test_thread_state_FileUploadClient(self):
77+ client = InstrumentedLibrarianClient()
78+ th = PropagatingThread(
79+ target=client.addFile,
80+ args=('sample.txt', 6, StringIO('sample'), 'text/plain'))
81+ th.start()
82+ th.join()
83+ self.assertEqual(5, client.check_error_calls)
84+
85
86 class TestWebServiceErrors(unittest.TestCase):
87 """ Test that errors are correctly mapped to HTTP status codes."""

Subscribers

People subscribed via source and target branches

to status/vote changes: