Rho.Dyn.Store (rho v0.1.0)

Copy Markdown View Source

The Dyn layer on disk, under one root dir (<data dir>/dyn by default, Rho.Config.data_dir/0):

gens/<n>/src/**/*.ex     the generation's full source set, as the agent
                         wrote it (logical `Dyn.*` names)
gens/<n>/ebin/*.beam     the compiled, selftested binaries
                         (versioned `Dyn.G<n>.*` names)
gens/<n>/manifest.json   a `Rho.Dyn.Generation`
gens/<n>/diff.patch      unified diff against the parent's sources
current                  the active generation's number (0, or no
                         file: no Dyn layer)
staging/src/**/*.ex      where edits go before a proposal
boot.json                launch markers: `boot_attempts` (launches since
                         the last one that reached stable) and `stable`
log.jsonl                crash, revert and safe-mode reports
seed                     the generation the seed (the default sources
                         the host ships, `Rho.Dyn.Seed`) was last installed as
seed.json                what that install was built from: each seed
                         file's sha256 as shipped (`files`), and the
                         digest of a newer seed that was rejected
                         (`rejected`), so it isn't retried every launch

Everything that decides what runs (current, manifests, boot.json) is written to a temp file and renamed over the old one, so a crash mid-write leaves the old file.

Rho.Dyn, Rho.Dyn.Keeper, Rho.Dyn.Compiler and Rho.Dyn.Seed read and write it; hosts read Dyn state through Rho.Dyn instead. Every function takes the root dir first.

Summary

Types

boot.json: launches since the last stable one, whether the last reached stable, and the delivered Core update (mob_deliver manifest id) the last counted launch ran, nil for the build's own code.

Functions

Reserves the next generation number by creating its directory.

Appends a report (a JSON-encodable map) to the log; the file keeps the last 200.

Generation n's stored binaries (the module name comes from each binary).

The launch markers (boot_markers/0); a fresh, stable record when there are none.

The active generation's number; 0 when there is none (or the pointer is unreadable).

The default root dir: dyn under Rho.Config.data_dir/0.

Generation n's compile dependencies (Rho.Dyn.Reuse), or nil if it has none recorded (built before they were, or unreadable): then nothing of it is reused.

Generation n's unified diff against its parent; "" when there is none.

Generation n's manifest; generation 0 is Rho.Dyn.Generation.empty/0.

Every generation with a readable manifest, newest first, then generation 0.

The last limit reports, oldest first (string keys, as stored).

Writes generation n's compiled binaries.

Writes the launch markers (boot_markers/0).

Points current at generation n (0: no Dyn layer).

Writes generation n's compile dependencies (see deps/2).

Writes generation n's diff against its parent.

Writes generation gen.n's manifest.

Records that the seed was installed as generation n.

Writes the seed's record (seed.json, see seed_files/1).

Writes generation n's sources (%{relative_path => source}).

Replaces the staging copy with sources (normally the current generation's).

The generation the seed was installed as, or nil before it was.

The seed's record (seed.json): files, each seed file's sha256 as it shipped in the install's seed (nil when an older app installed it and didn't record them), and rejected, the digest of a seed whose install failed its check, compile or selftests.

Generation n's sources as %{relative_path => source}.

Generation n's source dir (compile diagnostics point into it).

Creates a staging source only when rel does not already exist.

Removes one staged source; :ok when it was already absent.

Writes (creates or replaces) one staged source; rel must pass valid_rel/1.

One staged source; rel must pass valid_rel/1.

The staged sources as %{relative_path => source}.

Has staging been set up (it is created from the current generation once)?

Reads generation n, applies fun, writes the result back.

Source paths are relative, .ex, made of [A-Za-z0-9_] segments.

Types

boot_markers()

@type boot_markers() :: %{
  boot_attempts: non_neg_integer(),
  stable: boolean(),
  core: String.t() | nil
}

boot.json: launches since the last stable one, whether the last reached stable, and the delivered Core update (mob_deliver manifest id) the last counted launch ran, nil for the build's own code.

Functions

allocate(root)

@spec allocate(Path.t()) :: pos_integer()

Reserves the next generation number by creating its directory.

append_log(root, entry)

@spec append_log(Path.t(), map()) :: :ok

Appends a report (a JSON-encodable map) to the log; the file keeps the last 200.

beams(root, n)

@spec beams(Path.t(), pos_integer()) :: [{module(), binary()}]

Generation n's stored binaries (the module name comes from each binary).

boot_markers(root)

@spec boot_markers(Path.t()) :: boot_markers()

The launch markers (boot_markers/0); a fresh, stable record when there are none.

current(root)

@spec current(Path.t()) :: non_neg_integer()

The active generation's number; 0 when there is none (or the pointer is unreadable).

default_root()

@spec default_root() :: Path.t()

The default root dir: dyn under Rho.Config.data_dir/0.

deps(root, n)

@spec deps(Path.t(), non_neg_integer()) :: map() | nil

Generation n's compile dependencies (Rho.Dyn.Reuse), or nil if it has none recorded (built before they were, or unreadable): then nothing of it is reused.

diff(root, n)

@spec diff(Path.t(), non_neg_integer()) :: String.t()

Generation n's unified diff against its parent; "" when there is none.

generation(root, n)

@spec generation(Path.t(), non_neg_integer()) ::
  {:ok, Rho.Dyn.Generation.t()} | {:error, :not_found}

Generation n's manifest; generation 0 is Rho.Dyn.Generation.empty/0.

generations(root)

@spec generations(Path.t()) :: [Rho.Dyn.Generation.t()]

Every generation with a readable manifest, newest first, then generation 0.

log(root, limit)

@spec log(Path.t(), pos_integer()) :: [map()]

The last limit reports, oldest first (string keys, as stored).

put_beams(root, n, beams)

@spec put_beams(Path.t(), pos_integer(), [{module(), binary()}]) :: :ok

Writes generation n's compiled binaries.

put_boot_markers(root, markers)

@spec put_boot_markers(Path.t(), %{
  :boot_attempts => non_neg_integer(),
  :stable => boolean(),
  optional(:core) => String.t() | nil
}) :: :ok | {:error, term()}

Writes the launch markers (boot_markers/0).

put_current(root, n)

@spec put_current(Path.t(), non_neg_integer()) :: :ok | {:error, term()}

Points current at generation n (0: no Dyn layer).

put_deps(root, n, deps)

@spec put_deps(Path.t(), pos_integer(), map()) :: :ok

Writes generation n's compile dependencies (see deps/2).

put_diff(root, n, text)

@spec put_diff(Path.t(), pos_integer(), String.t()) :: :ok

Writes generation n's diff against its parent.

put_generation(root, gen)

@spec put_generation(Path.t(), Rho.Dyn.Generation.t()) :: :ok | {:error, term()}

Writes generation gen.n's manifest.

put_seed(root, n)

@spec put_seed(Path.t(), pos_integer()) :: :ok | {:error, term()}

Records that the seed was installed as generation n.

put_seed_files(root, files, rejected)

@spec put_seed_files(
  Path.t(),
  %{required(String.t()) => String.t()} | nil,
  String.t() | nil
) ::
  :ok | {:error, term()}

Writes the seed's record (seed.json, see seed_files/1).

put_sources(root, n, sources)

@spec put_sources(Path.t(), pos_integer(), %{required(String.t()) => String.t()}) ::
  :ok

Writes generation n's sources (%{relative_path => source}).

reset_staging(root, sources)

@spec reset_staging(Path.t(), %{required(String.t()) => String.t()}) :: :ok

Replaces the staging copy with sources (normally the current generation's).

seed(root)

@spec seed(Path.t()) :: pos_integer() | nil

The generation the seed was installed as, or nil before it was.

seed_files(root)

@spec seed_files(Path.t()) :: %{
  files: %{required(String.t()) => String.t()} | nil,
  rejected: String.t() | nil
}

The seed's record (seed.json): files, each seed file's sha256 as it shipped in the install's seed (nil when an older app installed it and didn't record them), and rejected, the digest of a seed whose install failed its check, compile or selftests.

sources(root, n)

@spec sources(Path.t(), non_neg_integer()) :: %{required(String.t()) => String.t()}

Generation n's sources as %{relative_path => source}.

src_dir(root, n)

@spec src_dir(Path.t(), pos_integer()) :: Path.t()

Generation n's source dir (compile diagnostics point into it).

stage_create(root, rel, source)

@spec stage_create(Path.t(), String.t(), String.t()) ::
  :ok | {:error, :bad_path | :exists | File.posix()}

Creates a staging source only when rel does not already exist.

stage_delete(root, rel)

@spec stage_delete(Path.t(), String.t()) :: :ok | {:error, :bad_path}

Removes one staged source; :ok when it was already absent.

stage_put(root, rel, source)

@spec stage_put(Path.t(), String.t(), String.t()) :: :ok | {:error, :bad_path}

Writes (creates or replaces) one staged source; rel must pass valid_rel/1.

stage_read(root, rel)

@spec stage_read(Path.t(), String.t()) ::
  {:ok, String.t()} | {:error, :bad_path | :not_found}

One staged source; rel must pass valid_rel/1.

staged(root)

@spec staged(Path.t()) :: %{required(String.t()) => String.t()}

The staged sources as %{relative_path => source}.

staging?(root)

@spec staging?(Path.t()) :: boolean()

Has staging been set up (it is created from the current generation once)?

update_generation(root, n, fun)

@spec update_generation(Path.t(), pos_integer(), (Rho.Dyn.Generation.t() ->
                                              Rho.Dyn.Generation.t())) ::
  {:ok, Rho.Dyn.Generation.t()} | {:error, term()}

Reads generation n, applies fun, writes the result back.

valid_rel(rel)

@spec valid_rel(String.t()) :: {:ok, String.t()} | {:error, :bad_path}

Source paths are relative, .ex, made of [A-Za-z0-9_] segments.