feat(cm-playground): load a machine from a snapshot tarball - #185
Open
tuler wants to merge 4 commits into
Open
Conversation
A machine now comes from one of two places: built from the form, or loaded whole from a stored one someone else built. A snapshot is the directory `cartesi-machine --store=<dir>` writes, tarred up — it arrives here as bytes like an image does, is unpacked into the emulator's own filesystem, and is what `load()` is pointed at, resuming wherever it was stored instead of booting from scratch. Meeting an archive halfway is most of the work. It is gzipped as often as not, which `DecompressionStream` handles with no library; it may wrap the machine in a directory of its own, which is what `tar -cf snap.tar snap` produces, so the machine is found by looking for the `config.json` every stored machine has; and it is as big as the machine inside it, so the unpacked copy is dropped the moment `load()` — which defaults to no sharing, and has read the whole thing into memory — is done with it. Snapshots share the image library's IndexedDB store under a `snapshot` kind, so the same fetch-a-URL and add-a-file paths carry them, GitHub releases through this app's proxy included. The form's source switch decides which panels the page shows: a stored machine settled its own drives, command line and registers when it was stored, and all this page still has a say in is the console it is driven through and how long it runs for. The run statistics follow: a loaded machine starts at the cycle it was stored at, so the speed and the cycle limit both count from where this run began rather than from zero. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LYAATiMPkcpeS6AoWSgUvg
The other direction of loading one. "Store", in the run bar, catches the machine as it stands: it is written out, tarred, gzipped and filed in the snapshot library, where it is a snapshot like any other — loadable again, and downloadable as a `.tar.gz` that `tar -xzf` opens. Compressing is not a nicety here. A machine is mostly memory nothing has written to, so its tar is mostly zeroes: a 64 MiB machine goes from 126 MB to 309 KB, which is the difference between a snapshot worth keeping in a browser and one that is not. The sharing mode is not a choice. A machine created in memory — the only kind this page has — can only be written out with `All`; the other two ask the emulator to roll ranges back to a backing store that was never there. What it does not mean is that the machine is then tied to the files, so the directory is tarred and removed straight away, giving a machine's worth of memory back while the run carries on untouched. Storing happens beside the run rather than instead of it: the message arrives between slices of the drive loop, which picks up where it left off. It has its own small state in the run bar, since a store that fails is not the run failing. Track the app with changesets, so all of this lands in a changelog: private packages are now versioned (never published), which gives @cartesi/cm-playground a CHANGELOG.md and bumps the @cartesi/machine version it pins along with that package's releases. @cartesi/docs is unaffected — it depends on the workspace packages only in devDependencies, which changesets does not release a package for. `baseBranch` was still "main", which no longer exists here and left `changeset status` unable to run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LYAATiMPkcpeS6AoWSgUvg
`main` is what the workflows treat as the trunk — ci, docs and release all run on `main`, `release/*` and `prerelease/*` — so it is the branch feature branches are cut from and merged back into, whatever branch happens to be the default while a prerelease line is open. Pointing it at the prerelease branch was fixing the wrong thing. `baseBranch` reaches exactly two commands, `changeset status` and `changeset add`, which diff against it to work out which packages a branch touched; `version`, `publish` and `pre` never read it, in pre mode or out of it. So a baseBranch naming a branch this clone does not have breaks those two locally and nothing in the release, and it comes right on its own once `main` is back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LYAATiMPkcpeS6AoWSgUvg
🦋 Changeset detectedLatest commit: ce79b28 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
A stored machine is the worst case for tar. A RAM image and drives that are
mostly untouched are files full of holes, and libarchive — the `tar` on macOS
— records those sparsely without being asked, in GNU's 1.0 format, where the
name in the entry header is a decoy (`GNUSparseFile.0/ram.bin`) and the real
one is in a pax record. The reader wrote the decoy and never wrote the file,
so `load()` failed later with
unable to open backing file '…/0000000080000000-8000000.bin':
No such file or directory when initializing RAM
GNU tar's own --sparse broke it differently: the continuation blocks holding
the sparse map were read as the next header, which failed the checksum.
All three sparse dialects are expanded now — GNU 1.0 and 0.1 through the pax
records, old-GNU `S` through the header and its continuation blocks — and pax
extended headers are parsed for `path` and the `GNU.sparse.*` keys, so long
and re-stated names arrive intact. GNU long-name entries are honoured too.
Two silences go with them, both of which turned a broken archive into a
baffling error much later rather than a clear one at the time:
- an archive that stops early is refused, instead of unpacking as far as it
got and reporting a whole machine
- an entry the reader cannot represent is refused, instead of being skipped
Entries reaching outside the snapshot directory are refused outright, which
matters now that snapshots come from wherever a URL points.
The tests build each dialect by hand and unpack it into a Map, so they need
neither the emulator nor a tar on the host; ten of the twelve fail against
the reader they replace.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYAATiMPkcpeS6AoWSgUvg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A machine now comes from one of two places: built from the form, or loaded
whole from a stored one someone else built. A snapshot is the directory
cartesi-machine --store=<dir>writes, tarred up — it arrives here as byteslike an image does, is unpacked into the emulator's own filesystem, and is
what
load()is pointed at, resuming wherever it was stored instead ofbooting from scratch.
Meeting an archive halfway is most of the work. It is gzipped as often as
not, which
DecompressionStreamhandles with no library; it may wrap themachine in a directory of its own, which is what
tar -cf snap.tar snapproduces, so the machine is found by looking for the
config.jsoneverystored machine has; and it is as big as the machine inside it, so the
unpacked copy is dropped the moment
load()— which defaults to no sharing,and has read the whole thing into memory — is done with it.
Snapshots share the image library's IndexedDB store under a
snapshotkind,so the same fetch-a-URL and add-a-file paths carry them, GitHub releases
through this app's proxy included. The form's source switch decides which
panels the page shows: a stored machine settled its own drives, command line
and registers when it was stored, and all this page still has a say in is
the console it is driven through and how long it runs for.
The run statistics follow: a loaded machine starts at the cycle it was
stored at, so the speed and the cycle limit both count from where this run
began rather than from zero.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01LYAATiMPkcpeS6AoWSgUvg