Skip to content
Merged
1 change: 1 addition & 0 deletions changelog.d/19602.misc.1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update `HomeserverTestCase.pump()` docstring to demystify behavior (Twisted reactor/clock).
1 change: 1 addition & 0 deletions changelog.d/19602.misc.2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Deprecate `HomeserverTestCase.pump()` in favor of more direct `HomeserverTestCase.reactor.advance(...)` usage.
37 changes: 36 additions & 1 deletion tests/unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,8 +697,43 @@ async def run_bg_updates() -> None:

def pump(self, by: float = 0.0) -> None:
"""
Pump the reactor enough that Deferreds will fire.
XXX: Deprecated: This method is deprecated. Use `self.reactor.advance(...)`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

though even if we signpost advance more, maybe deprecating pump is a bit far. Making people write for _ in range(5): self.advance(0) in places may not necessarily make things more readable. Though in that case we could have a more explicit alternative advance_many where the 100 is replaced with an actual number.
Hard to say.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suspicion is that most pump() usage is bogus. And in the cases where it does something, we really just need to advance time for a scheduled thing to happen.

I'm ok with moving forward with deprecating as it encourages bad behavior.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm coming around to this, yes let's deprecate it. Advancing 100 times internally 'just because' feels like bad practice to me.

I'd rather have a wrapper around advance

def advance(by = 0.0, require_calls = True)

or some such.

require_calls (true by default) would assert that there is actually something to progress. I am not sure whether to define that to mean 'there is something on the queue' or (stronger) 'there is something on the queue that actually gets executed by this advance call'.

But this is derailing this PR, so let's leave it for later (maybe I have a crack at this later today for the curiosity)

directly instead.

Pump the reactor enough that `clock.call_later` scheduled callbacks will fire.

To demystify this function, it simply advances time by the number of seconds
specified (defaults to `0`, we also multiply by 100, so `pump(1)` is 100 seconds
Comment thread
MadLittleMods marked this conversation as resolved.
in 1 second steps/increments) whilst calling any pending callbacks, allowing any
queued/pending tasks to run because enough time has passed.

So for example, if you have some Synapse code that does
`clock.call_later(Duration(seconds=2), callback)`, then calling
`self.pump(by=0.02)` will advance time by 2 seconds, which is enough for that
callback to be ready to run now. Same for `clock.sleep(...)` ,
`clock.looping_call(...)`, and whatever other clock utilities that use
`clock.call_later` under the hood for scheduling tasks. Trying to use
`pump(by=...)` with exact math to meet a specific deadline feels pretty dirty
though which is why we recommend using `self.reactor.advance(...)` directly
nowadays.

We don't have any exact historical context for why `pump()` was introduced into
the codebase beyond the code itself. We assume that we multiply by 100 so that
when you use the clock to schedule something that schedules more things, it
tries to run the whole chain to completion.

XXX: If you're having to call this function, please call out in comments, which
scheduled thing you're aiming to trigger. Please also check whether the
`pump(...)` is even necessary as it was often misused.

Args:
by: The time increment in seconds to advance time by. We will advance time
in 100 steps, each step by this value.
"""
# We multiply by 100, so `pump(1)` actually advances time by 100 seconds in 1
# second steps/increments. We assume this was done so that when you use the
# clock to schedule something that schedules more things, it tries to run the
# whole chain to completion.
self.reactor.pump([by] * 100)

def get_success(self, d: Awaitable[TV], by: float = 0.0) -> TV:
Expand Down
Loading