Skip to content
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

fix(request-state): remove data race on early state load #179

Merged
merged 6 commits into from
Dec 10, 2024

Conversation

MWGuy
Copy link
Contributor

@MWGuy MWGuy commented Nov 14, 2024

The problem

Sometimes i got condition where client ready to hydate conponents and expects server data loaded to nanostores in context but nanostores not populated with server data yet because document not yet downloaded from server.

1115.mp4

Solution

This patch fixes middleware logic that inserts hydration data into end of head rather than end of body with dynamic scripts imports.

Summary by CodeRabbit

  • New Features

    • State injection point in HTML responses has been updated to occur before the closing </head> tag, enhancing the structure of the response.
  • Bug Fixes

    • Fixed synchronization issues related to state during response streaming, improving user experience.
  • Documentation

    • Added "Caveats" sections to the documentation for request-nanostores and @inox-tools/request-state, highlighting limitations regarding response streaming and state generation. Adjustments made for clarity and coherence.

This patch fixes condition where client already hydated and expects server data from naostores but nanostores not populated with server data yet

Signed-off-by: Максим Тарасов <[email protected]>
Copy link

vercel bot commented Nov 14, 2024

@MWGuy is attempting to deploy a commit to the Fryuni Pro Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

coderabbitai bot commented Nov 14, 2024

Caution

Review failed

The pull request is closed.

Walkthrough

The changes in this pull request modify the onRequest middleware function in the middleware.ts file. The main adjustment is the relocation of the state injection from the closing </body> tag to the closing </head> tag in the HTML response. This includes renaming the variable bodyCloseIndex to headCloseIndex to reflect the new logic. The overall structure and error handling of the middleware remain unchanged, while the transformation stream for state injection is no longer necessary. Additionally, documentation updates were made to include a "Caveats" section in both request-nanostores.mdx and request-state.mdx to address limitations regarding response streaming.

Changes

File Path Change Summary
packages/request-state/src/runtime/middleware.ts Modified onRequest middleware to inject state before </head> instead of </body>. Removed injectState function and introduced headCloseIndex.
docs/src/content/docs/request-nanostores.mdx Added "Caveats" section highlighting that enabling request-nanostores disables response streaming. Minor text adjustments for clarity.
docs/src/content/docs/request-state.mdx Added "Caveats" section noting that enabling request-state disables response streaming. Refined documentation for clarity.
.changeset/neat-panthers-notice.md Updated package version of @inox-tools/request-state and addressed synchronization issues during response streaming.

Poem

In the head where scripts do play,
The state now dances, bright and gay.
No longer tucked where bodies close,
It leaps to life where the headlight glows.
A hop, a skip, a joyful cheer,
Our middleware shines, the change is clear! 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 00086dc and 372d230.

📒 Files selected for processing (4)
  • .changeset/neat-panthers-notice.md (1 hunks)
  • docs/src/content/docs/request-nanostores.mdx (1 hunks)
  • docs/src/content/docs/request-state.mdx (1 hunks)
  • packages/request-state/src/runtime/middleware.ts (1 hunks)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Experiment)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (1)
packages/request-state/src/runtime/middleware.ts (1)

29-37: Consider adding error handling for missing head tag.

While moving the injection point to </head> fixes the race condition, we should handle cases where the head tag isn't found to maintain robustness.

Consider this enhancement:

 const headCloseIndex = chunk.indexOf('</head>');
 if (headCloseIndex > -1) {
   const state = getState();
   if (state) {
     const stateScript = `<script id="it-astro-state" type="application/json+devalue">${state}</script>`;
     chunk = chunk.slice(0, headCloseIndex) + stateScript + chunk.slice(headCloseIndex);
   }
   injected = true;
+} else {
+  // Log warning if head tag not found in first chunk
+  console.warn('Warning: </head> tag not found in chunk, state injection may be delayed');
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7e20109 and 95d6cb8.

📒 Files selected for processing (1)
  • packages/request-state/src/runtime/middleware.ts (1 hunks)
🔇 Additional comments (1)
packages/request-state/src/runtime/middleware.ts (1)

29-30: LGTM! Moving state injection to head addresses the data race.

The change to inject state before </head> instead of </body> is a good solution to prevent the data race condition where client-side hydration occurs before state is available.

Let's verify the HTML structure assumptions:

Also applies to: 35-35

✅ Verification successful

Moving state injection to </head> is safe and well-tested

The implementation and test analysis confirms:

  • All test fixtures have proper HTML structure with single <head> tags
  • E2E tests verify state injection and hydration works correctly
  • View transitions test confirms state persistence across page navigations
  • The middleware correctly handles streaming HTML transformation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify HTML structure assumptions for state injection
# Test: Check if there are any HTML files with multiple </head> tags or missing </head>
# which could affect the state injection logic

# Search for multiple </head> tags in same file
echo "Checking for multiple </head> tags..."
rg -l "</head>.*</head>" -g "*.{html,astro}"

# Search for files with opening <head> but missing closing tag
echo "Checking for unclosed <head> tags..."
rg -l "<head>(?![\s\S]*</head>)" -g "*.{html,astro}"

Length of output: 503


Script:

#!/bin/bash
# Let's try a different approach using simpler patterns and PCRE2 mode
# First, let's find all HTML/Astro files
echo "Finding all HTML/Astro files..."
fd -e html -e astro

# Then check content of these files for HTML structure
echo "Checking HTML structure in template files..."
rg --pcre2 "<head>.*</head>" -l

# Also check for any potential template files that might generate HTML
echo "Checking other potential template files..."
fd -e jsx -e tsx -e vue -e svelte

# Look for head-related patterns in middleware tests
echo "Checking test files for head-related patterns..."
rg "head" -l packages/request-state/src/runtime/middleware.test.ts

Length of output: 5022


Script:

#!/bin/bash
# Let's check the actual test file location and its content
echo "Finding middleware test file..."
fd middleware.test

# Let's examine the actual middleware implementation to understand the HTML handling
echo "Checking middleware implementation..."
cat packages/request-state/src/runtime/middleware.ts

# Let's also check if there are any other tests that might verify HTML handling
echo "Checking for related test files..."
fd -e test.ts -e spec.ts -e test.js -e spec.js -p request-state

# Look for any HTML transformation tests in e2e tests
echo "Checking e2e tests..."
rg -l "head" packages/request-state/e2e

Length of output: 2068


Script:

#!/bin/bash
# Let's examine the e2e tests to understand how HTML transformation is tested
echo "Checking e2e test content..."
cat packages/request-state/e2e/basic.spec.ts
cat packages/request-state/e2e/dev.spec.ts
cat packages/request-state/e2e/view-transitions.spec.ts

# Let's also check the test fixtures to see the HTML structure
echo "Checking test fixture content..."
cat packages/request-state/e2e/fixture/basic/src/pages/index.astro
cat packages/request-state/e2e/fixture/dev/src/pages/index.astro
cat packages/request-state/e2e/fixture/view-transitions/src/pages/index.astro

Length of output: 5735

packages/request-state/src/runtime/middleware.ts Outdated Show resolved Hide resolved
@MWGuy
Copy link
Contributor Author

MWGuy commented Nov 15, 2024

Hoping that @Fryuni merge this PR as fast as possible 🙏

Copy link
Owner

@Fryuni Fryuni left a comment

Choose a reason for hiding this comment

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

Thanks for the PR! Sorry for the delayed response.

It is a great catch and a simple solution, but it requires some polishing to deal with its consequences.

packages/request-state/src/runtime/middleware.ts Outdated Show resolved Hide resolved
Copy link

vercel bot commented Nov 15, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
inox-tools ✅ Ready (Inspect) Visit Preview Dec 10, 2024 1:36am

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (4)
docs/src/content/docs/request-nanostores.mdx (2)

120-122: Improve grammar and clarity in the Caveats section.

The current text has some grammatical issues. Consider this revision:

-Enabling request-nanostores disables [response streaming](https://docs.astro.build/en/recipes/streaming-improve-page-performance/). This behavior caused by [request-state](/request-state) package that prevents race condition where nanostores not initialized before client components starts hydrating.
+Enabling request-nanostores disables [response streaming](https://docs.astro.build/en/recipes/streaming-improve-page-performance/). This behavior is caused by the [request-state](/request-state) package to prevent race conditions where nanostores are not initialized before client components start hydrating.

120-122: Consider expanding the Caveats section with additional details.

The section would benefit from:

  1. Explaining the performance implications of disabling response streaming
  2. Providing examples of scenarios where this limitation might be significant
  3. Suggesting potential workarounds or best practices for performance-critical applications

Would you like me to help draft this additional content?

docs/src/content/docs/request-state.mdx (2)

133-135: Improve grammar in the Caveats section

The technical content accurately describes the streaming limitation. However, the text could be clearer with proper articles.

Apply this diff to improve readability:

-Enabling request-state disables [response streaming](https://docs.astro.build/en/recipes/streaming-improve-page-performance/). The entire application state must be generated before shipping closing head tag to the client. This behavior prevents data race when client components start hydrating before entire document is loaded.
+Enabling request-state disables [response streaming](https://docs.astro.build/en/recipes/streaming-improve-page-performance/). The entire application state must be generated before shipping the closing head tag to the client. This behavior prevents data races when client components start hydrating before the entire document is loaded.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~135-~135: Possible missing article found.
Context: ...state must be generated before shipping closing head tag to the client. This behavior p...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~135-~135: Possible missing article found.
Context: ...lient components start hydrating before entire document is loaded. ## License Astro ...

(AI_HYDRA_LEO_MISSING_THE)


133-136: Consider adding more context about the data race

While the caveat section correctly explains the limitation, it would be helpful to provide more context about the data race scenario that this prevents.

Consider expanding the section with:

 ## Caveats

 Enabling request-state disables [response streaming](https://docs.astro.build/en/recipes/streaming-improve-page-performance/). The entire application state must be generated before shipping the closing head tag to the client. This behavior prevents data races when client components start hydrating before the entire document is loaded.
+
+This limitation is necessary because client-side hydration could begin as soon as the component's script is loaded, potentially before the server state is available in nanostores. By injecting the state in the head section, we ensure that all required data is available before any client-side hydration occurs.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~135-~135: Possible missing article found.
Context: ...state must be generated before shipping closing head tag to the client. This behavior p...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~135-~135: Possible missing article found.
Context: ...lient components start hydrating before entire document is loaded. ## License Astro ...

(AI_HYDRA_LEO_MISSING_THE)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 95d6cb8 and 00086dc.

📒 Files selected for processing (2)
  • docs/src/content/docs/request-nanostores.mdx (1 hunks)
  • docs/src/content/docs/request-state.mdx (1 hunks)
🧰 Additional context used
🪛 LanguageTool
docs/src/content/docs/request-state.mdx

[uncategorized] ~135-~135: Possible missing article found.
Context: ...state must be generated before shipping closing head tag to the client. This behavior p...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~135-~135: Possible missing article found.
Context: ...lient components start hydrating before entire document is loaded. ## License Astro ...

(AI_HYDRA_LEO_MISSING_THE)

@MWGuy MWGuy requested a review from Fryuni November 17, 2024 01:29
@Fryuni
Copy link
Owner

Fryuni commented Dec 10, 2024

Hey @MWGuy, sorry for taking so long to get back to this.

Without having to worry about streaming, the code looks a little different (it runs after the entire response is done instead of as part of the response stream), that was the missing part.

Gonna merge this now! Thanks for the PR and for the patience 😄

@Fryuni Fryuni changed the title fix(request-state): fix data race fix(request-state): remove data race on early state load Dec 10, 2024
@Fryuni Fryuni merged commit 4870d82 into Fryuni:main Dec 10, 2024
14 of 21 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Dec 10, 2024
@MWGuy MWGuy deleted the request-state-fix-data-race branch December 10, 2024 12:41
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.

3 participants