Vendor opencode source for docker build
This commit is contained in:
145
opencode/specs/03-request-throttling.md
Normal file
145
opencode/specs/03-request-throttling.md
Normal file
@@ -0,0 +1,145 @@
|
||||
## Request throttling
|
||||
|
||||
Debounce and cancel high-frequency server calls
|
||||
|
||||
---
|
||||
|
||||
### Summary
|
||||
|
||||
Some user interactions trigger bursts of server requests that can overlap and return out of order. We’ll debounce frequent triggers and cancel in-flight requests (or ignore stale results) for file search and LSP refresh.
|
||||
|
||||
---
|
||||
|
||||
### Goals
|
||||
|
||||
- Reduce redundant calls from file search and LSP refresh
|
||||
- Prevent stale responses from overwriting newer UI state
|
||||
- Preserve responsive typing and scrolling during high activity
|
||||
|
||||
---
|
||||
|
||||
### Non-goals
|
||||
|
||||
- Changing server-side behavior or adding new endpoints
|
||||
- Implementing global request queues for all SDK calls
|
||||
- Persisting search results across reloads
|
||||
|
||||
---
|
||||
|
||||
### Current state
|
||||
|
||||
- File search calls `sdk.client.find.files` via `files.searchFilesAndDirectories`.
|
||||
- LSP refresh is triggered frequently (exact call sites vary, but the refresh behavior is high-frequency).
|
||||
- Large UI modules involved include `packages/app/src/pages/layout.tsx` and `packages/app/src/components/prompt-input.tsx`.
|
||||
|
||||
---
|
||||
|
||||
### Proposed approach
|
||||
|
||||
- Add a small request coordinator utility:
|
||||
- debounced triggering (leading/trailing configurable)
|
||||
- cancellation via `AbortController` when supported
|
||||
- stale-result protection via monotonic request ids when abort is not supported
|
||||
- Integrate coordinator into:
|
||||
- `files.searchFilesAndDirectories` (wrap `sdk.client.find.files`)
|
||||
- LSP refresh call path (wrap refresh invocation and ensure only latest applies)
|
||||
|
||||
---
|
||||
|
||||
### Phased implementation steps
|
||||
|
||||
1. Add a debounced + cancellable helper
|
||||
|
||||
- Create `packages/app/src/utils/requests.ts` with:
|
||||
- `createDebouncedAsync(fn, delayMs)`
|
||||
- `createLatestOnlyAsync(fn)` that drops stale responses
|
||||
- Prefer explicit, readable primitives over a single complex abstraction.
|
||||
|
||||
Sketch:
|
||||
|
||||
```ts
|
||||
function createLatestOnlyAsync<TArgs extends unknown[], TResult>(
|
||||
fn: (args: { input: TArgs; signal?: AbortSignal }) => Promise<TResult>,
|
||||
) {
|
||||
let id = 0
|
||||
let controller: AbortController | undefined
|
||||
|
||||
return async (...input: TArgs) => {
|
||||
id += 1
|
||||
const current = id
|
||||
controller?.abort()
|
||||
controller = new AbortController()
|
||||
|
||||
const result = await fn({ input, signal: controller.signal })
|
||||
if (current !== id) return
|
||||
return result
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Apply to file search
|
||||
|
||||
- Update `files.searchFilesAndDirectories` to:
|
||||
- debounce input changes (e.g. 150–300 ms)
|
||||
- abort prior request when a new query begins
|
||||
- ignore results if they are stale
|
||||
- Ensure “empty query” is handled locally without calling the server.
|
||||
|
||||
3. Apply to LSP refresh
|
||||
|
||||
- Identify the refresh trigger points used during typing and file switching.
|
||||
- Add:
|
||||
- debounce for rapid triggers (e.g. 250–500 ms)
|
||||
- cancellation for in-flight refresh if supported
|
||||
- last-write-wins behavior for applying diagnostics/results
|
||||
|
||||
4. Add feature flags and metrics
|
||||
|
||||
- Add flags:
|
||||
- `requests.debounce.fileSearch`
|
||||
- `requests.latestOnly.lspRefresh`
|
||||
- Add simple dev-only counters for “requests started / aborted / applied”.
|
||||
|
||||
---
|
||||
|
||||
### Data migration / backward compatibility
|
||||
|
||||
- No persisted data changes.
|
||||
- Behavior is compatible as long as UI state updates only when the “latest” request resolves.
|
||||
|
||||
---
|
||||
|
||||
### Risk + mitigations
|
||||
|
||||
- Risk: aggressive debounce makes UI feel laggy.
|
||||
- Mitigation: keep delays small and tune separately for search vs refresh.
|
||||
- Risk: aborting requests may surface as errors in logs.
|
||||
- Mitigation: treat `AbortError` as expected and do not log it as a failure.
|
||||
- Risk: SDK method may not accept `AbortSignal`.
|
||||
- Mitigation: use request-id stale protection even without true cancellation.
|
||||
|
||||
---
|
||||
|
||||
### Validation plan
|
||||
|
||||
- Manual scenarios:
|
||||
- type quickly in file search and confirm requests collapse and results stay correct
|
||||
- trigger LSP refresh repeatedly and confirm diagnostics do not flicker backward
|
||||
- Add a small unit test for latest-only behavior (stale results are ignored).
|
||||
|
||||
---
|
||||
|
||||
### Rollout plan
|
||||
|
||||
- Ship helpers behind flags default off.
|
||||
- Enable file search debounce first (high impact, easy to validate).
|
||||
- Enable LSP latest-only next, then add cancellation if SDK supports signals.
|
||||
- Keep a quick rollback by disabling the flags.
|
||||
|
||||
---
|
||||
|
||||
### Open questions
|
||||
|
||||
- Does `sdk.client.find.files` accept an abort signal today, or do we need stale-result protection only?
|
||||
- Where is LSP refresh initiated, and does it have a single chokepoint we can wrap?
|
||||
- What debounce values feel best for common repos and slower machines?
|
||||
Reference in New Issue
Block a user