mirror of
https://github.com/coder/coder.git
synced 2026-09-22 21:22:17 +08:00
Implements the chatd stabilization RFC. Combines: - https://github.com/coder/coder/pull/25908 - https://github.com/coder/coder/pull/25923 - https://github.com/coder/coder/pull/26109 - https://github.com/coder/coder/pull/26110 - https://github.com/coder/coder/pull/26111 - https://github.com/coder/coder/pull/26112
168 lines
5.6 KiB
Go
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}
|