{"record":{"id":"4b84b7d7ce698d35","repo":"gastownhall/beads","slug":"iter-dependents-acquire-conn-w","errorCode":null,"errorMessage":"iter dependents: acquire conn: %w","messagePattern":"iter dependents: acquire conn: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/iter_dependents.go","lineNumber":94,"sourceCode":"// GetDependenciesWithMetadata (which resolves targets across both `issues`\n// and `wisps`) rather than a streaming join, because a dependency's target\n// table cannot be determined from the edge table alone. There is no streaming\n// caller for this direction today; revisit if one appears.\nfunc (s *DoltStore) IterDependenciesWithMetadata(ctx context.Context, issueID string) (storage.Iter[types.IssueWithDependencyMetadata], error) {\n\tdeps, err := s.GetDependenciesWithMetadata(ctx, issueID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn storage.NewSliceIter(deps), nil\n}\n\nfunc (s *DoltStore) iterIssuesWithDepType(ctx context.Context, q string, args ...any) (storage.Iter[types.IssueWithDependencyMetadata], error) {\n\tif s.closed.Load() {\n\t\treturn nil, ErrStoreClosed\n\t}\n\tconn, err := s.db.Conn(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"iter dependents: acquire conn: %w\", err)\n\t}\n\trows, err := conn.QueryContext(ctx, q, args...)\n\tif err != nil {\n\t\t_ = conn.Close()\n\t\treturn nil, fmt.Errorf(\"iter dependents: query: %w\", err)\n\t}\n\treturn &doltDependentsIter{s: s, conn: conn, rows: rows}, nil\n}\n\nfunc (it *doltDependentsIter) Next(ctx context.Context) bool {\n\tif it.err != nil || it.closed {\n\t\treturn false\n\t}\n\tif err := ctx.Err(); err != nil {\n\t\tit.err = err\n\t\treturn false\n\t}\n\tif !it.rows.Next() {","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/iter_dependents.go#L76-L112","documentation":"iterIssuesWithDepType acquires a dedicated connection from the pool (s.db.Conn) before streaming dependents rows, because the iterator owns the connection for its lifetime. This error wraps failure to obtain that connection: the pool is exhausted, the context was cancelled/timed out while waiting, or the DB handle is unhealthy. No query has run yet at this point.","triggerScenarios":"Calling IterDependentsWithMetadata (which routes to iterIssuesWithDepType) when db.Conn(ctx) fails: all pool connections busy for longer than the context deadline, ctx cancelled by the caller, or the store's underlying Dolt handle is broken. Note ErrStoreClosed is returned separately before this.","commonSituations":"Long-running dependent iteration holding many connections in a loop that opens iterators without closing them; many concurrent bd commands against a small connection pool; slow/hung Dolt server making Conn block until the deadline.","solutions":["Ensure every returned iterator is fully drained or closed — leaked doltDependentsIter connections exhaust the pool (this is the most common root cause).","Increase the SQL pool size (SetMaxOpenConns) if workloads legitimately need many concurrent iterators.","Check the wrapped cause: context deadline exceeded means contention; raise the timeout or reduce concurrency.","Restart the Dolt backend / reopen the store if the pool handle itself is unhealthy."],"exampleFix":"// before\nit, err := store.IterDependentsWithMetadata(ctx, issueID)\nif err != nil { return err }\nfor it.Next(ctx) { ... } // iterator never closed -> conn leak\n// after\nit, err := store.IterDependentsWithMetadata(ctx, issueID)\nif err != nil { return err }\ndefer it.Close() // releases the pooled conn\nfor it.Next(ctx) { ... }","handlingStrategy":"try-catch","validationCode":"// avoid opening new iterators while many are unclosed\ncctx, cancel := context.WithTimeout(ctx, 10*time.Second)\ndefer cancel()\n_ = cctx // pass cctx to IterDependentsWithMetadata to bound pool wait time","typeGuard":"func isConnAcquireErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"acquire conn\")\n}","tryCatchPattern":"it, err := store.IterDependentsWithMetadata(ctx, issueID)\nif err != nil {\n    if isConnAcquireErr(err) {\n        // pool exhausted or timeout: close leaked iterators, then retry\n        return fmt.Errorf(\"could not acquire DB connection (check for unclosed iterators): %w\", err)\n    }\n    return err\n}\ndefer it.Close()","preventionTips":["Always defer Close() (or drain) every iterator returned by IterDependentsWithMetadata","Use context timeouts so pool waits fail fast instead of hanging","Increase MaxOpenConns if you legitimately iterate many dependency graphs concurrently","Monitor for 'acquire conn' failures as an early sign of connection leaks"],"tags":["connection-pool","database","iterator","dolt"],"backgroundTag":"connection-acquisition-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}