|
45 | 45 | ) |
46 | 46 |
|
47 | 47 | from tests.server import get_clock |
48 | | -from tests.unittest import TestCase |
| 48 | +from tests.unittest import TestCase, logcontext_clean |
49 | 49 |
|
50 | 50 | logger = logging.getLogger(__name__) |
51 | 51 |
|
@@ -198,7 +198,12 @@ def canceller(_d: Deferred) -> None: |
198 | 198 |
|
199 | 199 | self.failureResultOf(timing_out_d, defer.TimeoutError) |
200 | 200 |
|
201 | | - async def test_logcontext_is_preserved_on_cancellation(self) -> None: |
| 201 | + @logcontext_clean |
| 202 | + async def test_logcontext_is_preserved_on_timeout_cancellation(self) -> None: |
| 203 | + """ |
| 204 | + Test that the logcontext is preserved when we timeout and the deferred is |
| 205 | + cancelled. |
| 206 | + """ |
202 | 207 | # Sanity check that we start in the sentinel context |
203 | 208 | self.assertEqual(current_context(), SENTINEL_CONTEXT) |
204 | 209 |
|
@@ -270,6 +275,65 @@ def mark_was_cancelled(res: Failure) -> None: |
270 | 275 | # Back to the sentinel context |
271 | 276 | self.assertEqual(current_context(), SENTINEL_CONTEXT) |
272 | 277 |
|
| 278 | + @logcontext_clean |
| 279 | + async def test_logcontext_is_not_lost_when_awaiting_on_timeout_cancellation( |
| 280 | + self, |
| 281 | + ) -> None: |
| 282 | + """ |
| 283 | + Test that the logcontext isn't lost when we `await make_deferred_yieldable(...)` |
| 284 | + the deferred to complete/timeout and it times out. |
| 285 | + """ |
| 286 | + |
| 287 | + # Sanity check that we start in the sentinel context |
| 288 | + self.assertEqual(current_context(), SENTINEL_CONTEXT) |
| 289 | + |
| 290 | + # Create a deferred which we will never complete |
| 291 | + incomplete_d: Deferred = Deferred() |
| 292 | + |
| 293 | + async def competing_task() -> None: |
| 294 | + with LoggingContext( |
| 295 | + name="competing", server_name="test_server" |
| 296 | + ) as context_competing: |
| 297 | + timing_out_d = timeout_deferred( |
| 298 | + deferred=incomplete_d, |
| 299 | + timeout=1.0, |
| 300 | + clock=self.clock, |
| 301 | + ) |
| 302 | + self.assertNoResult(timing_out_d) |
| 303 | + # We should still be in the logcontext we started in |
| 304 | + self.assertIs(current_context(), context_competing) |
| 305 | + |
| 306 | + # Mimic the normal use case to wait for the work to complete or timeout. |
| 307 | + # |
| 308 | + # In this specific test, we expect the deferred to timeout and raise an |
| 309 | + # exception at this point. |
| 310 | + await make_deferred_yieldable(timing_out_d) |
| 311 | + |
| 312 | + self.fail( |
| 313 | + "We should not make it to this point as the `timing_out_d` should have been cancelled" |
| 314 | + ) |
| 315 | + |
| 316 | + d = defer.ensureDeferred(competing_task()) |
| 317 | + |
| 318 | + # Still in the sentinel context |
| 319 | + self.assertEqual(current_context(), SENTINEL_CONTEXT) |
| 320 | + |
| 321 | + # Pump until we trigger the timeout |
| 322 | + self.reactor.pump( |
| 323 | + # We only need to pump `1.0` (seconds) as we set |
| 324 | + # `timeout_deferred(timeout=1.0)` above |
| 325 | + (1.0,) |
| 326 | + ) |
| 327 | + |
| 328 | + # Still in the sentinel context |
| 329 | + self.assertEqual(current_context(), SENTINEL_CONTEXT) |
| 330 | + |
| 331 | + # We expect a failure due to the timeout |
| 332 | + self.failureResultOf(d, defer.TimeoutError) |
| 333 | + |
| 334 | + # Back to the sentinel context at the end of the day |
| 335 | + self.assertEqual(current_context(), SENTINEL_CONTEXT) |
| 336 | + |
273 | 337 |
|
274 | 338 | class _TestException(Exception): # |
275 | 339 | pass |
|
0 commit comments