Skip to content

v4.3.0

Latest

Choose a tag to compare

@github-actions github-actions released this 07 Jul 15:41
Immutable release. Only release title and notes can be modified.

✨ Features

  • Topic subscription introspection with TopicStats (#1238). ActorSystem.TopicStats(ctx, topic, timeout) (*TopicStats, error) returns a read-only, point-in-time snapshot of a topic's subscription state without ever exposing subscriber identities, so presence-style features ("how many clients follow this feed", "does any node have subscribers for this topic") no longer require a shadow registry alongside the built-in pub/sub.
    • TopicStats reports LocalSubscriberCount (subscribers registered on this node's topic actor) and TopicInstanceCount (topic-actor instances, i.e. nodes, cluster-wide with at least one subscriber for the topic).
    • In cluster mode the local topic actor answers with its own count and fans a lightweight query out to every peer's topic actor; peers answer with their local view only and never fan the query out any further, which avoids query storms. Outside cluster mode TopicInstanceCount is simply 0 or 1 depending on whether this node has local subscribers.
    • The count reflects only live subscribers, applying the same liveness filter the publish path uses, so it matches what a publish would actually deliver to.
    • TopicStats prefers an explicit failure over an inconsistent count: if any peer cannot be reached within timeout, the whole call returns an error rather than a silently undercounted result.
    • It requires the topic actor to be running (WithPubSub() or clustering enabled), otherwise it returns the new ErrTopicActorNotStarted. Publish, subscribe, and delivery semantics are unchanged.
  • Cluster-wide single fire for cron schedules (#1236). In cluster mode, ScheduleWithCron now delivers exactly once per trigger tick across the cluster.
    • Every node keeps running its own cron trigger locally, so ticks stay in sync with no extra network round trips on the hot path.
    • Immediately before delivery each node races to claim an exclusive, TTL-bounded slot for that specific tick in the cluster store, the same atomic put-if-absent primitive that guarantees single grain activation. Only the claim winner delivers; every other node silently skips that tick.
    • There is no fixed leader to fail over: if the winning node leaves the cluster, arbitration for the next tick proceeds among the remaining nodes.
    • The cron expression is evaluated in UTC in cluster mode so every node computes the same tick instants regardless of its local timezone (outside cluster mode it stays local-timezone, as before).
    • The claim TTL scales with the cron period, clamped between one minute and 24 hours, and a node that reaches a tick more than the TTL late skips it rather than re-delivering an already-delivered tick.
    • Schedule (interval) and ScheduleOnce remain node-local, in cluster mode or not: their fire times are anchored to each node's own registration clock, so there is no shared tick to arbitrate. Outside cluster mode nothing changes.
  • Scheduler introspection with ListSchedules (#1235). ActorSystem.ListSchedules() []ScheduleInfo returns a read-only snapshot of every schedule currently known to the scheduler, exposed alongside the existing reference-based CancelSchedule/PauseSchedule/ResumeSchedule management APIs.
    • Each ScheduleInfo reports the schedule Reference (user-supplied via WithReference or auto-generated) and the target actor Path.
    • It has no effect on the schedules themselves, and a schedule stops appearing once it has been canceled or, for one-shot schedules created via ScheduleOnce, once it has fired and been delivered.
  • React to cluster leadership changes with the LeaderChanged event (#1233). Each node publishes a LeaderChanged event on its eventstream, alongside NodeJoined/NodeLeft, when the coordinator moves to a different node, so code can react to failovers without polling IsLeader.
    • The event carries the new coordinator's peers address (Address()) and transition time (Timestamp()).
    • The cluster engine emits it from the authoritative membership flag once membership settles after a topology change, at most once per transition; the initial coordinator is seeded silently, not announced.
    • It is best-effort and eventually consistent (may be missed under heavy churn), so query IsLeader(ctx) or Leader(ctx) for authoritative state. Nothing is emitted outside cluster mode.
  • Factory-free grain activation with GrainOf (#1231). The new generic package-level function actor.GrainOf[*MyGrain](ctx, system, name, opts...) retrieves or activates a grain without a factory.
    • The grain kind is derived from the type parameter, auto-registered in the kind registry, and the grain is constructed as a zero value only when local activation is actually needed.
    • Initialization belongs in OnActivate, with external resources supplied through WithGrainDependencies, which is the same construction contract the cluster already applies when recreating or relocating grains.
    • Identity resolution no longer executes a factory or allocates a grain instance on lookups and remote activations.
    • The testkit gains matching testkit.GrainOf[T] and testkit.NodeGrainOf[T] helpers.
    • GrainOf rejects non pointer-to-struct type parameters with the new ErrInvalidGrainKind and detects same-named kind collisions across packages with the new ErrGrainKindConflict instead of silently instantiating the wrong type.

⚠️ Behavior changes

  • ScheduleWithCron in cluster mode requires an explicit reference. The reference is the shared identity every node claims against, so an auto-generated per-node reference would let every node deliver independently. Calls without WithReference are rejected with the new ErrScheduleReferenceRequired.
  • Cron schedules registered on every node with the same reference now deliver once per tick cluster-wide instead of once per node. To keep per-node delivery in cluster mode, register each node's schedule under a distinct reference.
  • ScheduleWithCron evaluates the cron expression in UTC when the actor system is in cluster mode (local timezone is unchanged outside cluster mode). Express clustered cron schedules in UTC; for example "0 0 9 * * *" fires at 09:00 UTC.

🗑️ Deprecations

  • The factory-based grain APIs are deprecated but remain functional: ActorSystem.GrainIdentity, GrainContext.GrainIdentity, the GrainFactory type, and the testkit TestKit.GrainIdentity / TestNode.GrainIdentity wrappers. Migrate to GrainOf; factories that capture dependencies in their closure only ever took effect on the local node, since remote recreation always built the grain as a zero value.

🔧 Fixes

  • WithGrainDisableRelocation now reaches the cluster (#1231). The option was stored in the grain configuration but never propagated: the wire record always carried DisableRelocation=false, so the relocator relocated grains that had explicitly opted out. The flag now flows end to end through the claim record, the remote activation request (new DisableRelocation field on remote.GrainRequest), and grain recreation, so peer-activated and recreated grains keep the opt-out.
  • Remote grain activation no longer discards the caller's configuration. The remote activation request hardcoded a 1s activation timeout with 5 retries and dropped dependencies and mailbox capacity. It now carries the configured WithGrainInitTimeout, WithGrainInitMaxRetries, WithGrainMailboxCapacity, and WithGrainDependencies values to the activating node.

🔀 Pull Requests

Full Changelog: v4.2.13...v4.3.0