-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor FXIOS-11147 [Tab Optomization Phase 1] Additional Tab Manager cleanup #24560
base: main
Are you sure you want to change the base?
Conversation
Task { | ||
if let tab = tabManager[webView] { | ||
// Need to wait here in case we're waiting for a pending `window.open()`. | ||
try await Task.sleep(nanoseconds: NSEC_PER_MSEC * 100) | ||
await tabManager.removeTab(tab.tabUUID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was not able to test this because I am not sure how to get this function to fire https://mozilla.slack.com/archives/C05C9RET70F/p1738709743883439
nit; PR title should follow guidelines 😄 |
This pull request has conflicts when rebasing. Could you fix it @Cramsden? 🙏 |
removeTab(selectedTab) | ||
Task { | ||
await removeTab(selectedTab.tabUUID) | ||
await tabSessionStore.deleteUnusedTabSessionData(keeping: []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Cramsden Doesn't this create a potential race condition? removeTab
is @MainActor
(I believe), so it will run on the main thread (which is good since tabs
is not thread-safe), but the Task
can still be suspended and will allow other code to potentially execute on the MT before removeTab
actually runs (at an arbitrary point in the future). LMK if I may be misunderstanding though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattreaganmozilla it is very possible. Help me understand what the race condition you would expect would be? Like you go to clear your tabs history and the tab is not removed immediately? The other implementation of this same function dispatches the call to the main thread asynchronously as well so it might also not execute right away?
My hope here with this work was to at least minimize the places where we are calling async/await and gcd. Clearly I didn't actually accomplish this because the call site of clearAllTabsHistory
also has a DispatchQueue.main.async
.
My naive strategy has been if we are already using async/await to try to bubble it up as far as possible so that we can insure an isolated context or at least make the places where we are wrapping work in tasks as visible as possible. So maybe here clearAllTabsHistory
should also be async as well? Or none of it should be async. Who even knows. I surely don't
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Help me understand what the race condition you would expect would be? Like you go to clear your tabs history and the tab is not removed immediately?
Quick disclaimer that I am not a Swift concurrency expert so I could be totally mistaken on some of this. 😅 But AFAIU the await
creates a suspension point that Swift can suspend as long as it thinks it needs to. This is an extreme/unlikely example, but for the sake of illustration I believe it's theoretically possible that someone could delete a tab, and then quickly hit a command key on iPad to undo the delete, and the actual deletion Task
is performed after the undo code runs. At least, the fundamental problem is similar, which is that we could potentially have code running before this delete finishes its work that we weren't expecting.
The other implementation of this same function dispatches the call to the main thread asynchronously as well so it might also not execute right away?
If the former code is DispatchQueue.main.async
that is possibly also problematic, though maybe slightly less dangerous since it's basically guaranteed to be enqueued immediately, and the main queue is serial. I don't think an await
suspension point is exactly the same; the Task
is enqueued/scheduled but I don't believe there's any guarantee of when it runs in relation to other code on the main thread.
My hope here with this work was to at least minimize the places where we are calling async/await and gcd. Clearly I didn't actually accomplish this because the call site of clearAllTabsHistory also has a DispatchQueue.main.async. My naive strategy has been if we are already using async/await to try to bubble it up as far as possible so that we can insure an isolated context or at least make the places where we are wrapping work in tasks as visible as possible. So maybe here clearAllTabsHistory should also be async as well? Or none of it should be async. Who even knows. I surely don't
Yeah this is all fantastic and I'm glad we're digging more into this. 👍 I am a little concerned that the way we're using Tasks is potentially unsafe and might be contributing to weird/rare edge case bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes look good to me. Unfortunately not sure about the concurrency changes but maybe we can mitigate risk by getting it into a beta early and noting it to QA?
📜 Tickets
Jira ticket
Github issue
💡 Description
I set out with the intention of simplifying the
TabManager
protocol by removing duplicate functions. I did not accomplish that goal at all. I did:📝 Checklist
You have to check all boxes before merging
@Mergifyio backport release/v120
)