Immutable
release. Only release title and notes can be modified.
✨ Features
- Grain request scheduling (reentrancy) (#1290). Grains can now issue non-blocking requests and keep processing while a reply is in flight, extending the actor
Requestmachinery to grains and closing the loop between the two process kinds: all four request edges (actor to actor, actor to grain, grain to grain, grain to actor) work locally and across nodes.GrainContext.RequestGrainandGrainContext.RequestActor(andReceiveContext.RequestGrainfor actors) return aRequestCallhandle: register a continuation withThen, orCancelthe request. Continuations, timeouts, and cancellations all run on the grain's own turn, so handler code stays single-threaded in every mode.- Reentrancy is enabled per grain with
WithGrainReentrancyusing the samereentrancyconfig actors use:AllowAllkeeps processing any message between turns,StashNonReentrantpauses the user mailbox until the last in-flight request completes (buffered messages, timer ticks included, replay in exact arrival order), andMaxInFlightcaps concurrent requests (ErrReentrancyInFlightLimit). Per-callWithRequestTimeoutandWithReentrancyModeoverride the defaults; grain requests default toDefaultGrainRequestTimeout, and an explicit non-positive timeout disables it. GrainContext.DeferResponsetransfers reply ownership out of the current turn: it returns a one-shotGrainReplyhandle that a continuation or a later turn completes withResponse,Err, orNoErr, which is what lets a grain answer anAskGrainonly after its own downstream request comes back.CorrelationIDexposes the request ID of the message being processed.AskGrainagainst a reentrant grain no longer parks the caller on the grain's reply channels: the caller waits on a correlation slot while the grain keeps processing, so a deferred reply completes the ask without holding the grain's turn. Non-reentrant grains keep the existing ask path unchanged.- Reentrancy can also be enabled and disabled at runtime with
EnableReentrancy/DisableReentrancyon bothReceiveContextandGrainContext, for capabilities needed only in particular cases rather than for the process's whole lifetime. Disabling only rejects new requests: in-flight requests carry the mode they were admitted with and complete, unpause, and unstash normally. - Passivation cooperates with in-flight requests: a pending request keeps the grain alive, and the idle-deactivation decision for reentrant grains is taken on the grain's own turn, so it cannot race message processing. Shutdown cancels in-flight requests with
ErrRequestCanceledbefore deactivating. - The reentrancy config travels with the grain's wire record, so it survives eager relocation and remote activation. A grain reactivated by a bare send on a stored identity comes back with default config (no reentrancy) until activated with options again or re-enabled from a handler, and in-flight requests do not survive requester relocation or node crash: late replies to a fresh activation are dropped.
- A
TellGrainagainst a grain paused inStashNonReentrantmode can returnErrRequestTimeouteven though the message is delivered and processes after resume; preferRequestGraintoward reentrant grains. Internals are documented for maintainers in architecture/REENTRANCY.md.
- Grain timers: activation-scoped timers for grains (#1288). Grains can now schedule messages to themselves with
ScheduleOnce,Schedule, andScheduleWithCron, available on bothGrainContext(fromOnReceive) andGrainProps(fromOnActivate, the canonical place to start a grain's periodic behavior), plusCancelScheduleto stop a timer by reference. Ticks are delivered through the grain's mailbox and processed like any other message, so tick handling is serialized with the grain's other messages. Timers are volatile and scoped to the current activation: they are all cancelled when the grain deactivates, they are never persisted, and they never reactivate a passivated grain. Timers registered duringOnActivatestay dormant until activation completes and are discarded when activation fails. By default a tick does not reset the grain's passivation clock; opt in per timer withWithTimerKeepAlive. References are scoped per grain and registering under a reference already in use replaces the existing timer (WithTimerReferencesets one explicitly).Schedulefires at a fixed cadence like the actor scheduler, and cron expressions are evaluated in the process's local timezone with no cluster-wide arbitration needed, since a grain has exactly one activation cluster-wide. Steady-state ticks reuse the timer, the mailbox context, and the tick envelope, so recurring timers allocate almost nothing per fire.
🔧 Fixes
- Reentrant
RequestandRequestNamenow reach actors on other nodes (#1290). An async request addressed to an actor on another node failed at the sender withno serializer found for message type *commands.AsyncRequest, so reentrancy only ever worked inside a single node. The request and response envelopes that carry the correlation ID and the reply address are now serialized and registered with the remoting layer, and a reply is delivered to the address recorded on the request rather than by resolving the requester's name, so replies work wherever remoting works and do not depend on the cluster registry. Deployment note for rolling upgrades: a node on this version can now put an async request on the wire, and a node on an older version has no serializer to decode it and discards it, so such a request only completes if it was given aWithRequestTimeout(there is no implicit default). Complete the rollout before relying on cross-node reentrancy. memory.Freereported a quarter of the free memory on Apple Silicon. The macOS implementation ran thevm_statcommand and scraped its output with two regexes. When the page-size line did not match it fell back to a hardcoded 4096-byte page size, but Apple Silicon uses 16 KiB pages, so any miss undercounted free memory fourfold. A miss on the free-pages line was worse: it returned zero bytes with a nil error, leaving a caller unable to tell an exhausted system from a failed read. The subprocess and both regexes are gone.Freenow reads thevm.page_free_countandhw.pagesizesysctls and returns the underlying error when either read fails, so it can no longer report a plausible but wrong number.actor.Metric().MemoryAvailable()is the caller affected.memory.Sizetruncated total RAM on OpenBSD and NetBSD. Those kernels typehw.physmemandhw.usermemas 32-bit integers and publishhw.physmem64andhw.usermem64for the real values, so a host with more than 4 GiB of RAM reported its size modulo 4 GiB, and a 32 GiB machine reported zero. Both accessors now prefer the 64-bit node when the platform publishes one and fall back to the classic name otherwise, which leaves FreeBSD and DragonFly unchanged since they already size the classic nodes aslong. When neither node satisfies a 64-bit read the error is returned instead of a truncated value. The same change removes the package's hand-rolled sysctl decoder, which reinterpreted a[]byteas auint64through an unaligned pointer after padding a NUL-trimmed string back to eight bytes; the reads now go throughgolang.org/x/sys/unix, which requests a fixed-size buffer and verifies the returned length.- Calling
memory.Sizeormemory.Freebroke the build on platforms without an implementation. Both were defined only for Linux, macOS, Windows and the BSDs, so any consumer that called them failed to compile forwasip1,js/wasm,solaris,aixorplan9withundefined: memory.Size. Both now have a fallback on those platforms that returnserrors.ErrUnsupported, so the limitation surfaces as a runtime error rather than a build failure in a downstream toolchain.
⚡ Performance
memory.Freeno longer forks a process on macOS. Every call ran thevm_statbinary and parsed its output; it now issues two sysctls.actor.MetriccallsFreeon each invocation, so a metrics scrape no longer pays a fork and exec per sample. Resolvingvm_statthroughPATHis gone with it.
🔀 Pull Requests
- #1287 refactor: ⚡ enhance the circuit breaker performance by @Tochemey
- #1289 feat(grain): ✨ add activation-scoped timers to grains by @Tochemey
- #1291 fix(actor): deliver reentrant Request and RequestName across nodes by @Tochemey
- #1292 feat(grain): add request scheduling (reentrancy) to grains (#1290) by @Tochemey
- #1293 docs: update the grains docs by @Tochemey
- #1294 fix: fix memory stats reported by @Tochemey
Full Changelog: v4.4.2...v4.4.3