Files
coder/coderd/x/chatd/chatstate/state.go
T

168 lines
5.6 KiB
Go

package chatstate
import (
"github.com/coder/coder/v2/coderd/database"
)
// ExecutionState identifies a chat's current execution state. Values
// outside the chat execution state model are represented by
// [StateInvalid].
type ExecutionState string
const (
// StateN: chat does not exist.
StateN ExecutionState = "N"
// StateW: waiting, empty queue, not archived.
StateW ExecutionState = "W"
// StateE0: error, empty queue, not archived.
StateE0 ExecutionState = "E0"
// StateE1: error, non-empty queue, not archived.
StateE1 ExecutionState = "E1"
// StateR0: running, empty queue, not archived.
StateR0 ExecutionState = "R0"
// StateR1: running, non-empty queue, not archived.
StateR1 ExecutionState = "R1"
// StateI0: interrupting, empty queue, not archived.
StateI0 ExecutionState = "I0"
// StateI1: interrupting, non-empty queue, not archived.
StateI1 ExecutionState = "I1"
// StateA0: requires_action, empty queue, not archived.
StateA0 ExecutionState = "A0"
// StateA1: requires_action, non-empty queue, not archived.
StateA1 ExecutionState = "A1"
// StateXW: archived waiting, empty queue.
StateXW ExecutionState = "XW"
// StateXE0: archived error, empty queue.
StateXE0 ExecutionState = "XE0"
// StateXE1: archived error, non-empty queue.
StateXE1 ExecutionState = "XE1"
// StateInvalid groups every status/archive/queue combination that
// is not one of the valid states above. The state machine refuses
// non-reconciliation transitions on invalid states and exposes the
// [Tx.ReconcileInvalidState] transition to recover.
StateInvalid ExecutionState = "Invalid"
)
// String implements fmt.Stringer.
func (s ExecutionState) String() string { return string(s) }
// AllExecutionStates is the canonical enumeration of every value the
// classifier can return. Tests rely on this list to iterate over every
// state when verifying transition coverage.
var AllExecutionStates = []ExecutionState{
StateN,
StateW,
StateE0,
StateE1,
StateR0,
StateR1,
StateI0,
StateI1,
StateA0,
StateA1,
StateXW,
StateXE0,
StateXE1,
StateInvalid,
}
// IsRunnable returns true for the execution states that the chat
// worker is allowed to acquire and drive forward: R0, R1, I0, I1,
// A0, and A1. Requires-action states need worker ownership for
// timeout processing. Other states are idle (W, E*, XW, XE*), absent
// (N), or invalid.
func (s ExecutionState) IsRunnable() bool {
switch s {
case StateR0, StateR1, StateI0, StateI1, StateA0, StateA1:
return true
default:
return false
}
}
// IsArchived returns true for the three archived execution states.
func (s ExecutionState) IsArchived() bool {
switch s {
case StateXW, StateXE0, StateXE1:
return true
default:
return false
}
}
// QueueNonEmpty returns true for execution states that require a
// non-empty queue. Useful when seeding test fixtures.
func (s ExecutionState) QueueNonEmpty() bool {
switch s {
case StateE1, StateR1, StateI1, StateA1, StateXE1:
return true
default:
return false
}
}
// ClassifyExecutionState turns the chat row, queue cardinality, and
// whether the chat row exists into an [ExecutionState]. The caller is
// responsible for loading the chat under the row lock and reading the
// queue count in the same transaction.
//
// Callers that have no chat row (lookup returned sql.ErrNoRows)
// should pass exists=false; the chat, status, and archive arguments
// are then ignored.
//
// The classifier is a single flat switch over the valid (status,
// archived, queue) tuples in the chat execution state model. Anything
// outside that set (legacy pending/paused/completed statuses, archived
// busy states, waiting with a non-empty queue, future enum values)
// falls through to [StateInvalid].
//
//nolint:revive // queueNonEmpty/exists are simple classifier inputs.
func ClassifyExecutionState(chat database.Chat, queueNonEmpty, exists bool) ExecutionState {
if !exists {
return StateN
}
switch {
case chat.Status == database.ChatStatusWaiting && !chat.Archived && !queueNonEmpty:
return StateW
case chat.Status == database.ChatStatusWaiting && chat.Archived && !queueNonEmpty:
return StateXW
case chat.Status == database.ChatStatusError && !chat.Archived && !queueNonEmpty:
return StateE0
case chat.Status == database.ChatStatusError && !chat.Archived && queueNonEmpty:
return StateE1
case chat.Status == database.ChatStatusError && chat.Archived && !queueNonEmpty:
return StateXE0
case chat.Status == database.ChatStatusError && chat.Archived && queueNonEmpty:
return StateXE1
case chat.Status == database.ChatStatusRunning && !chat.Archived && !queueNonEmpty:
return StateR0
case chat.Status == database.ChatStatusRunning && !chat.Archived && queueNonEmpty:
return StateR1
case chat.Status == database.ChatStatusInterrupting && !chat.Archived && !queueNonEmpty:
return StateI0
case chat.Status == database.ChatStatusInterrupting && !chat.Archived && queueNonEmpty:
return StateI1
case chat.Status == database.ChatStatusRequiresAction && !chat.Archived && !queueNonEmpty:
return StateA0
case chat.Status == database.ChatStatusRequiresAction && !chat.Archived && queueNonEmpty:
return StateA1
}
return StateInvalid
}
// OwnershipState identifies whether a chat row is currently owned by a
// worker. The state machine treats execution and ownership as
// orthogonal.
type OwnershipState string
const (
// StateU: chat has no owner (worker_id IS NULL).
StateU OwnershipState = "U"
// StateO: chat has an owner (worker_id IS NOT NULL).
StateO OwnershipState = "O"
)
// AllOwnershipStates is the canonical enumeration of ownership states.
var AllOwnershipStates = []OwnershipState{StateU, StateO}