Skip to content

use promise for the sandbox init - #1710

Merged
dobrac merged 2 commits into
feat/memory-prefetchingfrom
promise-poc
Jan 12, 2026
Merged

dobrac merged 2 commits into
feat/memory-prefetchingfrom
promise-poc

Conversation

@dobrac

@dobrac dobrac commented Jan 12, 2026 •

Copy link
Copy Markdown
Contributor

Note

Modernizes sandbox resume flow with Promise-based parallel initialization and introduces a shared Promise utility.

  • Replace errgroup/channels with utils.Promise to initialize uffd, network slot acquisition, rootfs overlay, and memory serving in parallel; start prefetch once uffd is ready
  • Adjust FC startup to use resolved Slot and overlay, fetching metadata/rootfs as needed and waiting on promises before Resume
  • Add packages/shared/pkg/utils/promise (generic) with Wait/Done/Result and comprehensive unit tests

Written by Cursor Bugbot for commit 7608780. This will update automatically on new commits. Configure here.

@dobrac dobrac changed the title feat: memory prefetching use promise for the sandbox init Jan 12, 2026
})

// Prefetching
go func() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goroutine at line 410 silently swallows errors from t.Memfile(ctx) and t.Metadata(). If these operations fail, the prefetcher simply won't run without any visibility. Consider logging these errors before returning.

var wg errgroup.Group
// Uffd initialization
fcUffdPath := sandboxFiles.SandboxUffdSocketPath()
uffdPromise := utils.NewPromise(func() (*uffd.Uffd, error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Promise function closures capture ctx directly (line 392, 452, 476), but these closures execute in separate goroutines that may outlive the original context. If ResumeSandbox returns early due to an error, ctx may be cancelled while the promise goroutines are still running, potentially causing all promises to fail with context.Canceled instead of completing their work.

ips := <-ipsCh
if ips.err != nil {
return nil, fmt.Errorf("failed to get network slot: %w", ips.err)
rootfs, err := t.Rootfs()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant call to t.Rootfs() - this was already fetched and stored in the overlayPromise at line 476. You're fetching it again here when you could reuse the readonlyRootfs from the promise closure, wasting an I/O operation.

}

telemetry.ReportEvent(ctx, "got network slot")
meta, err := t.Metadata()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, t.Metadata() is already called in the prefetch goroutine. This creates redundant work if the metadata fetch is expensive. Consider restructuring to fetch once and reuse.


telemetry.ReportEvent(ctx, "got snapfile")

fcUffd, err := uffdPromise.Wait(ctx)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uffdPromise.Wait() call here creates a potential race - if uffdPromise fails before this point, fcUffd will be set to the error value. However, you've already started using fcUffd in the prefetch goroutine (line 426) which may have succeeded with the promise earlier. This could cause inconsistent behavior if promise failures happen at different times.

@dobrac
dobrac marked this pull request as ready for review January 12, 2026 14:51

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2014e95fb2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +461 to +462
go func(ctx context.Context) {
returnErr := f.networkPool.Return(ctx, slot)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return network slots with uncancelled context

Cleanup now passes the same ctx into networkPool.Return, but Pool.Return short-circuits on ctx.Done() (see packages/orchestrator/internal/sandbox/network/pool.go), so any sandbox teardown that happens after a cancellation/error will fail to return the slot and leak pool capacity. This is a regression from the prior context.WithoutCancel usage in the cleanup path and can accumulate if ResumeSandbox is retried or canceled. Consider wrapping the context (or using a background context) before calling Return so cleanup always returns slots.

Useful? React with 👍 / 👎.

})

var meta metadata.Template
wg.Go(func() error {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resource leak when promises complete after cleanup runs

High Severity

The migration from errgroup to promises removes synchronization that prevents resource leaks. When any promise fails early (e.g., overlayPromise fails), the deferred cleanup.Run() executes immediately. Other promises still running in goroutines (like ipsPromise or uffdPromise) will eventually complete and call cleanup.Add(), but since hasRun is already true, their cleanup functions are silently ignored. This leaks network slots (not returned to pool), uffd handlers (not closed), and rootfs overlays (not closed). The old code had a cleanup function that explicitly waited on the channel before cleanup could proceed, ensuring resources were registered before cleanup ran.

Additional Locations (2)

Fix in Cursor Fix in Web

if returnErr != nil {
logger.L().Error(ctx, "failed to return network slot", zap.Error(returnErr))
}
}(ctx)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing context.WithoutCancel causes network slot return failures

Medium Severity

The network slot cleanup goroutine passes ctx directly instead of context.WithoutCancel(ctx). During cleanup, the parent context is often already canceled. The old code in getNetworkSlotAsync (line 1036) explicitly used context.WithoutCancel(ctx) to ensure networkPool.Return completes even when the context is canceled. Without this protection, the return operation may fail with a context cancellation error, potentially leaking network slots from the pool.

Fix in Cursor Fix in Web

@dobrac
dobrac merged commit e39b0da into feat/memory-prefetching Jan 12, 2026
26 checks passed
@dobrac
dobrac deleted the promise-poc branch January 12, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants