Conversation
|
| let read = self.next_inner(); | ||
| match timeout { | ||
| Some(timeout) => tokio::time::timeout(timeout, read) | ||
| .await | ||
| .map_err(|_| AdapterError::Timeout)?, | ||
| None => read.await, |
There was a problem hiding this comment.
Timed-out Frames Desynchronize Stream
If a handshake or request times out after only part of a JSONL frame arrives, the timeout drops the accumulated prefix while leaving stdout positioned after it. A later operation then reads only the remaining suffix and fails protocol decoding, making the provider unusable after a recoverable timeout. Preserve partial-frame state across calls or invalidate and restart the transport when a partial read times out.
Artifacts
- The uploaded source contains the focused Tokio test and real Python subprocess fixture that writes one JSONL frame in two parts, ending with the asserted desynchronization.
- The captured parent-source inspection shows the earlier transport used a single `read_until` call and had no `next_with_timeout` path, establishing the relevant before implementation.
- The executed focused test records a timeout after the emitted prefix and a malformed-protocol error when the subsequent call reads the remaining suffix, confirming the defect.
- The executed format check and complete subprocess library test suite passed all four tests, including the focused regression, confirming the harness compiles and runs with the changed transport.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-subprocess/src/lib.rs
Line: 150-155
Comment:
**Timed-out Frames Desynchronize Stream**
If a handshake or request times out after only part of a JSONL frame arrives, the timeout drops the accumulated prefix while leaving stdout positioned after it. A later operation then reads only the remaining suffix and fails protocol decoding, making the provider unusable after a recoverable timeout. Preserve partial-frame state across calls or invalidate and restart the transport when a partial read times out.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| match &frame { | ||
| Frame::Event { request_id, .. } if request_id == &id => frames.push(frame), | ||
| Frame::Response { | ||
| id: response_id, .. | ||
| } if response_id == &id => { | ||
| frames.push(frame); | ||
| return Ok(frames); | ||
| } | ||
| Frame::HelloOk { .. } => { | ||
| return Err(AdapterError::UnexpectedFrame( | ||
| "received hello_ok after handshake".into(), | ||
| )); | ||
| } | ||
| Frame::Event { request_id, .. } => { | ||
| return Err(AdapterError::UnexpectedFrame(format!( | ||
| "event for another request while waiting for {id}: {request_id}" | ||
| ))); | ||
| } | ||
| other => { | ||
| return Err(AdapterError::UnexpectedFrame(format!( | ||
| "unexpected frame while waiting for {id}: {other:?}" | ||
| ))); |
There was a problem hiding this comment.
Concurrent Requests Consume Frames
Two request_and_collect calls independently consume the same stdout stream. When provider frames are interleaved, each collector can remove the other request's event and return UnexpectedFrame, so both otherwise valid requests fail. Serialize complete exchanges or route frames through one reader to per-request queues.
Knowledge Base Used: Provider selection and runtime adapters
Artifacts
- The authored Rust integration test starts a Python JSONL fixture, validates a single-request control, and reproduces interleaved two-request frame handling; the test encodes the confirmed failure.
- Ran the single-request control from `/home/user/repo`; it received its matching event and response successfully, establishing normal non-interleaved behavior.
- Ran the two-request correlated-frame repro from `/home/user/repo`; request A consumed B's event and request B consumed A's event, confirming the defect.
- Ran both focused integration tests together from `/home/user/repo`; the control succeeded and the asserted concurrent failure reproduced, confirming the finding.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-subprocess/src/lib.rs
Line: 238-259
Comment:
**Concurrent Requests Consume Frames**
Two `request_and_collect` calls independently consume the same stdout stream. When provider frames are interleaved, each collector can remove the other request's event and return `UnexpectedFrame`, so both otherwise valid requests fail. Serialize complete exchanges or route frames through one reader to per-request queues.
**Knowledge Base Used:** [Provider selection and runtime adapters](https://app.greptile.com/solo-systems/-/custom-context/knowledge-base/1jehuang/jcode/-/docs/provider-selection-and-runtime-adapters.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| let id = id.into(); | ||
| self.request(id.clone(), method, params).await?; | ||
| let deadline = tokio::time::Instant::now() + self.config.request_timeout; |
There was a problem hiding this comment.
Writes Bypass Request Deadline
request_and_collect starts its deadline only after sending the request. If a provider stops reading stdin, a sufficiently large request fills the pipe and write_all blocks indefinitely, so the configured request timeout never fires and the caller remains stuck. Apply the same deadline to writing and flushing the request.
Artifacts
- This authored executable integration test starts a Python provider that reads only the hello frame, then compares a small request with an 8 MiB request under a 50 ms configured timeout, demonstrating that the write is outside the configured deadline.
- The executed focused control test returned `Err(Timeout)` after 51 ms, showing the configured deadline applies once the small request write completes.
- The executed oversized-request test returned only the external watchdog's `Err(Elapsed(()))` after 510 ms despite a 50 ms configured request timeout, confirming the blocked write is not timed.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-subprocess/src/lib.rs
Line: 228-230
Comment:
**Writes Bypass Request Deadline**
`request_and_collect` starts its deadline only after sending the request. If a provider stops reading stdin, a sufficiently large request fills the pipe and `write_all` blocks indefinitely, so the configured request timeout never fires and the caller remains stuck. Apply the same deadline to writing and flushing the request.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Summary
Add a versioned subprocess provider protocol so user-maintained external providers can survive jcode application upgrades without being embedded into the main binary.
Changes
The follow-up registry and GitHub
/pluginlifecycle work is intentionally separate from this protocol foundation.Validation
upstream/mastercompleted successfully.jcode-provider-protocoltests passed.jcode-provider-subprocesstests passed.Closes #745