Skip to content

Commit 7fa1ad8

Browse files
authored
fix(agent/agentcontainers): reduce need to recreate sub agents (#18402)
1 parent 7e9a9e0 commit 7fa1ad8

File tree

5 files changed

+192
-99
lines changed

5 files changed

+192
-99
lines changed

agent/agent_test.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2080,6 +2080,10 @@ func TestAgent_DevcontainerAutostart(t *testing.T) {
20802080
subAgentConnected := make(chan subAgentRequestPayload, 1)
20812081
subAgentReady := make(chan struct{}, 1)
20822082
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2083+
if r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/api/v2/workspaceagents/me/") {
2084+
return
2085+
}
2086+
20832087
t.Logf("Sub-agent request received: %s %s", r.Method, r.URL.Path)
20842088

20852089
if r.Method != http.MethodPost {
@@ -2226,11 +2230,22 @@ func TestAgent_DevcontainerAutostart(t *testing.T) {
22262230
// Ensure the container update routine runs.
22272231
tickerFuncTrap.MustWait(ctx).MustRelease(ctx)
22282232
tickerFuncTrap.Close()
2229-
_, next := mClock.AdvanceNext()
2230-
next.MustWait(ctx)
22312233

2232-
// Verify that a subagent was created.
2233-
subAgents := agentClient.GetSubAgents()
2234+
// Since the agent does RefreshContainers, and the ticker function
2235+
// is set to skip instead of queue, we must advance the clock
2236+
// multiple times to ensure that the sub-agent is created.
2237+
var subAgents []*proto.SubAgent
2238+
for {
2239+
_, next := mClock.AdvanceNext()
2240+
next.MustWait(ctx)
2241+
2242+
// Verify that a subagent was created.
2243+
subAgents = agentClient.GetSubAgents()
2244+
if len(subAgents) > 0 {
2245+
t.Logf("Found sub-agents: %d", len(subAgents))
2246+
break
2247+
}
2248+
}
22342249
require.Len(t, subAgents, 1, "expected one sub agent")
22352250

22362251
subAgent := subAgents[0]

agent/agentcontainers/api.go

Lines changed: 58 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -671,9 +671,9 @@ func (api *API) getContainers() (codersdk.WorkspaceAgentListContainersResponse,
671671
if len(api.knownDevcontainers) > 0 {
672672
devcontainers = make([]codersdk.WorkspaceAgentDevcontainer, 0, len(api.knownDevcontainers))
673673
for _, dc := range api.knownDevcontainers {
674-
// Include the agent if it's been created (we're iterating over
674+
// Include the agent if it's running (we're iterating over
675675
// copies, so mutating is fine).
676-
if proc := api.injectedSubAgentProcs[dc.WorkspaceFolder]; proc.agent.ID != uuid.Nil && dc.Container != nil && proc.containerID == dc.Container.ID {
676+
if proc := api.injectedSubAgentProcs[dc.WorkspaceFolder]; proc.agent.ID != uuid.Nil {
677677
dc.Agent = &codersdk.WorkspaceAgentDevcontainerAgent{
678678
ID: proc.agent.ID,
679679
Name: proc.agent.Name,
@@ -977,7 +977,7 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
977977
)
978978

979979
// Check if subagent already exists for this devcontainer.
980-
recreateSubAgent := false
980+
maybeRecreateSubAgent := false
981981
proc, injected := api.injectedSubAgentProcs[dc.WorkspaceFolder]
982982
if injected {
983983
if proc.containerID == container.ID && proc.ctx.Err() == nil {
@@ -992,12 +992,15 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
992992
logger.Debug(ctx, "container ID changed, injecting subagent into new container",
993993
slog.F("old_container_id", proc.containerID),
994994
)
995-
recreateSubAgent = true
995+
maybeRecreateSubAgent = proc.agent.ID != uuid.Nil
996996
}
997997

998998
// Container ID changed or the subagent process is not running,
999999
// stop the existing subagent context to replace it.
10001000
proc.stop()
1001+
} else {
1002+
// Set SubAgent defaults.
1003+
proc.agent.OperatingSystem = "linux" // Assuming Linux for devcontainers.
10011004
}
10021005

10031006
// Prepare the subAgentProcess to be used when running the subagent.
@@ -1090,36 +1093,29 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
10901093
// logger.Warn(ctx, "set CAP_NET_ADMIN on agent binary failed", slog.Error(err))
10911094
// }
10921095

1093-
// Detect workspace folder by executing `pwd` in the container.
1094-
// NOTE(mafredri): This is a quick and dirty way to detect the
1095-
// workspace folder inside the container. In the future we will
1096-
// rely more on `devcontainer read-configuration`.
1097-
var pwdBuf bytes.Buffer
1098-
err = api.dccli.Exec(ctx, dc.WorkspaceFolder, dc.ConfigPath, "pwd", []string{},
1099-
WithExecOutput(&pwdBuf, io.Discard),
1100-
WithExecContainerID(container.ID),
1101-
)
1102-
if err != nil {
1103-
return xerrors.Errorf("check workspace folder in container: %w", err)
1104-
}
1105-
directory := strings.TrimSpace(pwdBuf.String())
1106-
if directory == "" {
1107-
logger.Warn(ctx, "detected workspace folder is empty, using default workspace folder",
1108-
slog.F("default_workspace_folder", DevcontainerDefaultContainerWorkspaceFolder),
1096+
subAgentConfig := proc.agent.CloneConfig(dc)
1097+
if proc.agent.ID == uuid.Nil || maybeRecreateSubAgent {
1098+
// Detect workspace folder by executing `pwd` in the container.
1099+
// NOTE(mafredri): This is a quick and dirty way to detect the
1100+
// workspace folder inside the container. In the future we will
1101+
// rely more on `devcontainer read-configuration`.
1102+
var pwdBuf bytes.Buffer
1103+
err = api.dccli.Exec(ctx, dc.WorkspaceFolder, dc.ConfigPath, "pwd", []string{},
1104+
WithExecOutput(&pwdBuf, io.Discard),
1105+
WithExecContainerID(container.ID),
11091106
)
1110-
directory = DevcontainerDefaultContainerWorkspaceFolder
1111-
}
1112-
1113-
if proc.agent.ID != uuid.Nil && recreateSubAgent {
1114-
logger.Debug(ctx, "deleting existing subagent for recreation", slog.F("agent_id", proc.agent.ID))
1115-
client := *api.subAgentClient.Load()
1116-
err = client.Delete(ctx, proc.agent.ID)
11171107
if err != nil {
1118-
return xerrors.Errorf("delete existing subagent failed: %w", err)
1108+
return xerrors.Errorf("check workspace folder in container: %w", err)
11191109
}
1120-
proc.agent = SubAgent{}
1121-
}
1122-
if proc.agent.ID == uuid.Nil {
1110+
directory := strings.TrimSpace(pwdBuf.String())
1111+
if directory == "" {
1112+
logger.Warn(ctx, "detected workspace folder is empty, using default workspace folder",
1113+
slog.F("default_workspace_folder", DevcontainerDefaultContainerWorkspaceFolder),
1114+
)
1115+
directory = DevcontainerDefaultContainerWorkspaceFolder
1116+
}
1117+
subAgentConfig.Directory = directory
1118+
11231119
displayAppsMap := map[codersdk.DisplayApp]bool{
11241120
// NOTE(DanielleMaywood):
11251121
// We use the same defaults here as set in terraform-provider-coder.
@@ -1138,6 +1134,13 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
11381134

11391135
for _, customization := range coderCustomization {
11401136
for app, enabled := range customization.DisplayApps {
1137+
if _, ok := displayAppsMap[app]; !ok {
1138+
logger.Warn(ctx, "unknown display app in devcontainer customization, ignoring",
1139+
slog.F("app", app),
1140+
slog.F("enabled", enabled),
1141+
)
1142+
continue
1143+
}
11411144
displayAppsMap[app] = enabled
11421145
}
11431146
}
@@ -1149,26 +1152,41 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
11491152
displayApps = append(displayApps, app)
11501153
}
11511154
}
1155+
slices.Sort(displayApps)
11521156

1157+
subAgentConfig.DisplayApps = displayApps
1158+
}
1159+
1160+
deleteSubAgent := proc.agent.ID != uuid.Nil && maybeRecreateSubAgent && !proc.agent.EqualConfig(subAgentConfig)
1161+
if deleteSubAgent {
1162+
logger.Debug(ctx, "deleting existing subagent for recreation", slog.F("agent_id", proc.agent.ID))
1163+
client := *api.subAgentClient.Load()
1164+
err = client.Delete(ctx, proc.agent.ID)
1165+
if err != nil {
1166+
return xerrors.Errorf("delete existing subagent failed: %w", err)
1167+
}
1168+
proc.agent = SubAgent{} // Clear agent to signal that we need to create a new one.
1169+
}
1170+
1171+
if proc.agent.ID == uuid.Nil {
11531172
logger.Debug(ctx, "creating new subagent",
1154-
slog.F("directory", directory),
1155-
slog.F("display_apps", displayApps),
1173+
slog.F("directory", subAgentConfig.Directory),
1174+
slog.F("display_apps", subAgentConfig.DisplayApps),
11561175
)
11571176

11581177
// Create new subagent record in the database to receive the auth token.
11591178
client := *api.subAgentClient.Load()
1160-
proc.agent, err = client.Create(ctx, SubAgent{
1161-
Name: dc.Name,
1162-
Directory: directory,
1163-
OperatingSystem: "linux", // Assuming Linux for devcontainers.
1164-
Architecture: arch,
1165-
DisplayApps: displayApps,
1166-
})
1179+
newSubAgent, err := client.Create(ctx, subAgentConfig)
11671180
if err != nil {
11681181
return xerrors.Errorf("create subagent failed: %w", err)
11691182
}
1183+
proc.agent = newSubAgent
11701184

11711185
logger.Info(ctx, "created new subagent", slog.F("agent_id", proc.agent.ID))
1186+
} else {
1187+
logger.Debug(ctx, "subagent already exists, skipping recreation",
1188+
slog.F("agent_id", proc.agent.ID),
1189+
)
11721190
}
11731191

11741192
api.mu.Lock() // Re-lock to update the agent.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy