Mojoler.Own (mojoler v0.1.0)

Copy Markdown View Source

Resource ownership for counters, blobs, pins, and retained terms.

A counter is a mutex-protected integer. Closing it is idempotent. When the VM collects the resource, the destructor sends :counter_destroyed once to the process that created it.

A blob is a copy of a binary. Borrowing it blocks freeze. Freeze publishes the bytes as a resource binary. A pin keeps the blob alive after the original handle is dropped. A retained term is a copy in a private environment and remains readable after the creating process exits.

Handles are opaque references. The native fields (value, borrow count, token, buffer pointer) are not Elixir fields.

Examples that create a handle store it in :persistent_term under a private key. That keeps the destructor from running while later examples in this VM still hold a use for the same object. A program that is finished with a handle drops it and lets the VM collect it.

Summary

Types

Opaque blob handle returned by open_blob/1.

Opaque counter handle returned by open_counter/0.

Opaque pin returned by blob_pin/1.

Opaque handle returned by retain_binary/1.

Functions

Increments the borrow count and returns the byte size.

Marks blob closed.

Returns how many blob destructors have run in this VM.

Publishes the blob bytes as a resource binary.

Returns a pin that keeps blob alive.

Writes byte at index in the blob.

Decrements the borrow count when it is above zero.

Returns the integer token assigned when the blob was opened.

Adds n to counter and returns the new total.

Marks counter closed.

Loads priv/native/libown.so.

Copies bin into a new blob.

Opens a counter at 0.

Returns the term stored by retain_binary/1.

Copies term into a private environment and returns a handle.

Types

blob()

@type blob() :: reference()

Opaque blob handle returned by open_blob/1.

Inspects as a reference. The copied bytes, borrow count, closed flag, frozen flag, and token live in the native object.

counter()

@type counter() :: reference()

Opaque counter handle returned by open_counter/0.

Inspects as a reference. It has no Elixir-visible fields.

Examples

iex> counter = Mojoler.Own.open_counter()
iex> :persistent_term.put(:mojoler_doc_counter_type, counter)
iex> is_reference(counter)
true

pin()

@type pin() :: reference()

Opaque pin returned by blob_pin/1.

While this reference is alive, the pinned blob's destructor does not run.

retained()

@type retained() :: reference()

Opaque handle returned by retain_binary/1.

read_retained/1 copies the stored term back out.

Functions

blob_borrow(blob)

@spec blob_borrow(blob()) :: {:ok, non_neg_integer()} | {:error, :not_mutable}

Increments the borrow count and returns the byte size.

Freeze is refused while the count is above zero.

Returns

  • {:ok, size}
  • {:error, :not_mutable} when the blob is closed or frozen

Raises

Examples

iex> blob = Mojoler.Own.open_blob(<<1, 2, 3>>)
iex> :persistent_term.put(:mojoler_doc_blob_borrow, blob)
iex> Mojoler.Own.blob_borrow(blob)
{:ok, 3}
iex> Mojoler.Own.blob_release(blob)
:ok

blob_close(blob)

@spec blob_close(blob()) :: :ok

Marks blob closed.

Close does not invalidate a binary already returned by blob_freeze/1.

Raises

Examples

iex> blob = Mojoler.Own.open_blob(<<1, 2, 3>>)
iex> :persistent_term.put(:mojoler_doc_blob_close, blob)
iex> {:ok, bin} = Mojoler.Own.blob_freeze(blob)
iex> Mojoler.Own.blob_close(blob)
:ok
iex> bin
<<1, 2, 3>>

blob_dtors()

@spec blob_dtors() :: non_neg_integer()

Returns how many blob destructors have run in this VM.

The count starts at 0 and increases by one each time a blob resource is collected. It is process-wide, not per blob.

Examples

iex> is_integer(Mojoler.Own.blob_dtors())
true

blob_freeze(blob)

@spec blob_freeze(blob()) :: {:ok, binary()} | {:error, :borrowed | :frozen}

Publishes the blob bytes as a resource binary.

The returned binary stays valid after blob_close/1. A later poke returns {:error, :not_mutable}.

Returns

  • {:ok, binary}
  • {:error, :borrowed} while a borrow is outstanding
  • {:error, :frozen} when the blob was already frozen

Raises

Examples

iex> blob = Mojoler.Own.open_blob(<<1, 2, 3>>)
iex> :persistent_term.put(:mojoler_doc_blob_freeze, blob)
iex> Mojoler.Own.blob_borrow(blob)
{:ok, 3}
iex> Mojoler.Own.blob_freeze(blob)
{:error, :borrowed}
iex> Mojoler.Own.blob_release(blob)
:ok
iex> Mojoler.Own.blob_freeze(blob)
{:ok, <<1, 2, 3>>}
iex> Mojoler.Own.blob_freeze(blob)
{:error, :frozen}

blob_pin(blob)

@spec blob_pin(blob()) :: pin()

Returns a pin that keeps blob alive.

The pin is a separate reference. Collecting it releases the keep. Collecting both the pin and the blob runs the blob destructor.

Raises

Examples

iex> blob = Mojoler.Own.open_blob(<<1>>)
iex> :persistent_term.put(:mojoler_doc_blob_pin_blob, blob)
iex> pin = Mojoler.Own.blob_pin(blob)
iex> :persistent_term.put(:mojoler_doc_blob_pin, pin)
iex> is_reference(pin)
true

blob_poke(blob, index, byte)

@spec blob_poke(blob(), integer(), 0..255) :: :ok | {:error, :not_mutable | :bounds}

Writes byte at index in the blob.

index is a zero-based integer. byte is an integer in 0..255. The write is refused when the blob is borrowed, frozen, or closed.

Returns

  • :ok
  • {:error, :not_mutable}
  • {:error, :bounds} when index is outside the blob

Raises

  • ArgumentError when blob is not a blob, index is not an integer, or byte is not an integer in 0..255

Examples

iex> blob = Mojoler.Own.open_blob(<<1, 2, 3, 4>>)
iex> :persistent_term.put(:mojoler_doc_blob_poke, blob)
iex> Mojoler.Own.blob_poke(blob, 1, 8)
:ok
iex> Mojoler.Own.blob_poke(blob, 9, 1)
{:error, :bounds}
iex> Mojoler.Own.blob_poke(blob, 0, 300)
** (ArgumentError) argument error

blob_release(blob)

@spec blob_release(blob()) :: :ok

Decrements the borrow count when it is above zero.

A release with no outstanding borrow still returns :ok.

Raises

Examples

iex> blob = Mojoler.Own.open_blob(<<1>>)
iex> :persistent_term.put(:mojoler_doc_blob_release, blob)
iex> Mojoler.Own.blob_borrow(blob)
{:ok, 1}
iex> Mojoler.Own.blob_release(blob)
:ok
iex> Mojoler.Own.blob_release(blob)
:ok

blob_token(blob)

@spec blob_token(blob()) :: {:ok, integer()}

Returns the integer token assigned when the blob was opened.

The token is not a pointer. Pokes do not change it.

Raises

Examples

iex> blob = Mojoler.Own.open_blob(<<1, 2, 3, 4>>)
iex> :persistent_term.put(:mojoler_doc_blob_token, blob)
iex> {:ok, token} = Mojoler.Own.blob_token(blob)
iex> Mojoler.Own.blob_poke(blob, 1, 9)
:ok
iex> Mojoler.Own.blob_token(blob)
{:ok, token}

counter_add(counter, n)

@spec counter_add(counter(), integer()) :: {:ok, integer()} | {:error, :closed}

Adds n to counter and returns the new total.

counter is a counter/0. n is a signed 64-bit integer. There is no optional argument.

Returns

Raises

  • ArgumentError when counter is not a counter from this module, or n is not an integer

Examples

iex> counter = Mojoler.Own.open_counter()
iex> :persistent_term.put(:mojoler_doc_counter_add, counter)
iex> Mojoler.Own.counter_add(counter, 20)
{:ok, 20}
iex> Mojoler.Own.counter_add(counter, 22)
{:ok, 42}
iex> Mojoler.Own.counter_add(:nope, 1)
** (ArgumentError) argument error

counter_close(counter)

@spec counter_close(counter()) :: :ok

Marks counter closed.

A second close on the same handle returns :ok. Further adds return {:error, :closed}.

Raises

Examples

iex> counter = Mojoler.Own.open_counter()
iex> :persistent_term.put(:mojoler_doc_counter_close, counter)
iex> Mojoler.Own.counter_close(counter)
:ok
iex> Mojoler.Own.counter_close(counter)
:ok
iex> Mojoler.Own.counter_add(counter, 1)
{:error, :closed}

load_nif()

@spec load_nif() :: :ok | {:error, term()}

Loads priv/native/libown.so.

A second call returns {:error, {:reload, charlist}}.

Examples

iex> Mojoler.Own.load_nif()
{:error, {:reload, ~c"NIF library already loaded (reload disallowed since OTP 20)."}}

open_blob(bin)

@spec open_blob(binary()) :: blob() | {:error, :not_binary}

Copies bin into a new blob.

Returns

  • a blob/0
  • {:error, :not_binary} when bin is not a binary

Examples

iex> blob = Mojoler.Own.open_blob(<<1, 2, 3>>)
iex> :persistent_term.put(:mojoler_doc_open_blob, blob)
iex> is_reference(blob)
true
iex> Mojoler.Own.open_blob([1, 2, 3])
{:error, :not_binary}

open_counter()

@spec open_counter() :: counter()

Opens a counter at 0.

Returns

A counter/0.

Examples

iex> counter = Mojoler.Own.open_counter()
iex> :persistent_term.put(:mojoler_doc_open_counter, counter)
iex> Mojoler.Own.counter_add(counter, 0)
{:ok, 0}

read_retained(handle)

@spec read_retained(retained()) :: term()

Returns the term stored by retain_binary/1.

Raises

  • ArgumentError when handle is not a retained term from this module

Examples

iex> handle = Mojoler.Own.retain_binary(<<9, 8, 7>>)
iex> :persistent_term.put(:mojoler_doc_read_retained, handle)
iex> Mojoler.Own.read_retained(handle)
<<9, 8, 7>>

retain_binary(term)

@spec retain_binary(term()) :: retained()

Copies term into a private environment and returns a handle.

term may be any Elixir term. The copy does not keep the caller's process alive.

Examples

iex> handle = Mojoler.Own.retain_binary(<<9, 8, 7>>)
iex> :persistent_term.put(:mojoler_doc_retained, handle)
iex> Mojoler.Own.read_retained(handle)
<<9, 8, 7>>