-
Notifications
You must be signed in to change notification settings - Fork 435
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
Bug: Turbo.visit Render sequence race condition for Cache and Request #447
Comments
FYI, after additional troubleshooting I found the following sequence of events: Events after POST, PUT, DELETE 302 Redirect
Events after GET 302 Redirect
If I disable complete() {
if (this.state == VisitState.started) {
this.recordTimingMetric(TimingMetric.visitEnd)
this.state = VisitState.completed
this.adapter.visitCompleted(this)
this.delegate.visitCompleted(this)
- this.followRedirect()
}
} Related to #328. Maybe @jaysson can take a look? FYI, this may be also related to #428 |
We are bumping into this issue multiple places in our Rails application. So, we come up with an ugly workaround to force Turbo to reload the page when a redirect after GET.
# application_controller.rb
class ApplicationController < ActionController::Base
+ private
+
+ def redirect_to(*)
+ session[:redirect_from_get] = request.request_id if request.get?
+ super
+ end
end <!-- application.html.erb -->
<!DOCTYPE html>
<html>
<head>
<title>My Title</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
+ <%= %(<style data-turbo-track="reload" data-turbo-cache-key="#{session.delete(:redirect_from_get)}"></style>) if session[:redirect_from_get] %>
</head>
<body>
<%= yield %>
</body>
</html> Note: this workaround causes additional requests: Normally you'd see
With workaround you see:
|
My PR relied on a then valid behaviour where Ref: Line 272 in 6b6bdb2
|
I've narrowed down this bug to a race condition BrowserAdapter#visitStarted Race ConditionThe BrowserAdapter has the following: export class BrowserAdapter implements Adapter {
//...
visitStarted(visit) {
visit.issueRequest() //=> Async send request
visit.changeHistory()
visit.goToSamePageAnchor()
visit.loadCachedSnapshot() //=> render cached snapshot while waiting for async request
} Scenario 1: Works as expectedVisit cached page with response from server Turbo.visit('page1.html'); // renders cache then renders response Scenario 2: Does not work as expectedVisit cached page with local copy of response Turbo.visit('page1.html', { response }); // renders response then renders cache In this scenario, since we already have the response, there's no wait time and export class BrowserAdapter implements Adapter {
//...
visitStarted(visit) {
- visit.issueRequest()
+ window.setTimeout(() => visit.issueRequest(), 1)
visit.changeHistory()
visit.goToSamePageAnchor()
visit.loadCachedSnapshot()
} Turbo.visit('page1.html', { response }); // renders cache then renders response I'm not suggesting this as a solution, but the problem goes away with this change. Solution 1: BrowserAdapter#visitStartedChange the order of calls in export class BrowserAdapter implements Adapter {
//...
visitStarted(visit: Visit) {
+ visit.loadCachedSnapshot()
visit.issueRequest()
visit.changeHistory()
visit.goToSamePageAnchor()
- visit.loadCachedSnapshot()
} Thoughts:
Solution 2: Visit#loadCachedSnapshotCheck if export class Visit implements FetchRequestDelegate {
//...
loadCachedSnapshot() {
const snapshot = this.getCachedSnapshot()
- if (snapshot) {
+ if (snapshot && !this.hasPreloadedResponse()) {
const isPreview = this.shouldIssueRequest()
this.render(async () => {
this.cacheSnapshot()
if (this.isSamePage) {
this.adapter.visitRendered(this)
} else {
if (this.view.renderPromise) await this.view.renderPromise
await this.view.renderPage(snapshot, isPreview)
this.adapter.visitRendered(this)
if (!isPreview) {
this.complete()
}
}
})
}
} Thoughts:
Unit TestI'm having issues running the Turbo tests suite in general. This makes it difficult to create a test to make sure we detect if the cache is rendered before the request. Most of my tests pass, but a handful fail:
@seanpdoyle - Which of the solutions above do you prefer? Any tips on how to fix my unit tests so I can create a PR? (Unless you want to create the test). |
@jayohms could this be related to https://github.com/hotwired/turbo/pull/328/files#diff-78d8451f964182fd51330bac500ba6e71234f81aad9e8a57e682e723d0f517b3? |
@seanpdoyle - read my last comment and you'll see it's not directly caused by the Turbo redirect code, but rather a Turbo.visit('page1.html', { response }); // renders response then renders cache |
@tleish I tend to run the test suite with one browser at a time by passing the |
@seanpdoyle -- Yes. It looks like some of my firefox tests fail on the latest main branch (something in my environment), but I can at least run all chrome tests locally and once they pass have the CI test both browsers. Thanks! |
|
Now able to reproduce it in a test, will create a PR with a failing test today |
Client-side hotfix based on the above pull request: // FIXME: Hotfix for <https://github.com/hotwired/turbo/issues/447>
Turbo.session.adapter.__proto__.visitStarted = function(visit) {
visit.loadCachedSnapshot()
visit.issueRequest()
visit.changeHistory()
visit.goToSamePageAnchor()
} |
I came across what I believe to be a bug.
Fetch requests show updated content in network request.
A few additional observations:
Adding to the landing page page temporarily resolves the issue, but unless I'm misunderstanding turbo caching, adding to every page with a redirect is not permanent solution.
Small demo app to reproduce the error:
GitHub - tleish/turbo-cache-test
Bug also reported in this MR (#445 (comment))
The text was updated successfully, but these errors were encountered: