Roux.Blob.Trace (roux v0.2.2)

Copy Markdown View Source

Verifying traces: a value kept with what computing it observed, reused only while every observation still holds.

deps = [{{:file, path}, stamp(path)}, {{:env, "LANG"}, System.get_env("LANG")}]
:ok = Roux.Blob.Trace.put(store, {:parse, path}, deps, parsed)

Roux.Blob.Trace.find(store, {:parse, path}, fn
  {:file, path} -> stamp(path)
  {:env, name} -> System.get_env(name)
end)

A name keeps several traces, one per distinct set of observations: a value computed before an edit is found again once the edit is undone. find/4 tries the most recently used first, observing each dependency at most once, and marks the trace it returns used (mark_used/1: within the store's refresh interval, it is left alone — Roux.Blob's "Raw I/O, and touches that refresh"), so "recently used" means used, not only written — and a collection (Roux.Blob.gc/2) keeps traces in use, and the entries their values name.

Versions

A trace is never replaced in place: a rename onto a name is not atomic everywhere, and a reader that meets its moment finds no trace at all. Each value a trace holds is a version, a file of its own named by the digest of its observations, the time it was written and the digest of its bytes, linked into place and never changed — the store's CAS entries' rule (Roux.Blob's "Entries"). A put of another value under the same observations adds a version, then removes the versions it found there that were written more than a second before it: a reader that listed one a moment ago can still read it. A lookup reads each set of observations' newest version, by the time in its name (a modification time, in whole seconds, cannot order two versions of one second); one removed between its listing and its read is looked for again. So a reader always finds a complete version, and after a put, the one it wrote. Putting the bytes of the newest version again writes nothing, and marks it used.

Bounded history

Every set of observations a name ever met would otherwise stay until a collection, and a lookup reads and decodes them all. So history is bounded at both ends:

  • put/5 keeps a name's keep: most recently used traces (8 by default), and every trace written or used within the store's window (Roux.Blob.window/1, twice its refresh interval: a trace used within the interval was marked within the window), however many: an older one goes, renamed aside and then removed (or put back, if a lookup marked it used meanwhile), so a reader finds a trace whole or not at all.
  • fetch/3 and find/4 take limit:: they stat a name's traces and read and decode only the limit most recently used.

Several OS processes may put and look up one name at once: a trace removed as a lookup reaches it is passed over, so a lookup is a hit or a miss, never an error.

Summary

Types

What computing a value observed: {what was looked at, what it was}.

t()

A trace: its name, its observations, the value they gave, where it is kept, when it was last used (its modification time, POSIX seconds), and the refresh interval of the store it came from.

Functions

The traces kept under name, the most recently used first: each set of observations' newest version.

The value of a trace under name whose observations all still hold — observe.(what) equal (===) to what was observed — or :miss. source is a store, or traces already fetched from one (fetch/3). The trace found is marked used (mark_used/1): it is now the name's most recently used. One removed as it was found — taken by a collection, or superseded — is not returned: with a store, it is looked for again.

Marks a trace fetched (fetch/3) used, as find/4 marks the one it returns: its modification time set to now, unless that is within its store's refresh interval. :gone when the trace was removed since it was read, which can happen only to one unused for longer than the interval: a collection may be taking the entries its value names, so that value is not to be used.

Keeps value under name, with the observations deps it was computed from: a new version of the trace of those observations (see "Versions"), and the only one once this returns, unless another process put one meanwhile. Keeps the keep: most recently used traces of the name, and every one used within the window (see "Bounded history").

Types

dep()

@type dep() :: {term(), term()}

What computing a value observed: {what was looked at, what it was}.

t()

@type t() :: %{
  name: term(),
  deps: [dep()],
  value: term(),
  path: Path.t(),
  mtime: integer(),
  refresh: non_neg_integer()
}

A trace: its name, its observations, the value they gave, where it is kept, when it was last used (its modification time, POSIX seconds), and the refresh interval of the store it came from.

Functions

fetch(store, name, opts \\ [])

@spec fetch(Roux.Blob.t(), term(), keyword()) :: [t()]

The traces kept under name, the most recently used first: each set of observations' newest version.

Options

  • :limit — read and decode only the limit most recently used (default: all of them). The others are looked at with one stat each.

find(source, name, observe, opts \\ [])

@spec find(Roux.Blob.t() | [t()], term(), (term() -> term()), keyword()) ::
  {:ok, term()} | :miss

The value of a trace under name whose observations all still hold — observe.(what) equal (===) to what was observed — or :miss. source is a store, or traces already fetched from one (fetch/3). The trace found is marked used (mark_used/1): it is now the name's most recently used. One removed as it was found — taken by a collection, or superseded — is not returned: with a store, it is looked for again.

Options

  • :limit — with a store, look among only the limit most recently used traces of name (fetch/3).

mark_used(map)

@spec mark_used(t()) :: :ok | :gone

Marks a trace fetched (fetch/3) used, as find/4 marks the one it returns: its modification time set to now, unless that is within its store's refresh interval. :gone when the trace was removed since it was read, which can happen only to one unused for longer than the interval: a collection may be taking the entries its value names, so that value is not to be used.

put(store, name, deps, value, opts \\ [])

@spec put(Roux.Blob.t(), term(), [dep()], term(), keyword()) ::
  :ok | {:error, File.posix()}

Keeps value under name, with the observations deps it was computed from: a new version of the trace of those observations (see "Versions"), and the only one once this returns, unless another process put one meanwhile. Keeps the keep: most recently used traces of the name, and every one used within the window (see "Bounded history").

Options

  • :keep — how many traces the name keeps beyond the window: a positive integer (default 8), or :infinity.