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

Early draft of storing temp sites in OPFS #1838

Closed
wants to merge 88 commits into from
Closed

Conversation

adamziel
Copy link
Collaborator

@adamziel adamziel commented Oct 2, 2024

Motivation for the change, related issues

After #1822, Playground forcibly refreshes all the browser tabs after deploying a new webapp version. This fixes version upgrades, but it also causes data loss in the temporary Playgrounds running in those refreshed tabs. Let's explore storing all that temporary data in OPFS so we can restore it after a page refresh.

deployment_bot added 30 commits October 2, 2024 01:56
@brandonpayton
Copy link
Member

brandonpayton commented Oct 3, 2024

Some things left to do:

  • Stop refreshes with no query params from creating endless, temporarily persisted sites.
  • Figure out how to select existing temp-on-OPFS sites based on app URL. Some options:
    • Map name in query API params to existing temp site
    • Be explicit with a dedicated param like temp-slug that uses slug or temp-site-id that uses the UUID. It is good to distinguish between selection of persisted sites and selection of temporary sites because we likely want to handle site-not-found scenarios differently for each:
      • Persistent sites that are not found should probably be shown a warning.
      • Temporary sites that are not found might be shown a warning but could also be automatically redirected to a fresh temp site.
  • Make new temp sites actually write WP and data to OPFS. This isn't yet happening.
  • Adjust the Save to OPFS code to simply change a site's storage type.

@brandonpayton
Copy link
Member

Added one more item to the above list:

  • Adjust the Save to OPFS code to simply change a site's storage type.

I also took a look at making sure opfs-temporary sites actually had OPFS mounts, but when I did so, WordPress installation failed because the site's directory already existed with a wp-runtime.json file in it. So we might need to adjust something either about timing or with unzipping WP.

@brandonpayton
Copy link
Member

Also changed

  • Make default temp site creation update query params. Otherwise, refreshing with no query params will continue to create new temp sites.

to

  • Stop refreshes with no query params from creating endless, temporarily persisted sites.

@bgrgicak
Copy link
Collaborator

bgrgicak commented Oct 3, 2024

I took some time and added a few things.

There is now a temp-slug that's added to every temp site.
I suggest that we rename it to site-slug later if we don't find a use for having a different name. (There is no use now.)

If a site-slug or temp-slug doesn't exist and the user already has sites, a message will be displayed.
I updated the existing message to include a Start a new Playground button.

Screenshot 2024-10-03 at 10 36 26

@adamziel
Copy link
Collaborator Author

adamziel commented Oct 3, 2024

Oh no! Saving fresh sites to OPFS takes a considerable amount of time, like 12 seconds. Such a long waiting time is quite a bad first user experience. Remembering my explorations around zip streaming and OPFS, here's a breakdown of the things that require significant processing time:

  • Unzipping
  • Moving files through postMessage
  • Writing files using an asynchronous OPFS handle – sync handles are much faster

Perhaps we could start with a simple MEMFS site and then run a non-blocking synchronization in the background while somehow postMessage–transmitting files in bulk and not on at a time.

The risk there would be the user closing their browser tab before the sync is complete – we'd need to add an window.onunload modal and track the synchronization state to show the site is corrupted on the next visit.

@adamziel
Copy link
Collaborator Author

adamziel commented Oct 3, 2024

Aha, but WordPress is not present in OPFS yet during the very first site boot, therefore we don't have to block loading the site for the 12 seconds it takes to synchronize MEMFS to OPFS – there are no files to synchronize yet.

@adamziel
Copy link
Collaborator Author

adamziel commented Oct 3, 2024

I've just pushed archived sites management and the code to write new temp-opfs sites to OPFS.

We'll still need to retain the "in-memory" storage for, e.g., Safari in private mode.

@adamziel
Copy link
Collaborator Author

adamziel commented Oct 3, 2024

The experience I went for is this:

  • Temp sites are archived after a page refresh
  • The currently open temp site isn't getting archived after a page refresh. This is flaky as I might have multiple browser tabs open – we could make it so that requesting a ?temp-slug unarchives a site.
  • Archived sites created < 24h ago are deleted

I'm having second thoughts. I wonder how will that feel for the user – sometimes a site disappears, sometimes it doesn't. Also, we'll eventually need something to prevent conflicting writes to the same OPFS site from multiple Playground instances. Alternatively, we could always run those sites in a SharedWorker and avoid any synchronization. That wouldn't work in Safari today, but maybe that's okay.

@brandonpayton
Copy link
Member

brandonpayton commented Oct 3, 2024

The experience I went for is this:

  • Temp sites are archived after a page refresh
  • The currently open temp site isn't getting archived after a page refresh. This is flaky as I might have multiple browser tabs open – we could make it so that requesting a ?temp-slug unarchives a site.
  • Archived sites created < 24h ago are deleted

I'm having second thoughts. I wonder how will that feel for the user – sometimes a site disappears, sometimes it doesn't.

Would unarchiving on request be sufficient for reducing user confusion if we wait a week before deleting archived sites?

How practical this is may depend on device storage quotas and availability. But if we run into limits, maybe we could clean up archived sites earlier, starting with the once loaded longest ago.

@brandonpayton
Copy link
Member

Perhaps we could start with a simple MEMFS site and then run a non-blocking synchronization in the background while somehow postMessage–transmitting files in bulk and not on at a time.

The risk there would be the user closing their browser tab before the sync is complete – we'd need to add an window.onunload modal and track the synchronization state to show the site is corrupted on the next visit.

@adamziel and I spoke a bit about this, and it might make sense for us to maintain some of this app-specific state separately from the wp-runtime.json metadata which we imagine could be shared along with sites themselves.

We could have a file like <site-dir>/.wp-playground-state.json that has properties like:

  • whenLastLoaded
  • isArchived
  • storage
  • initialPersistenceState: not-started | in-progress | completed - to track the process of transitioning temp sites from MEMFS to OPFS after load and hopefully help us identify and address corruption due to incomplete WP installs. The actual property name could be improved. :)

@brandonpayton
Copy link
Member

There is now a temp-slug that's added to every temp site.
I suggest that we rename it to site-slug later if we don't find a use for having a different name. (There is no use now.)

Thanks for working on these things, @bgrgicak. Agreed about renaming later if there's no benefit.

@brandonpayton
Copy link
Member

brandonpayton commented Oct 3, 2024

Next up:

  • Separating Playground app state into a different file. Maybe <site>/.wp-playground-state.json.
  • Saving a site's MEMFS files to OPFS in the background
  • Handle MEMFS-to-OPFS process being interrupted. This probably means:
    • Not loading a temp site from OPFS unless we've marked that process as complete.
    • Supporting overwriting existing files during the MEMFS-to-OPFS operation
  • Adding versions (or maybe schema declarations?) to these metadata and state files to give us an opportunity to be backwards compatible.

I'm hoping to make good progress on these yet today, but please feel free to take any of these if you agree with the direction when you pick up this PR. 🙇

@brandonpayton
Copy link
Member

We are discussing backing off of this approach as it is making temporary sites less temporary.

Instead, we need to find a way to coordinate app updates with users.

Let's pause this until we decide what to do.

@adamziel
Copy link
Collaborator Author

adamziel commented Oct 7, 2024

Let's discontinue this work:

  • This PR adds a lot of complexity: keeping tabs of stale sites, archiving them, deleting them, having a UI to browse them, dealing with storage limits and non-OPFS browsers, just to name some of it.
  • Playground users actually want temporary sites that are easy to scrap.
  • Even if they didn't, we'd still need to support temporary sites for browsers without OPFS support, e.g. Safari in private mode.
  • Refreshless website deployments – load remote.html using the network-first strategy #1849 solves the problem this PR was meant to solve.

This reveals a larger, strategic point: The Playground webapp may never have advanced site management features such as:

Building these features is definitely possible, but it requires spending significant amount of time and effort from building the platform to building a user-facing product. Let's leave that to app authors.

cc @bgrgicak @brandonpayton

@adamziel adamziel closed this Oct 7, 2024
adamziel added a commit that referenced this pull request Oct 7, 2024
…first strategy (#1849)

## Motivation for the change, related issues

Related to #1821

Changes the webapp upgrade protocol proposed in #1822 to avoid forcibly
refreshing the browser tabs with unsaved changes in them.

## Technical implementation

**Before this PR**, the new service worker would clear the offline
cache, claim all the active clients, and forcibly refresh them to ensure
the latest Playground version is loaded everywhere.

This worked, but every webapp upgrade would destroy any work the user
may have done in their temporary Playground. We've explored [storing
temporary Playgrounds in
OPFS](#1838), but
backtracked because 1) it created an uncanny amount of complexity, and
2) some browsers (e.g. Safari in private mode) don't support OPFS and
must rely on a temporary in-memory site.

**After this PR**, the service worker clears the offline cache, claims
all the active clients, but it doesn't forcibly refresh them. Instead,
it uses the network-first strategy for the `remote.html` route and the
`/` route. All the other files are still loaded using the cache-first
strategy.

Every Playground that's already open, either temporary or stored, will
remain functional. The heavy, asynchronously loaded resources such as
PHP.wasm and WordPress.zip were already processed – there's no user flow
that could lead to `import()`-ing a non-existing `php.js` file.

Every newly opened Playground will be loaded using a freshly downloaded
`remote.html` file containing references to freshly deployed Playground
assets. Thus

## Other changes

This PR inlines the reusable service worker utilities from
`packages/php-wasm/web/src/lib/register-service-worker.ts` into
`@wp-playground` packages. It turns out, they weren't as reusable and
keeping them separate was annoying. I'm now convinced the service worker
bits are application specific and splitting them between multiple
packages just isn't useful.

## Testing instructions

Review the app deployment E2E tests check what we need to check, and
them confirm they are green in the CI.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

3 participants