Verdict on this Mac: PASS, with the limits below. Linux: PENDING.
Mojoler.Own registers three resource types plus a pin type, in load. unload destroys the mutex and frees the private box. The upgrade callback returns 1. No test performed an OTP upgrade.
| Object | Allocator | Owner | Freed by |
|---|---|---|---|
| Counter / blob / retained / pin object | enif_alloc_resource | BEAM resource refcount | resource destructor after the last ref |
| Blob bytes | calloc | the blob resource | free in blob_dtor |
| Retained term | enif_alloc_env plus enif_make_copy | the retained resource | enif_free_env in retained_dtor |
| Entry and function table | calloc | OTP, after nif_init | not freed; bounded by the reload refusal |
counter_add takes the module mutex. Close is idempotent. A closed counter returns {:error, :closed}. The destructor sends :counter_destroyed once. Twenty concurrent adds of 1 land on 20.
open_blob copies the input into a calloc buffer and stores a token. blob_poke writes that buffer in place. The token does not change. Freeze fails while borrows != 0, then enif_make_resource_binary publishes the same bytes. A second freeze is {:error, :frozen}. Poke after freeze is {:error, :not_mutable}. Closing the handle leaves an already created resource binary readable.
blob_pin calls enif_keep_resource. Dropping the blob term does not run blob_dtor while the pin term is alive. Dropping the pin does. blob_dtors/0 is the counter.
retain_binary copies into an independent env. The test reads <<9, 8, 7>> after the creating process has exited.
Elixir functions return terms. They do not return pointers. A strict no-copy request on a non-binary is Mojoler.Dsl.Bytes.borrow_len/1, which returns {:error, :not_binary}.
scripts/probe-abort.sh runs abort in a child BEAM (E26). That path is not in the product libraries.