fix(org-import): limit concurrent Docker provisioning to 3 (#1084)
The org import fired all workspace provisioning goroutines concurrently, overwhelming Docker when creating 39+ containers. Containers timed out, leaving workspaces stuck in 'provisioning' with no schedules or hooks. Fix: - Add provisionConcurrency=3 semaphore limiting concurrent Docker ops - Increase workspaceCreatePacingMs from 50ms to 2000ms between siblings - Pass semaphore through createWorkspaceTree recursion With 39 workspaces at 3 concurrent + 2s pacing, import takes ~30s instead of timing out. Each workspace gets its full template: schedules, hooks, settings, hierarchy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2730a20194
commit
762b38fa30
@ -28,7 +28,12 @@ import (
|
||||
// OrgHandler manages org template import/export.
|
||||
// workspaceCreatePacingMs is the brief delay between sibling workspace creations
|
||||
// during org import. Prevents overwhelming Docker when creating many containers.
|
||||
const workspaceCreatePacingMs = 50
|
||||
const workspaceCreatePacingMs = 2000
|
||||
|
||||
// provisionConcurrency limits how many Docker containers can be provisioned
|
||||
// simultaneously during org import. Without this, importing 39+ workspaces
|
||||
// fires 39 goroutines that all hit Docker at once, causing timeouts (#1084).
|
||||
const provisionConcurrency = 3
|
||||
|
||||
// orgImportScheduleSQL is the upsert executed for every schedule during
|
||||
// org/import. Extracted to a const so TestImport_OrgScheduleSQLShape can
|
||||
@ -291,9 +296,12 @@ func (h *OrgHandler) Import(c *gin.Context) {
|
||||
results := []map[string]interface{}{}
|
||||
var createErr error
|
||||
|
||||
// Semaphore limits concurrent Docker provisioning (#1084).
|
||||
provisionSem := make(chan struct{}, provisionConcurrency)
|
||||
|
||||
// Recursively create workspaces
|
||||
for _, ws := range tmpl.Workspaces {
|
||||
if err := h.createWorkspaceTree(ws, nil, tmpl.Defaults, orgBaseDir, &results); err != nil {
|
||||
if err := h.createWorkspaceTree(ws, nil, tmpl.Defaults, orgBaseDir, &results, provisionSem); err != nil {
|
||||
createErr = err
|
||||
break
|
||||
}
|
||||
@ -346,7 +354,8 @@ func (h *OrgHandler) Import(c *gin.Context) {
|
||||
}
|
||||
|
||||
// createWorkspaceTree recursively creates a workspace and its children.
|
||||
func (h *OrgHandler) createWorkspaceTree(ws OrgWorkspace, parentID *string, defaults OrgDefaults, orgBaseDir string, results *[]map[string]interface{}) error {
|
||||
// provisionSem limits concurrent Docker container creation (#1084).
|
||||
func (h *OrgHandler) createWorkspaceTree(ws OrgWorkspace, parentID *string, defaults OrgDefaults, orgBaseDir string, results *[]map[string]interface{}, provisionSem chan struct{}) error {
|
||||
// Apply defaults
|
||||
runtime := ws.Runtime
|
||||
if runtime == "" {
|
||||
@ -652,7 +661,12 @@ func (h *OrgHandler) createWorkspaceTree(ws OrgWorkspace, parentID *string, defa
|
||||
}
|
||||
}
|
||||
|
||||
go h.workspace.provisionWorkspace(id, templatePath, configFiles, payload)
|
||||
// #1084: limit concurrent Docker provisioning via semaphore.
|
||||
provisionSem <- struct{}{} // acquire
|
||||
go func(wID, tPath string, cFiles map[string][]byte, p models.CreateWorkspacePayload) {
|
||||
defer func() { <-provisionSem }() // release
|
||||
h.workspace.provisionWorkspace(wID, tPath, cFiles, p)
|
||||
}(id, templatePath, configFiles, payload)
|
||||
}
|
||||
|
||||
// Insert schedules if defined. Resolve each schedule's prompt body from
|
||||
@ -792,7 +806,7 @@ func (h *OrgHandler) createWorkspaceTree(ws OrgWorkspace, parentID *string, defa
|
||||
// creating many containers in sequence; container provisioning runs in
|
||||
// goroutines so the main createWorkspaceTree returns quickly.
|
||||
for _, child := range ws.Children {
|
||||
if err := h.createWorkspaceTree(child, &id, defaults, orgBaseDir, results); err != nil {
|
||||
if err := h.createWorkspaceTree(child, &id, defaults, orgBaseDir, results, provisionSem); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(workspaceCreatePacingMs * time.Millisecond)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user