Merge lp:~free.ekanayaka/landscape-client/method-call-defer into lp:~landscape/landscape-client/amp-trunk
- method-call-defer
- Merge into amp-trunk
Proposed by
Free Ekanayaka
Status: | Superseded |
---|---|
Proposed branch: | lp:~free.ekanayaka/landscape-client/method-call-defer |
Merge into: | lp:~landscape/landscape-client/amp-trunk |
Diff against target: |
402 lines (+221/-24) 2 files modified
landscape/lib/amp.py (+117/-7) landscape/lib/tests/test_amp.py (+104/-17) |
To merge this branch: | bzr merge lp:~free.ekanayaka/landscape-client/method-call-defer |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Landscape | Pending | ||
Landscape | Pending | ||
Review via email:
|
This proposal has been superseded by a proposal from 2010-01-20.
Commit message
Description of the change
To post a comment you must log in.
- 216. By Free Ekanayaka
-
Use maybeDeferred to simplify receive_method_call logic (Duncan [3])
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'landscape/lib/amp.py' | |||
2 | --- landscape/lib/amp.py 2010-01-20 08:33:12 +0000 | |||
3 | +++ landscape/lib/amp.py 2010-01-20 09:25:23 +0000 | |||
4 | @@ -1,8 +1,10 @@ | |||
5 | 1 | """Expose the methods of a remote object over AMP.""" | 1 | """Expose the methods of a remote object over AMP.""" |
6 | 2 | 2 | ||
7 | 3 | from uuid import uuid4 | ||
8 | 3 | from twisted.internet.defer import Deferred | 4 | from twisted.internet.defer import Deferred |
9 | 4 | from twisted.internet.protocol import ServerFactory, ClientFactory | 5 | from twisted.internet.protocol import ServerFactory, ClientFactory |
10 | 5 | from twisted.protocols.amp import Argument, String, Command, AMP | 6 | from twisted.protocols.amp import Argument, String, Command, AMP |
11 | 7 | from twisted.python.failure import Failure | ||
12 | 6 | 8 | ||
13 | 7 | from landscape.lib.bpickle import loads, dumps, dumps_table | 9 | from landscape.lib.bpickle import loads, dumps, dumps_table |
14 | 8 | 10 | ||
15 | @@ -35,11 +37,21 @@ | |||
16 | 35 | ("args", MethodCallArgument()), | 37 | ("args", MethodCallArgument()), |
17 | 36 | ("kwargs", MethodCallArgument())] | 38 | ("kwargs", MethodCallArgument())] |
18 | 37 | 39 | ||
20 | 38 | response = [("result", MethodCallArgument())] | 40 | response = [("result", MethodCallArgument()), |
21 | 41 | ("deferred", String(optional=True))] | ||
22 | 39 | 42 | ||
23 | 40 | errors = {MethodCallError: "METHOD_CALL_ERROR"} | 43 | errors = {MethodCallError: "METHOD_CALL_ERROR"} |
24 | 41 | 44 | ||
25 | 42 | 45 | ||
26 | 46 | class DeferredResponse(Command): | ||
27 | 47 | """Fire a L{Deferred} associated with an outstanding method call result.""" | ||
28 | 48 | |||
29 | 49 | arguments = [("uuid", String()), | ||
30 | 50 | ("result", MethodCallArgument(optional=True)), | ||
31 | 51 | ("failure", String(optional=True))] | ||
32 | 52 | requiresAnswer = False | ||
33 | 53 | |||
34 | 54 | |||
35 | 43 | class MethodCallServerProtocol(AMP): | 55 | class MethodCallServerProtocol(AMP): |
36 | 44 | """Expose methods of a local object over AMP. | 56 | """Expose methods of a local object over AMP. |
37 | 45 | 57 | ||
38 | @@ -72,13 +84,105 @@ | |||
39 | 72 | result = getattr(self.factory.object, method)(*args, **kwargs) | 84 | result = getattr(self.factory.object, method)(*args, **kwargs) |
40 | 73 | except Exception, error: | 85 | except Exception, error: |
41 | 74 | raise MethodCallError("Remote exception %s" % str(error)) | 86 | raise MethodCallError("Remote exception %s" % str(error)) |
42 | 87 | |||
43 | 88 | # If the method returns a Deferred, register a callback that will | ||
44 | 89 | # eventually notify the remote peer of its success or failure. | ||
45 | 90 | if isinstance(result, Deferred): | ||
46 | 91 | |||
47 | 92 | # If the Deferred was already fired, we can return its result | ||
48 | 93 | if result.called: | ||
49 | 94 | if isinstance(result.result, Failure): | ||
50 | 95 | failure = str(result.result.value) | ||
51 | 96 | result.addErrback(lambda error: None) # Stop propagating | ||
52 | 97 | raise MethodCallError(failure) | ||
53 | 98 | return {"result": result.result} | ||
54 | 99 | |||
55 | 100 | uuid = str(uuid4()) | ||
56 | 101 | result.addBoth(self.send_deferred_response, uuid) | ||
57 | 102 | return {"result": None, "deferred": uuid} | ||
58 | 103 | |||
59 | 75 | if not MethodCallArgument.check(result): | 104 | if not MethodCallArgument.check(result): |
60 | 76 | raise MethodCallError("Non-serializable result") | 105 | raise MethodCallError("Non-serializable result") |
61 | 77 | return {"result": result} | 106 | return {"result": result} |
62 | 78 | 107 | ||
63 | 108 | def send_deferred_response(self, result, uuid): | ||
64 | 109 | """Send a L{DeferredResponse} for the deferred with given C{uuid}. | ||
65 | 110 | |||
66 | 111 | This is called when the result of a L{Deferred} returned by an | ||
67 | 112 | object's method becomes available. A L{DeferredResponse} notifying | ||
68 | 113 | such result (either success or failure) is sent to the peer. | ||
69 | 114 | """ | ||
70 | 115 | kwargs = {"uuid": uuid} | ||
71 | 116 | if isinstance(result, Failure): | ||
72 | 117 | kwargs["failure"] = str(result.value) | ||
73 | 118 | else: | ||
74 | 119 | kwargs["result"] = result | ||
75 | 120 | self.callRemote(DeferredResponse, **kwargs) | ||
76 | 121 | |||
77 | 79 | 122 | ||
78 | 80 | class MethodCallClientProtocol(AMP): | 123 | class MethodCallClientProtocol(AMP): |
80 | 81 | """Calls methods of a remote object over L{AMP}.""" | 124 | """Calls methods of a remote object over L{AMP}. |
81 | 125 | |||
82 | 126 | @note: If the remote method returns a deferred, the associated local | ||
83 | 127 | deferred returned by L{send_method_call} will result in the same | ||
84 | 128 | callback value of the remote deferred. | ||
85 | 129 | @cvar timeout: A timeout for remote methods returning L{Deferred}s, if a | ||
86 | 130 | response for the deferred is not received within this amount of | ||
87 | 131 | seconds, the remote method call will errback with a L{MethodCallError}. | ||
88 | 132 | """ | ||
89 | 133 | timeout = 60 | ||
90 | 134 | |||
91 | 135 | def __init__(self): | ||
92 | 136 | AMP.__init__(self) | ||
93 | 137 | self._pending_responses = {} | ||
94 | 138 | |||
95 | 139 | @DeferredResponse.responder | ||
96 | 140 | def receive_deferred_response(self, uuid, result, failure): | ||
97 | 141 | """Receive the deferred L{MethodCall} response. | ||
98 | 142 | |||
99 | 143 | @param uuid: The id of the L{MethodCall} we're getting the result of. | ||
100 | 144 | @param result: The result of the associated deferred if successful. | ||
101 | 145 | @param failure: The failure message of the deferred if it failed. | ||
102 | 146 | """ | ||
103 | 147 | self.fire_pending_response(uuid, result, failure) | ||
104 | 148 | return {} | ||
105 | 149 | |||
106 | 150 | def fire_pending_response(self, uuid, result, failure): | ||
107 | 151 | """Fire the L{Deferred} associated with a pending response. | ||
108 | 152 | |||
109 | 153 | @param uuid: The id of the L{MethodCall} we're getting the result of. | ||
110 | 154 | @param result: The result of the associated deferred if successful. | ||
111 | 155 | @param failure: The failure message of the deferred if it failed. | ||
112 | 156 | """ | ||
113 | 157 | try: | ||
114 | 158 | deferred, call = self._pending_responses.pop(uuid) | ||
115 | 159 | except KeyError: | ||
116 | 160 | # Late response for a request that has timeout, just ignore it | ||
117 | 161 | return | ||
118 | 162 | if not call.called: | ||
119 | 163 | call.cancel() | ||
120 | 164 | if failure is None: | ||
121 | 165 | deferred.callback({"result": result}) | ||
122 | 166 | else: | ||
123 | 167 | deferred.errback(MethodCallError(failure)) | ||
124 | 168 | |||
125 | 169 | def handle_response(self, response): | ||
126 | 170 | """Handle a L{MethodCall} response. | ||
127 | 171 | |||
128 | 172 | If the response is tagged as deferred, it will be queued as pending, | ||
129 | 173 | and a L{Deferred} is returned, which will be fired as soon as the | ||
130 | 174 | final response becomes available, or the timeout is reached. | ||
131 | 175 | """ | ||
132 | 176 | if response["deferred"]: | ||
133 | 177 | uuid = response["deferred"] | ||
134 | 178 | deferred = Deferred() | ||
135 | 179 | call = self.factory.reactor.callLater(self.timeout, | ||
136 | 180 | self.fire_pending_response, | ||
137 | 181 | uuid, None, "timeout") | ||
138 | 182 | self._pending_responses[uuid] = (deferred, call) | ||
139 | 183 | return deferred | ||
140 | 184 | |||
141 | 185 | return response | ||
142 | 82 | 186 | ||
143 | 83 | def send_method_call(self, method, args=[], kwargs={}): | 187 | def send_method_call(self, method, args=[], kwargs={}): |
144 | 84 | """Send a L{MethodCall} command with the given arguments. | 188 | """Send a L{MethodCall} command with the given arguments. |
145 | @@ -87,8 +191,12 @@ | |||
146 | 87 | @param args: The positional arguments to pass to the remote method. | 191 | @param args: The positional arguments to pass to the remote method. |
147 | 88 | @param kwargs: The keyword arguments to pass to the remote method. | 192 | @param kwargs: The keyword arguments to pass to the remote method. |
148 | 89 | """ | 193 | """ |
151 | 90 | return self.callRemote(MethodCall, | 194 | result = self.callRemote(MethodCall, |
152 | 91 | method=method, args=args, kwargs=kwargs) | 195 | method=method, args=args, kwargs=kwargs) |
153 | 196 | # The result can be C{None} only if the requested command is a | ||
154 | 197 | # DeferredResponse, which has requiresAnswer set to False | ||
155 | 198 | if result is not None: | ||
156 | 199 | return result.addCallback(self.handle_response) | ||
157 | 92 | 200 | ||
158 | 93 | 201 | ||
159 | 94 | class MethodCallServerFactory(ServerFactory): | 202 | class MethodCallServerFactory(ServerFactory): |
160 | @@ -111,16 +219,18 @@ | |||
161 | 111 | 219 | ||
162 | 112 | def __init__(self, reactor, notifier): | 220 | def __init__(self, reactor, notifier): |
163 | 113 | """ | 221 | """ |
165 | 114 | @param reactor: The reactor used to schedule connection callbacks. | 222 | @param reactor: The reactor used by the created protocols to schedule |
166 | 223 | notifications and timeouts. | ||
167 | 115 | @param notifier: A function that will be called when the connection is | 224 | @param notifier: A function that will be called when the connection is |
168 | 116 | established. It will be passed the protocol instance as argument. | 225 | established. It will be passed the protocol instance as argument. |
169 | 117 | """ | 226 | """ |
171 | 118 | self._reactor = reactor | 227 | self.reactor = reactor |
172 | 119 | self._notifier = notifier | 228 | self._notifier = notifier |
173 | 120 | 229 | ||
174 | 121 | def buildProtocol(self, addr): | 230 | def buildProtocol(self, addr): |
175 | 122 | protocol = self.protocol() | 231 | protocol = self.protocol() |
177 | 123 | self._reactor.callLater(0, self._notifier, protocol) | 232 | protocol.factory = self |
178 | 233 | self.reactor.callLater(0, self._notifier, protocol) | ||
179 | 124 | return protocol | 234 | return protocol |
180 | 125 | 235 | ||
181 | 126 | 236 | ||
182 | 127 | 237 | ||
183 | === modified file 'landscape/lib/tests/test_amp.py' | |||
184 | --- landscape/lib/tests/test_amp.py 2010-01-20 08:33:12 +0000 | |||
185 | +++ landscape/lib/tests/test_amp.py 2010-01-20 09:25:23 +0000 | |||
186 | @@ -1,9 +1,11 @@ | |||
187 | 1 | from twisted.internet import reactor | 1 | from twisted.internet import reactor |
188 | 2 | from twisted.internet.defer import Deferred | ||
189 | 2 | from twisted.internet.protocol import ClientCreator | 3 | from twisted.internet.protocol import ClientCreator |
190 | 3 | 4 | ||
191 | 4 | from landscape.lib.amp import ( | 5 | from landscape.lib.amp import ( |
194 | 5 | MethodCallError, MethodCallServerProtocol, MethodCallClientProtocol, | 6 | MethodCallError, MethodCallServerProtocol, |
195 | 6 | MethodCallServerFactory, RemoteObject, RemoteObjectCreator) | 7 | MethodCallClientProtocol, MethodCallServerFactory, |
196 | 8 | MethodCallClientFactory, RemoteObject, RemoteObjectCreator) | ||
197 | 7 | from landscape.tests.helpers import LandscapeTest | 9 | from landscape.tests.helpers import LandscapeTest |
198 | 8 | 10 | ||
199 | 9 | 11 | ||
200 | @@ -60,6 +62,24 @@ | |||
201 | 60 | def translate(self, word): | 62 | def translate(self, word): |
202 | 61 | raise WordsException("Unknown word") | 63 | raise WordsException("Unknown word") |
203 | 62 | 64 | ||
204 | 65 | def google(self, word): | ||
205 | 66 | deferred = Deferred() | ||
206 | 67 | if word == "Landscape": | ||
207 | 68 | reactor.callLater(0.01, lambda: deferred.callback("Cool!")) | ||
208 | 69 | elif word == "Easy query": | ||
209 | 70 | deferred.callback("Done!") | ||
210 | 71 | elif word == "Weird stuff": | ||
211 | 72 | reactor.callLater(0.01, lambda: deferred.errback(Exception("bad"))) | ||
212 | 73 | elif word == "Censored": | ||
213 | 74 | deferred.errback(Exception("very bad")) | ||
214 | 75 | elif word == "Long query": | ||
215 | 76 | # Do nothing, the deferred won't be fired at all | ||
216 | 77 | pass | ||
217 | 78 | elif word == "Slowish query": | ||
218 | 79 | # Fire the result after a while. | ||
219 | 80 | reactor.callLater(0.05, lambda: deferred.callback("Done!")) | ||
220 | 81 | return deferred | ||
221 | 82 | |||
222 | 63 | 83 | ||
223 | 64 | class WordsProtocol(MethodCallServerProtocol): | 84 | class WordsProtocol(MethodCallServerProtocol): |
224 | 65 | 85 | ||
225 | @@ -72,7 +92,8 @@ | |||
226 | 72 | "multiply_alphabetically", | 92 | "multiply_alphabetically", |
227 | 73 | "translate", | 93 | "translate", |
228 | 74 | "meaning_of_life", | 94 | "meaning_of_life", |
230 | 75 | "guess"] | 95 | "guess", |
231 | 96 | "google"] | ||
232 | 76 | 97 | ||
233 | 77 | 98 | ||
234 | 78 | class MethodCallProtocolTest(LandscapeTest): | 99 | class MethodCallProtocolTest(LandscapeTest): |
235 | @@ -114,7 +135,8 @@ | |||
236 | 114 | result = self.protocol.send_method_call(method="empty", | 135 | result = self.protocol.send_method_call(method="empty", |
237 | 115 | args=[], | 136 | args=[], |
238 | 116 | kwargs={}) | 137 | kwargs={}) |
240 | 117 | return self.assertSuccess(result, {"result": None}) | 138 | return self.assertSuccess(result, {"result": None, |
241 | 139 | "deferred": None}) | ||
242 | 118 | 140 | ||
243 | 119 | def test_with_return_value(self): | 141 | def test_with_return_value(self): |
244 | 120 | """ | 142 | """ |
245 | @@ -124,7 +146,8 @@ | |||
246 | 124 | result = self.protocol.send_method_call(method="motd", | 146 | result = self.protocol.send_method_call(method="motd", |
247 | 125 | args=[], | 147 | args=[], |
248 | 126 | kwargs={}) | 148 | kwargs={}) |
250 | 127 | return self.assertSuccess(result, {"result": "Words are cool"}) | 149 | return self.assertSuccess(result, {"result": "Words are cool", |
251 | 150 | "deferred": None}) | ||
252 | 128 | 151 | ||
253 | 129 | def test_with_one_argument(self): | 152 | def test_with_one_argument(self): |
254 | 130 | """ | 153 | """ |
255 | @@ -134,7 +157,8 @@ | |||
256 | 134 | result = self.protocol.send_method_call(method="capitalize", | 157 | result = self.protocol.send_method_call(method="capitalize", |
257 | 135 | args=["john"], | 158 | args=["john"], |
258 | 136 | kwargs={}) | 159 | kwargs={}) |
260 | 137 | return self.assertSuccess(result, {"result": "John"}) | 160 | return self.assertSuccess(result, {"result": "John", |
261 | 161 | "deferred": None}) | ||
262 | 138 | 162 | ||
263 | 139 | def test_with_boolean_return_value(self): | 163 | def test_with_boolean_return_value(self): |
264 | 140 | """ | 164 | """ |
265 | @@ -143,7 +167,8 @@ | |||
266 | 143 | result = self.protocol.send_method_call(method="is_short", | 167 | result = self.protocol.send_method_call(method="is_short", |
267 | 144 | args=["hi"], | 168 | args=["hi"], |
268 | 145 | kwargs={}) | 169 | kwargs={}) |
270 | 146 | return self.assertSuccess(result, {"result": True}) | 170 | return self.assertSuccess(result, {"result": True, |
271 | 171 | "deferred": None}) | ||
272 | 147 | 172 | ||
273 | 148 | def test_with_many_arguments(self): | 173 | def test_with_many_arguments(self): |
274 | 149 | """ | 174 | """ |
275 | @@ -152,7 +177,8 @@ | |||
276 | 152 | result = self.protocol.send_method_call(method="concatenate", | 177 | result = self.protocol.send_method_call(method="concatenate", |
277 | 153 | args=["You ", "rock"], | 178 | args=["You ", "rock"], |
278 | 154 | kwargs={}) | 179 | kwargs={}) |
280 | 155 | return self.assertSuccess(result, {"result": "You rock"}) | 180 | return self.assertSuccess(result, {"result": "You rock", |
281 | 181 | "deferred": None}) | ||
282 | 156 | 182 | ||
283 | 157 | def test_with_default_arguments(self): | 183 | def test_with_default_arguments(self): |
284 | 158 | """ | 184 | """ |
285 | @@ -162,7 +188,8 @@ | |||
286 | 162 | result = self.protocol.send_method_call(method="lower_case", | 188 | result = self.protocol.send_method_call(method="lower_case", |
287 | 163 | args=["OHH"], | 189 | args=["OHH"], |
288 | 164 | kwargs={}) | 190 | kwargs={}) |
290 | 165 | return self.assertSuccess(result, {"result": "ohh"}) | 191 | return self.assertSuccess(result, {"result": "ohh", |
291 | 192 | "deferred": None}) | ||
292 | 166 | 193 | ||
293 | 167 | def test_with_overriden_default_arguments(self): | 194 | def test_with_overriden_default_arguments(self): |
294 | 168 | """ | 195 | """ |
295 | @@ -173,7 +200,8 @@ | |||
296 | 173 | result = self.protocol.send_method_call(method="lower_case", | 200 | result = self.protocol.send_method_call(method="lower_case", |
297 | 174 | args=["OHH"], | 201 | args=["OHH"], |
298 | 175 | kwargs={"index": 2}) | 202 | kwargs={"index": 2}) |
300 | 176 | return self.assertSuccess(result, {"result": "OHh"}) | 203 | return self.assertSuccess(result, {"result": "OHh", |
301 | 204 | "deferred": None}) | ||
302 | 177 | 205 | ||
303 | 178 | def test_with_dictionary_arguments(self): | 206 | def test_with_dictionary_arguments(self): |
304 | 179 | """ | 207 | """ |
305 | @@ -183,7 +211,8 @@ | |||
306 | 183 | "alphabetically", | 211 | "alphabetically", |
307 | 184 | args=[{"foo": 2, "bar": 3}], | 212 | args=[{"foo": 2, "bar": 3}], |
308 | 185 | kwargs={}) | 213 | kwargs={}) |
310 | 186 | return self.assertSuccess(result, {"result": "barbarbarfoofoo"}) | 214 | return self.assertSuccess(result, {"result": "barbarbarfoofoo", |
311 | 215 | "deferred": None}) | ||
312 | 187 | 216 | ||
313 | 188 | def test_with_non_serializable_return_value(self): | 217 | def test_with_non_serializable_return_value(self): |
314 | 189 | """ | 218 | """ |
315 | @@ -211,17 +240,19 @@ | |||
316 | 211 | def setUp(self): | 240 | def setUp(self): |
317 | 212 | super(RemoteObjectTest, self).setUp() | 241 | super(RemoteObjectTest, self).setUp() |
318 | 213 | socket = self.mktemp() | 242 | socket = self.mktemp() |
322 | 214 | factory = MethodCallServerFactory(Words()) | 243 | server_factory = MethodCallServerFactory(Words()) |
323 | 215 | factory.protocol = WordsProtocol | 244 | server_factory.protocol = WordsProtocol |
324 | 216 | self.port = reactor.listenUNIX(socket, factory) | 245 | self.port = reactor.listenUNIX(socket, server_factory) |
325 | 217 | 246 | ||
326 | 218 | def set_remote(protocol): | 247 | def set_remote(protocol): |
327 | 219 | self.protocol = protocol | 248 | self.protocol = protocol |
328 | 220 | self.words = RemoteObject(protocol) | 249 | self.words = RemoteObject(protocol) |
329 | 221 | 250 | ||
333 | 222 | connector = ClientCreator(reactor, MethodCallClientProtocol) | 251 | connected = Deferred() |
334 | 223 | connected = connector.connectUNIX(socket) | 252 | connected.addCallback(set_remote) |
335 | 224 | return connected.addCallback(set_remote) | 253 | client_factory = MethodCallClientFactory(reactor, connected.callback) |
336 | 254 | reactor.connectUNIX(socket, client_factory) | ||
337 | 255 | return connected | ||
338 | 225 | 256 | ||
339 | 226 | def tearDown(self): | 257 | def tearDown(self): |
340 | 227 | self.protocol.transport.loseConnection() | 258 | self.protocol.transport.loseConnection() |
341 | @@ -319,6 +350,62 @@ | |||
342 | 319 | result = self.words.guess("word", "cool", value=4) | 350 | result = self.words.guess("word", "cool", value=4) |
343 | 320 | return self.assertSuccess(result, "Guessed!") | 351 | return self.assertSuccess(result, "Guessed!") |
344 | 321 | 352 | ||
345 | 353 | def test_with_success_full_deferred(self): | ||
346 | 354 | """ | ||
347 | 355 | If the target object method returns a L{Deferred}, it is handled | ||
348 | 356 | transparently. | ||
349 | 357 | """ | ||
350 | 358 | result = self.words.google("Landscape") | ||
351 | 359 | return self.assertSuccess(result, "Cool!") | ||
352 | 360 | |||
353 | 361 | def test_with_failing_deferred(self): | ||
354 | 362 | """ | ||
355 | 363 | If the target object method returns a failing L{Deferred}, a | ||
356 | 364 | L{MethodCallError} is raised. | ||
357 | 365 | """ | ||
358 | 366 | result = self.words.google("Weird stuff") | ||
359 | 367 | return self.assertFailure(result, MethodCallError) | ||
360 | 368 | |||
361 | 369 | def test_with_already_callback_deferred(self): | ||
362 | 370 | """ | ||
363 | 371 | The target object method can return an already fired L{Deferred}. | ||
364 | 372 | """ | ||
365 | 373 | result = self.words.google("Easy query") | ||
366 | 374 | return self.assertSuccess(result, "Done!") | ||
367 | 375 | |||
368 | 376 | def test_with_already_errback_deferred(self): | ||
369 | 377 | """ | ||
370 | 378 | If the target object method can return an already failed L{Deferred}. | ||
371 | 379 | """ | ||
372 | 380 | result = self.words.google("Censored") | ||
373 | 381 | return self.assertFailure(result, MethodCallError) | ||
374 | 382 | |||
375 | 383 | def test_with_deferred_timeout(self): | ||
376 | 384 | """ | ||
377 | 385 | If the peer protocol doesn't send a response for a deferred within | ||
378 | 386 | the given timeout, the method call fails. | ||
379 | 387 | """ | ||
380 | 388 | self.protocol.timeout = 0.1 | ||
381 | 389 | result = self.words.google("Long query") | ||
382 | 390 | return self.assertFailure(result, MethodCallError) | ||
383 | 391 | |||
384 | 392 | def test_with_late_response(self): | ||
385 | 393 | """ | ||
386 | 394 | If the peer protocol sends a late response for a request that has | ||
387 | 395 | already timeout, that response is ignored. | ||
388 | 396 | """ | ||
389 | 397 | self.protocol.timeout = 0.01 | ||
390 | 398 | result = self.words.google("Slowish query") | ||
391 | 399 | self.assertFailure(result, MethodCallError) | ||
392 | 400 | |||
393 | 401 | def assert_late_response_is_handled(ignored): | ||
394 | 402 | deferred = Deferred() | ||
395 | 403 | # We wait a bit to be sure that the late response gets delivered | ||
396 | 404 | reactor.callLater(0.1, lambda: deferred.callback(None)) | ||
397 | 405 | return deferred | ||
398 | 406 | |||
399 | 407 | return result.addCallback(assert_late_response_is_handled) | ||
400 | 408 | |||
401 | 322 | 409 | ||
402 | 323 | class RemoteObjectCreatorTest(LandscapeTest): | 410 | class RemoteObjectCreatorTest(LandscapeTest): |
403 | 324 | 411 |