Skip to content

fix(revisions): queue refresh when log-batching stream is in flight#616

Closed
cpennington wants to merge 1 commit intoidursun:mainfrom
cpennington:cale.pennington/fix-revisions-queue-refresh-when
Closed

fix(revisions): queue refresh when log-batching stream is in flight#616
cpennington wants to merge 1 commit intoidursun:mainfrom
cpennington:cale.pennington/fix-revisions-queue-refresh-when

Conversation

@cpennington
Copy link
Copy Markdown

I've been using jjui in a large monorepo (with a relatively high number of git branches, and a lot of commits in the history), and had been getting hangs in the UI when I opened the repo or the bookmarks tab. After doing some debugging, the conclusion that the (relatively) expensive stream of commits from jj log was being interrupted every time switched away from and back to the tab, which made an already slow operation even slower. This change debounces the refreshes so that they don't get interrupted.

When a streaming refresh is already running and another refresh is
triggered, queue it as a pending refresh to be fired once the stream
completes, rather than dropping it. Also refactor streamer close logic
to always nil the streamer field and move isLoading=false after the
close block.
Comment on lines +394 to +397
if streamDone && m.pendingRefresh {
m.pendingRefresh = false
cmds = append(cmds, common.RefreshAndKeepSelections)
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This is not debouncing but queueing the next refresh.

I don't see how this is helping with your situation. If a refresh is going on (say it takes 5 seconds each) and if you focus back 3 seconds later, instead of cancelling the ongoing one and saving 2 seconds, now you wait total of 10 seconds. Current implementation would settle in 8 seconds.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The debouncing is in the change to the refresh function. However, if we only included that change, then we'd lose out on trailing updates. That said, maybe I've misunderstood the tea model, and this will still block the UI updates, as you say.

return nil
case tea.FocusMsg:
return common.RefreshAndKeepSelections
return func() tea.Msg { return common.AutoRefreshMsg{} }
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This change doesn't make sense to me. Why using auto refresh message here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The goal of this change was to take advantage of the AutoRefreshMsg behavior in revisions.go that only triggers a RefreshAndKeepSelections if the oplog has changed since the last update. Perhaps it would be better to put that check directly into the RefreshMsg handler instead?

Comment on lines +358 to 370
// Have enough rows — close the stream so the jj process doesn't linger
if m.streamer != nil {
m.streamer.Close()
m.streamer = nil
}
streamDone = true
} else {
if m.streamer != nil {
m.streamer.Close()
m.streamer = nil
}
streamDone = true
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

both of the branches are doing the same thing, so else branch is redundant here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🤦 This PR in general was an off-cycle investigation w/ claude, and I clearly didn't look at the changes closely enough before submitting the PR.

@idursun
Copy link
Copy Markdown
Owner

idursun commented Mar 24, 2026

Thanks but I don't think this PR is solving the issue you describe in your description. Debouncing simply means waiting x amount of time before starting the work, which is not happening here. This change is queueing refresh works but that wouldn't help with you issue.

I would like to know how long jj log -n 50 takes on this repository first. jjui doesn't fetch more than 50 revisions when refreshed, which is configurable, you can try reducing that but usually snapshotting takes the most of the time on a refresh.

Copy link
Copy Markdown
Author

@cpennington cpennington left a comment

Choose a reason for hiding this comment

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

Anecdotally, this change has improved jjui responsiveness for me. I don't have hard metrics, though (I had added some debug logging to capture traces when it was behaving badly, but I don't know if I still have them). If it would be helpful, I can try to capture some traces with timing again, to help underscore what the problem I was seeing was.

return nil
case tea.FocusMsg:
return common.RefreshAndKeepSelections
return func() tea.Msg { return common.AutoRefreshMsg{} }
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The goal of this change was to take advantage of the AutoRefreshMsg behavior in revisions.go that only triggers a RefreshAndKeepSelections if the oplog has changed since the last update. Perhaps it would be better to put that check directly into the RefreshMsg handler instead?

Comment on lines +358 to 370
// Have enough rows — close the stream so the jj process doesn't linger
if m.streamer != nil {
m.streamer.Close()
m.streamer = nil
}
streamDone = true
} else {
if m.streamer != nil {
m.streamer.Close()
m.streamer = nil
}
streamDone = true
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🤦 This PR in general was an off-cycle investigation w/ claude, and I clearly didn't look at the changes closely enough before submitting the PR.

Comment on lines +394 to +397
if streamDone && m.pendingRefresh {
m.pendingRefresh = false
cmds = append(cmds, common.RefreshAndKeepSelections)
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The debouncing is in the change to the refresh function. However, if we only included that change, then we'd lose out on trailing updates. That said, maybe I've misunderstood the tea model, and this will still block the UI updates, as you say.

@idursun
Copy link
Copy Markdown
Owner

idursun commented Mar 25, 2026

Hey, no worries.

If it would be helpful, I can try to capture some traces with timing again, to help underscore what the problem I was seeing was.

I would like to see how long jj log -n 50 takes. Also, would like to see if jj log -n 25 is any faster. (doubt that it will be) Assuming it is, then you can reduce the batch size for this specific repo to improve the reload speed.

Alternatively, we can introduce a new configuration option to disable focus reporting (it is the mechanism that auto refresh when tab is gains focus) so that number of refreshes are reduced and becomes predictable.

Third option is to change the default revset for this repo so that instead of loading everything, it can load only the main and your changes if that fits your workflow.

Per repo config is implement but hasn't been released yet. Given that you can already compile the project, in theory, you can fine tune jjui for this specific large repository.

I also suggest that you create an issue with the actual problem and we can talk about possible solutions.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants