agenttask

package
v1.0.80 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Index

Constants

View Source
const (
	LeaseDuration = 60 * time.Second
)

Variables

This section is empty.

Functions

func BackoffFor

func BackoffFor(attempts int) time.Duration

BackoffFor returns the delay before a task that has consumed `attempts` attempts may be claimed again: 5s, 10s, 20s, 40s, ... capped at 5m.

func BootUpdateDedupeKey

func BootUpdateDedupeKey(sessionID string) string

BootUpdateDedupeKey scopes the UpdateOnStart task to one agent process, so a reconnect loop cannot queue a stack of update tasks while one is still active. It keys on the agent's session id, which survives reconnects, and not on the per-stream id.

func Cancel

func Cancel(taskID string) error

Cancel takes a pending task straight to terminal. A running task cannot be yanked from under the agent, so it is flagged instead; the agent picks the flag up on its next lease renewal and unwinds.

func Claim

func Claim(agentID bson.ObjectID, serverRunning bool) (*v2.AgentTaskSchema, error)

Claim atomically moves the agent's oldest due task from pending to running and mints a fencing token. It returns (nil, nil) when nothing is due.

If the agent already has a running task, `uniq_running_per_agent` rejects the write with E11000 and we report "busy" the same way as "nothing to do". Two replicas racing for the same agent therefore need no coordination: one wins, the other backs off.

func Complete

func Complete(taskID, leaseToken string) error

func Enqueue

func Enqueue(agentID, accountID bson.ObjectID, action string, data interface{}, dedupeKey string, trigger v2.TaskTrigger, opts EnqueueOpts) (string, error)

Enqueue creates a pending task. It is idempotent on dedupeKey: if an active task with the same key already exists, its id is returned instead of an error. This closes the window where a workflow step writes a task, crashes before persisting the id, and re-enqueues on restart.

func EnsureIndexes

func EnsureIndexes() error

EnsureIndexes creates the indexes the queue's correctness depends on.

The two unique partial indexes are not optimizations. `uniq_running_per_agent` is what serializes tasks on an agent; `uniq_active_dedupe` is what makes an enqueue idempotent. Removing either reintroduces the double-install bug.

func Fail

func Fail(taskID, leaseToken, errMsg string) error

Fail decides between a retry and a death, and honours a pending cancellation. A cancelled task is terminal: it must never come back on the next tick.

func FindByDedupeKey

func FindByDedupeKey(agentID bson.ObjectID, dedupeKey string) (*v2.AgentTaskSchema, error)

FindByDedupeKey returns the newest task with this key whatever its status, or nil if there is none.

Enqueue can only adopt an *active* task, because that is all the unique index constrains. A caller that lost the id of a task which has since finished would therefore enqueue a duplicate and re-run work that already succeeded. Looking the task up by key, terminal or not, closes that window. Safe because the only keys in use are unique for all time (workflow id + action index).

func FindPendingByAction

func FindPendingByAction(agentID bson.ObjectID, action string) (*v2.AgentTaskSchema, error)

FindPendingByAction returns the agent's pending task of this action, or nil if there is none.

This exists so a caller outside the package (agentmod, re-pointing a pending startsfserver at a newer sync — see SetGate) never needs its own GetCollection("agenttasks") read: the queue owns that collection, and every query against it lives here.

func Get

func Get(taskID string) (*v2.AgentTaskSchema, error)

func HasActiveAction

func HasActiveAction(agentID bson.ObjectID, action string) (bool, error)

HasActiveAction reports whether the agent has an ACTIVE (pending or running) task of this action.

It exists because agent-reported status is blind to task state. `agents.status.running` is still false for the whole time a startsfserver is mid-boot, so a caller that decides "is the game server running?" from that field alone decides FALSE while the game is coming up, and gates nothing. A syncmods inserted on that decision is claimable the moment the boot finishes, and rewrites the Mods directory under a live game. "Running" for gating purposes must mean "running, or about to be", and this is the second half of that.

`active` is present exactly for pending and running, and is unset on every terminal transition, so it is the whole predicate.

func InitAgentTaskService

func InitAgentTaskService() error

InitAgentTaskService creates the indexes before anything can dispatch. If the indexes are missing, the queue's invariants are not enforced, so a failure here must stop the process rather than degrade silently.

func ListForAgent

func ListForAgent(agentID bson.ObjectID, limit int64) ([]v2.AgentTaskSchema, error)

func ReapExpiredLeases

func ReapExpiredLeases() error

ReapExpiredLeases returns abandoned tasks to the queue, then releases tasks gated behind a parent that does not exist.

The attempt was already spent at claim time, so a crash-looping agent is bounded by MaxAttempts rather than retrying forever.

func Release

func Release(taskID, leaseToken string) error

Release returns a task to the queue without spending an attempt. It is the graceful-shutdown path: the agent chose to stop, so it should not be punished.

func RenewLease

func RenewLease(taskID, leaseToken string) (bool, bool, error)

RenewLease extends the lease and doubles as the cancellation channel. ok=false means the agent has lost the task and must abandon it.

func ReplacePendingPayload

func ReplacePendingPayload(agentID bson.ObjectID, action string, data interface{}) (string, error)

ReplacePendingPayload overwrites the data of an already-pending task of the given action, returning its id, or "" if there was none. It marshals the payload the same way Enqueue does.

This is the only safe way to update a not-yet-claimed task's payload: it matches status=pending only, never running, because a running task's payload is already in the agent's hands and rewriting it here would not reach the agent. Callers that need "replace if pending, otherwise enqueue" must not fall back to Enqueue's dedupe-adoption on failure to find a pending task, since adoption also matches a RUNNING task and would ship it a payload update it can never see.

func ReportProgress

func ReportProgress(taskID, leaseToken string, pct int32, msg string) error

func Retry

func Retry(taskID string) error

Retry resurrects a dead task with a fresh attempt budget. If an identical action is already active, uniq_active_dedupe rejects the write and the caller gets a usable message rather than a second install.

func SetGate

func SetGate(taskID string, opts EnqueueOpts) (bool, error)

SetGate makes a PENDING task's claim gates exactly the ones in opts, replacing whatever it carried before. It is a no-op on any other status: a running task's gates have already been evaluated.

It exists because Enqueue's dedupe-adoption path ignores opts — an adopted task keeps the gating it was created with — and because a caller's plan may need to move an existing task onto a different gate entirely (escalating a deferred syncmods into a stop -> sync chain must CLEAR requiresServerStopped as it sets dependsOn, or the chain's own stop is what the sync ends up waiting on forever). Gates are therefore set as a set, not patched.

The zero EnqueueOpts releases the task. That is also how a caller un-strands a task it gated onto a pre-assigned _id whose insert then failed.

The bool reports whether the task was still PENDING and so actually re-gated. It is load-bearing, not diagnostic: false means the dispatcher has already claimed the task and it is RUNNING RIGHT NOW. A caller re-gating a pre-existing pending task to keep it behind a task it has not inserted yet MUST treat false as "I lost the race" and abandon the insert — proceeding would leave an ungated syncmods next to a booting game server. Only a caller re-gating a task it just enqueued itself may ignore it.

func ShutdownAgentTaskService

func ShutdownAgentTaskService() error

func StartDispatcher

func StartDispatcher()

func StopDispatcher

func StopDispatcher()

func WorkflowDedupeKey

func WorkflowDedupeKey(workflowID bson.ObjectID, actionIdx int) string

WorkflowDedupeKey makes a workflow step's enqueue idempotent. Re-running the step produces the same key, and the unique partial index rejects the second insert.

Types

type Assignment

type Assignment struct {
	TaskID       string
	Action       string
	Data         string
	LeaseToken   string
	Attempt      int32
	MaxAttempts  int32
	LeaseSeconds int32
}

Assignment is one task pushed down an agent's stream.

type EnqueueOpts

type EnqueueOpts struct {
	ID                    *bson.ObjectID
	DependsOn             *bson.ObjectID
	RequiresServerStopped bool
}

EnqueueOpts carries the optional gates through to the stored task. Zero value means "claimable as soon as the agent is idle", which is what every existing caller wants.

ID lets the caller pre-assign the task's _id. That is what allows a caller to gate an ALREADY-PENDING task behind a task it has not inserted yet: gating first and inserting second means the pending task is never simultaneously ungated and older than the new one, which is the only ordering under which the dispatcher's FIFO claim cannot run them backwards. If the insert then fails, the caller must SetGate the stranded task back to claimable.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry tracks which agents have a live task stream on *this* replica.

func GetRegistry

func GetRegistry() *Registry

func (*Registry) Add

func (r *Registry) Add(agentID bson.ObjectID) (<-chan Assignment, func())

Add registers a stream and returns the receive channel plus a deregister func.

The deregister func removes the entry only if it is still *this* entry, using pointer identity rather than any id the agent supplies. An agent that reconnects before its old server-side stream has torn down would otherwise have the old teardown evict and close the new, healthy stream.

func (*Registry) ConnectedAgents

func (r *Registry) ConnectedAgents() []bson.ObjectID

func (*Registry) Has

func (r *Registry) Has(agentID bson.ObjectID) bool

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL