Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions content/docs/(overview)/devices.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ The hydration **empties the copy first** if the instance's event log now ends be

**For any other reason, the copy refuses reads and writes until a hydration, which empties it first.** Examples are another instance answering at the same address, or a credential whose reach changed.

**An app that refills a stale copy by itself can ask for a fill only in place.** Such a hydration never empties the copy. Where the fill can't be in place, it's refused `copy_expired` before it changes anything, and the copy, its queued writes and its files stay as they were. The reason says why:

| Reason | Why the fill can't be in place |
| --- | --- |
| `instance_changed` | Another instance now answers at the copy's address. |
| `reach_changed` | What the credential may read has changed. |
| `slice_changed` | The types, tier or edge types asked for aren't the ones the copy holds. |
| `head_past_log` | The instance's event log ends before the point the copy had reached, as after a restore from a backup. |
| `not_stale` | The copy isn't waiting to be filled again, for example because it's whole. |

The app can then ask the person whether to build the copy again, which empties it, or keep it as it is. The CLI's `marfa device hydrate --in-place-only` does the same.

**Writes queued against one instance aren't sent to another.** When a hydration builds the copy from another instance than the one it was built from, each write still waiting is `blocked` with the reason `instance_changed`, because the instance now at the address may never have held the items the write was made against. A release sends it to that instance; a withdrawal takes it out of the queue.

A copy made by an earlier build of the core can have a different shape. The device **refuses to open it** with `wrong_schema`, deletes nothing, and says how many writes the file holds that the server hasn't taken, because only the build that made it can send them. If some are of a kind the current build no longer sends, it names the kind. [Troubleshooting](/run/troubleshooting#staying-in-step) says what to do. An app built on the core's Node or Swift binding can replace a refused copy that holds nothing unsent with an empty one; the files the copy downloaded stay. A [folder](/run/sync-a-folder#move-to-a-newer-cli) can replace its own copy itself.

**An app can remove its working copy, with the unsent writes the person agreed to lose.** An app passes the number of writes the server hasn't taken that it showed the person, through the core's Node binding (`MarfaCore.removeStore`). The device removes the copy only when the copy holds exactly that number, whatever build made it: the file, its logs, the files it downloaded and its lock, and nothing else beside them. A symbolic link at one of those names is removed, never what it leads to. If the copy holds another number, or one the device can't read, the device refuses with `unsent_differs`, says the copy's own number, or that it can't read one, and leaves the copy, its logs, its downloaded files and its lock as they were. It refuses with `reading_handle` while anything else writes the copy, the app's own open copy included, so the app closes its copy first. A helper process that only reads the copy doesn't stop the removal, and keeps showing the old copy until it reopens. A folder's copy goes only with the folder, so the device refuses it with `invalid`. A path with no copy holds no unsent write, so removing it again finishes a removal that stopped part way.
Expand Down Expand Up @@ -72,7 +86,7 @@ Each write the device settles gets one verdict:

Only `refused`, `blocked` and `dead` need an **app's attention**. A write with no answer yet has no verdict.

A `blocked` write carries one of six reasons. Five hold it until something changes:
A `blocked` write carries one of seven reasons. Six hold it until something changes:

| Reason | What holds the write | What clears it |
| --- | --- | --- |
Expand All @@ -81,10 +95,11 @@ A `blocked` write carries one of six reasons. Five hold it until something chang
| `ancestor_unavailable` | The instance no longer holds the version the write was based on. | A withdrawal, or a release once the credential has come to read that version. Sent again before then, the write is refused the same way. |
| `conflict_unresolved` | A newer change collided with the write, and the instance didn't settle the two. | A withdrawal. Sent again, the write is refused the same way. |
| `failing_alone` | The write keeps failing as if the server were down, while the writes behind it go through. | A release or a withdrawal. |
| `instance_changed` | The write was queued while the copy came from another instance than the one it's now built from. | A release, which sends it to the instance now at the address, or a withdrawal. |

A **release** sends the write again in the next drain, under a fresh idempotency key. A **withdrawal** takes the write out of the queue and puts the copy back to what the instance holds. Withdrawing a create, or a write blocked `failing_alone`, also refuses the writes that wait on it. The Swift SDK's [queue](/swift-sdk#send-writes-and-read-verdicts) and the CLI's `marfa device release` and `marfa device withdraw` do both.

The sixth reason, `awaiting_dependency`, marks a write waiting behind another that has no answer yet, such as an edit of a note whose create hasn't been answered. Nothing needs to be done: the Swift SDK shows it as waiting, and the answer to the write it waits for releases it.
The seventh reason, `awaiting_dependency`, marks a write waiting behind another that has no answer yet, such as an edit of a note whose create hasn't been answered. Nothing needs to be done: the Swift SDK shows it as waiting, and the answer to the write it waits for releases it.

## Folders

Expand Down
11 changes: 10 additions & 1 deletion content/docs/run/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -256,14 +256,23 @@ Build the working copy again. [Devices](/devices) explains when a copy has to st
</Accordion>
<Accordion title="this working copy can no longer be kept current: …; rebuild the copy from the server">

This is how the CLI's device commands report `catchup_too_old` and `reach_changed`, with the code `copy_expired` and exit code `4`. Writes waiting in the queue are kept.
This is how the CLI's device commands report `catchup_too_old`, `reach_changed` and `instance_changed`, with the code `copy_expired` and exit code `4`. Writes waiting in the queue are kept.

Run `marfa device hydrate` again. After `catchup_too_old`, `marfa device status` reports the copy's hydration as `stale`, and the copy keeps answering reads, marked `stale`, while the hydrate fills it in place. Until the hydrate finishes, `marfa device drain` and `marfa device catch-up` keep answering `copy_expired` with `catchup_too_old`. If the credential no longer reads one of the types you held, leave that type out of `--types`, or the hydrate is refused with `type_not_permitted`.

An app built on the Swift SDK that holds a `changes()` stream refills a stale copy by itself. [Refill a stale copy](/swift-sdk#refill-a-stale-copy) says how.

After `instance_changed`, the hydrate builds the copy from the instance now at the address and holds each write still waiting as `blocked` with the reason `instance_changed`, so none reaches that instance by itself. Check `marfa device queue`, then send the ones you want there with `marfa device release --reason instance_changed`, or take one out with `marfa device withdraw <id>`.

`marfa device hydrate --in-place-only` gives the same error, with the reason `instance_changed`, `reach_changed`, `slice_changed` or `head_past_log`, where the copy can't be filled again in place. It changes nothing. Run `marfa device hydrate` without the option to build the copy again.

For a folder, run `marfa folders push` again instead: it builds the copy again itself. [Sync a folder](/run/sync-a-folder#replace-a-folders-key) covers a key you replaced.

</Accordion>
<Accordion title="this working copy is not waiting to be filled again in place (not_stale)">

`marfa device hydrate --in-place-only` fills only a copy whose cursor the instance's event log let go, and this copy is whole, was never built, or has to be built again. Nothing was changed. Run `marfa device catch-up` to bring a whole copy up to date, or `marfa device hydrate` without the option to build it again.

</Accordion>
<Accordion title="wrong_server">

Expand Down
Loading