Skip to content

Demystify and deprecate HomeserverTestCase.pump() (Twisted reactor/clock)#19602

Merged
MadLittleMods merged 12 commits intodevelopfrom
madlittlemods/demystify-pump
Mar 25, 2026
Merged

Demystify and deprecate HomeserverTestCase.pump() (Twisted reactor/clock)#19602
MadLittleMods merged 12 commits intodevelopfrom
madlittlemods/demystify-pump

Conversation

@MadLittleMods
Copy link
Copy Markdown
Contributor

@MadLittleMods MadLittleMods commented Mar 24, 2026

Demystify and deprecate HomeserverTestCase.pump() (Twisted reactor/clock)

Spawning from #18416 (comment)

Pull Request Checklist

  • Pull request is based on the develop branch
  • Pull request includes a changelog file. The entry should:
    • Be a short description of your change which makes sense to users. "Fixed a bug that prevented receiving messages from other servers." instead of "Moved X method from EventStore to EventWorkerStore.".
    • Use markdown where necessary, mostly for code blocks.
    • End with either a period (.) or an exclamation mark (!).
    • Start with a capital letter.
    • Feel free to credit yourself, by adding a sentence "Contributed by @github_username." or "Contributed by [Your Name]." to the end of the entry.
  • Code style is correct (run the linters)

Comment on lines +703 to +704
specified (defaults to `0`, for some reason we also multiply by 100, so
`pump(1)` is 100 seconds in 1 second increments) and run whatever tasks are
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.

for some reason we also multiply by 100, so pump(1) is 100 seconds in 1 second increments

It looks like pump(...) was first introduced as self.reactor.pump([0.0] * 100) (matrix-org/synapse#3725) without explanation. I can only assume this was done so that when you create a deferred which creates more deferreds, it tries to run the whole chain to completion.

Then was updated to self.reactor.pump([by] * 100) (matrix-org/synapse#3740) without explanation. I assume some scheduled calls were in the mix and this was the patch.

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.

Being enough to run the full chain has always been my understanding (or should I say assumption)

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.

Added this assumed context to the docstring.

@MadLittleMods MadLittleMods changed the title Demystify HomeserverTestCase.pump() Demystify HomeserverTestCase.pump() (Twisted reactor/clock) Mar 24, 2026
Copy link
Copy Markdown
Contributor

@reivilibre reivilibre left a comment

Choose a reason for hiding this comment

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

(side notes)


So for example, if you have some Synapse code that does
`clock.call_later(Duration(seconds=2), callback)`, then calling
`self.pump(0.02)` will advance time by 2 seconds, which is enough for that
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.

true, but relying on the multiplication factor being 100 and writing this to meet a specific deadline feels pretty dirty.
(Doesn't mean we don't do it)

I'm pretty sure there is also .advance somewhere that does a one-off advance with the given duration, that feels a little more sensible and maybe using pump everywhere is an antipattern

Copy link
Copy Markdown
Contributor Author

@MadLittleMods MadLittleMods Mar 24, 2026

Choose a reason for hiding this comment

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

👍 on using self.reactor.advance(...) directly.

I can call out this function as deprecated and point out the alternative. Updated ✅

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.

Now wondering if we ought to add a self.advance so it's as easy/obvious as self.pump. I think the advance name also makes it sound less magical, but could be imaginary

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.

I could see as it as being useful to add our own docstring context to similarly demystify but we already self.reactor.advance(...) in many places and I'd rather not create another abstraction at this point.

Comment on lines +703 to +704
specified (defaults to `0`, for some reason we also multiply by 100, so
`pump(1)` is 100 seconds in 1 second increments) and run whatever tasks are
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.

Being enough to run the full chain has always been my understanding (or should I say assumption)

@MadLittleMods MadLittleMods changed the title Demystify HomeserverTestCase.pump() (Twisted reactor/clock) Demystify and deprecate HomeserverTestCase.pump() (Twisted reactor/clock) Mar 24, 2026
@MadLittleMods MadLittleMods marked this pull request as ready for review March 24, 2026 18:19
@MadLittleMods MadLittleMods requested a review from a team as a code owner March 24, 2026 18:19
Copy link
Copy Markdown
Contributor

@reivilibre reivilibre left a comment

Choose a reason for hiding this comment

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

honestly anything you write is going to be better than what was there, just you opening this PR is giving me time to reflect deeper on this, which is not a bad thing :)

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)

Comment on lines +703 to +705
Pump the reactor enough that scheduled Deferreds will fire.

To demystify this function, it simply advances time by the number of seconds
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.

Suggested change
Pump the reactor enough that scheduled Deferreds will fire.
To demystify this function, it simply advances time by the number of seconds
Advances the test reactor's time by the number of seconds

Just thinking defining 'pump' as 'Pump' is not helping anyone, let's just skip to the chase and tell the reader what this does.

Copy link
Copy Markdown
Contributor Author

@MadLittleMods MadLittleMods Mar 24, 2026

Choose a reason for hiding this comment

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

"Pump" is fine but it just wasn't clear "what happens"

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.

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)

@MadLittleMods MadLittleMods merged commit f2b325f into develop Mar 25, 2026
40 checks passed
@MadLittleMods MadLittleMods deleted the madlittlemods/demystify-pump branch March 25, 2026 20:33
@MadLittleMods
Copy link
Copy Markdown
Contributor Author

Thanks for the review @reivilibre 🦜

Comment is even more clear now 🧊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants