wasm_snapshot_owner (wasm v0.3.0)

View Source

One process per snapshot, holding what an Erlang term cannot hold for itself.

An image is a term and the BEAM will collect it, but a term cannot say "I am gone", and two things have to be given back when it is: the image's claim on its module, and its share of the node-wide byte budget. So each image gets a process whose life matches its own, and whose only job is to know who still wants it.

Why this is its own module

docs/architecture.md names three cycles and wasm_architecture_SUITE asserts them, so joining one is a decision rather than a side effect. Holding a claim means calling wasm_module_cache, and the cache calls wasm:compile/2 on a miss, so anything long-lived that holds a claim and is reachable from the facade is in that cycle. There is no arrangement that avoids it, only a choice of which module joins.

This one does, and wasm_snapshot stays out. The mechanism -- what a capture copies and what a restore lays over -- is then a module with no cycle in it, and what joins the knot is fifty lines whose entire purpose is to hold a claim.

What it is not

Not the keeper. The plan put this in wasm_keeper, on the grounds that it is already the long-lived owner of every other snapshot resource. The keeper keeps its state in ETS rows that a restart adopts, so a second concern there is real surgery on the one process a node cannot do without, and the guarantee does not need it: what an image needs is a lifetime matching its own.

It also makes invalidation fall out rather than be enforced. The claim is made for this process, so it dies when this process does, and the cache is what sees to that.

Summary

Functions

Add a holder.

Charge an image, once, at capture.

Bytes currently charged to images across the node.

How many processes still hold this image, or gone.

Drop a holder. Never blocks, and has one result.

Start an owner holding Handle for FirstHolder, or say the module is gone.

Functions

acquire(Owner, Holder)

-spec acquire(pid(), pid()) -> ok | gone.

Add a holder.

Holding a copied term is not ownership: the term crosses a message send for free, and an image whose holders are whoever happens to have a copy has no lifetime at all.

charge(Bytes)

-spec charge(non_neg_integer()) -> ok | {error, wasm_error:error()}.

Charge an image, once, at capture.

A restore does not charge again: it takes the existing image, and the fresh memories it builds are an instance's and go to ordinary instance accounting where they belong. Charging per restore would make the budget mean something different depending on how many restores were in flight.

charged()

-spec charged() -> non_neg_integer().

Bytes currently charged to images across the node.

holders/1

-spec holders(undefined | pid()) -> non_neg_integer() | gone.

How many processes still hold this image, or gone.

refund(Bytes)

-spec refund(non_neg_integer()) -> integer().

release(Owner, Holder)

-spec release(pid(), pid()) -> ok.

Drop a holder. Never blocks, and has one result.

start(Handle, Bytes, FirstHolder)

-spec start(wasm_module_cache:handle(), non_neg_integer(), pid()) -> {ok, pid()} | {error, not_loaded}.

Start an owner holding Handle for FirstHolder, or say the module is gone.

The claim is taken here rather than by the caller, because a claim belongs to the process that will give it back.