| 1 | package plugin |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "sync/atomic" |
| 6 | ) |
| 7 | |
| 8 | // maxCodeGraphInstances caps how many CodeGraph indexer subprocesses may run |
| 9 | // concurrently across the whole process. Each tab/session boots its own plugin |
| 10 | // Host (desktop/tabs.go, boot.go), so opening many tabs on large projects used |
| 11 | // to spawn one full-tree indexer per tab — exhausting file descriptors and |
| 12 | // freezing the machine (#4361, #2992, #3797). A hard cap bounds the blast |
| 13 | // radius: once reached, additional CodeGraph instances refuse to start and the |
| 14 | // agent degrades to grep/glob, which is the documented manual workaround |
| 15 | // (`[codegraph] enabled = false`) applied automatically instead of a crash. |
| 16 | const maxCodeGraphInstances = 4 |
| 17 | |
| 18 | var liveCodeGraphInstances atomic.Int32 |
| 19 | |
| 20 | // acquireCodeGraphSlot reserves one of the bounded CodeGraph instance slots. |
| 21 | // It returns a release func (idempotent) and an error when the cap is reached. |
| 22 | // The cap only governs CodeGraph; every other stdio plugin is unaffected. |
| 23 | func acquireCodeGraphSlot() (func(), error) { |
| 24 | n := liveCodeGraphInstances.Add(1) |
| 25 | if n > maxCodeGraphInstances { |
| 26 | liveCodeGraphInstances.Add(-1) |
| 27 | return nil, fmt.Errorf("codegraph: %d instances already running (cap %d); not starting another to avoid file-descriptor exhaustion — close some tabs/sessions or set codegraph off for this one", n-1, maxCodeGraphInstances) |
| 28 | } |
| 29 | var released atomic.Bool |
| 30 | return func() { |
| 31 | if released.CompareAndSwap(false, true) { |
| 32 | liveCodeGraphInstances.Add(-1) |
| 33 | } |
| 34 | }, nil |
| 35 | } |
| 36 |