@@ -14,6 +14,7 @@ import (
1414"slices"
1515"strings"
1616"sync"
17+ "sync/atomic"
1718"time"
1819
1920"github.com/fsnotify/fsnotify"
@@ -59,7 +60,7 @@ type API struct {
5960dccli DevcontainerCLI
6061clock quartz.Clock
6162scriptLogger func (logSourceID uuid.UUID )ScriptLogger
62- subAgentClient SubAgentClient
63+ subAgentClient atomic. Pointer [ SubAgentClient ]
6364subAgentURL string
6465subAgentEnv []string
6566
@@ -133,7 +134,7 @@ func WithDevcontainerCLI(dccli DevcontainerCLI) Option {
133134// This is used to list, create, and delete devcontainer agents.
134135func WithSubAgentClient (client SubAgentClient )Option {
135136return func (api * API ) {
136- api .subAgentClient = client
137+ api .subAgentClient . Store ( & client )
137138}
138139}
139140
@@ -230,7 +231,6 @@ func NewAPI(logger slog.Logger, options ...Option) *API {
230231logger :logger ,
231232clock :quartz .NewReal (),
232233execer :agentexec .DefaultExecer ,
233- subAgentClient :noopSubAgentClient {},
234234containerLabelIncludeFilter :make (map [string ]string ),
235235devcontainerNames :make (map [string ]bool ),
236236knownDevcontainers :make (map [string ]codersdk.WorkspaceAgentDevcontainer ),
@@ -259,6 +259,10 @@ func NewAPI(logger slog.Logger, options ...Option) *API {
259259api .watcher = watcher .NewNoop ()
260260}
261261}
262+ if api .subAgentClient .Load ()== nil {
263+ var c SubAgentClient = noopSubAgentClient {}
264+ api .subAgentClient .Store (& c )
265+ }
262266
263267go api .watcherLoop ()
264268go api .updaterLoop ()
@@ -375,6 +379,11 @@ func (api *API) updaterLoop() {
375379}
376380}
377381
382+ // UpdateSubAgentClient updates the `SubAgentClient` for the API.
383+ func (api * API )UpdateSubAgentClient (client SubAgentClient ) {
384+ api .subAgentClient .Store (& client )
385+ }
386+
378387// Routes returns the HTTP handler for container-related routes.
379388func (api * API )Routes () http.Handler {
380389r := chi .NewRouter ()
@@ -623,9 +632,9 @@ func safeFriendlyName(name string) string {
623632return name
624633}
625634
626- //refreshContainers triggers an immediate update of the container list
635+ //RefreshContainers triggers an immediate update of the container list
627636// and waits for it to complete.
628- func (api * API )refreshContainers (ctx context.Context ) (err error ) {
637+ func (api * API )RefreshContainers (ctx context.Context ) (err error ) {
629638defer func () {
630639if err != nil {
631640err = xerrors .Errorf ("refresh containers failed: %w" ,err )
@@ -860,7 +869,7 @@ func (api *API) recreateDevcontainer(dc codersdk.WorkspaceAgentDevcontainer, con
860869
861870// Ensure an immediate refresh to accurately reflect the
862871// devcontainer state after recreation.
863- if err := api .refreshContainers (ctx );err != nil {
872+ if err := api .RefreshContainers (ctx );err != nil {
864873logger .Error (ctx ,"failed to trigger immediate refresh after devcontainer recreation" ,slog .Error (err ))
865874}
866875}
@@ -904,7 +913,8 @@ func (api *API) markDevcontainerDirty(configPath string, modifiedAt time.Time) {
904913// slate. This method has an internal timeout to prevent blocking
905914// indefinitely if something goes wrong with the subagent deletion.
906915func (api * API )cleanupSubAgents (ctx context.Context )error {
907- agents ,err := api .subAgentClient .List (ctx )
916+ client := * api .subAgentClient .Load ()
917+ agents ,err := client .List (ctx )
908918if err != nil {
909919return xerrors .Errorf ("list agents: %w" ,err )
910920}
@@ -927,7 +937,8 @@ func (api *API) cleanupSubAgents(ctx context.Context) error {
927937if injected [agent .ID ] {
928938continue
929939}
930- err := api .subAgentClient .Delete (ctx ,agent .ID )
940+ client := * api .subAgentClient .Load ()
941+ err := client .Delete (ctx ,agent .ID )
931942if err != nil {
932943api .logger .Error (ctx ,"failed to delete agent" ,
933944slog .Error (err ),
@@ -1101,7 +1112,8 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
11011112
11021113if proc .agent .ID != uuid .Nil && recreateSubAgent {
11031114logger .Debug (ctx ,"deleting existing subagent for recreation" ,slog .F ("agent_id" ,proc .agent .ID ))
1104- err = api .subAgentClient .Delete (ctx ,proc .agent .ID )
1115+ client := * api .subAgentClient .Load ()
1116+ err = client .Delete (ctx ,proc .agent .ID )
11051117if err != nil {
11061118return xerrors .Errorf ("delete existing subagent failed: %w" ,err )
11071119}
@@ -1144,7 +1156,8 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
11441156)
11451157
11461158// Create new subagent record in the database to receive the auth token.
1147- proc .agent ,err = api .subAgentClient .Create (ctx ,SubAgent {
1159+ client := * api .subAgentClient .Load ()
1160+ proc .agent ,err = client .Create (ctx ,SubAgent {
11481161Name :dc .Name ,
11491162Directory :directory ,
11501163OperatingSystem :"linux" ,// Assuming Linux for devcontainers.
@@ -1163,7 +1176,8 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
11631176if api .closed {
11641177deleteCtx ,deleteCancel := context .WithTimeout (context .Background (),defaultOperationTimeout )
11651178defer deleteCancel ()
1166- err := api .subAgentClient .Delete (deleteCtx ,proc .agent .ID )
1179+ client := * api .subAgentClient .Load ()
1180+ err := client .Delete (deleteCtx ,proc .agent .ID )
11671181if err != nil {
11681182return xerrors .Errorf ("delete existing subagent failed after API closed: %w" ,err )
11691183}
@@ -1249,8 +1263,9 @@ func (api *API) Close() error {
12491263// Note: We can't use api.ctx here because it's canceled.
12501264deleteCtx ,deleteCancel := context .WithTimeout (context .Background (),defaultOperationTimeout )
12511265defer deleteCancel ()
1266+ client := * api .subAgentClient .Load ()
12521267for _ ,id := range subAgentIDs {
1253- err := api . subAgentClient .Delete (deleteCtx ,id )
1268+ err := client .Delete (deleteCtx ,id )
12541269if err != nil {
12551270api .logger .Error (api .ctx ,"delete subagent record during shutdown failed" ,
12561271slog .Error (err ),