Skip to content

fix DevWorkspacePods.exec to fail on non-zero exit, timeout, and cancel cleanly (CRW-12138) - #356

Open
adietish wants to merge 1 commit into
redhat-developer:mainfrom
adietish:improve_pod_exec
Open

fix DevWorkspacePods.exec to fail on non-zero exit, timeout, and cancel cleanly (CRW-12138)#356
adietish wants to merge 1 commit into
redhat-developer:mainfrom
adietish:improve_pod_exec

Conversation

@adietish

@adietish adietish commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Decision order for exec’s result:

  1. Timeout (Int.MAX_VALUE) → IOException (“timed out”). Stdout discarded.
  2. Non-zero exit → IOException (“exit code N”). Stdout discarded.
  3. Exit 0 + stream read error (stdout/stderr closed with IOException before clean EOF) → IOException (“stream closed before output was fully read”). Partial buffer is not returned.
  4. Exit 0 + clean EOF → return stdout as String (may be empty "").

Exit code wins for failure/timeout. Partial output is only a problem on the success path, and that path now fails if a stream died mid-read instead of handing back truncated text.

Empty stdout with exit 0 and clean EOF is still a normal success (""); callers like RemoteIDEServer map that to empty status.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 904fc940-41fb-4714-bef2-705b3492aa35

📥 Commits

Reviewing files that changed from the base of the PR and between f5b4829 and 456ddfc.

📒 Files selected for processing (4)
  • src/main/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePods.kt
  • src/main/kotlin/com/redhat/devtools/gateway/openshift/PodExecSession.kt
  • src/main/kotlin/com/redhat/devtools/gateway/openshift/Utils.kt
  • src/test/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePodsTest.kt
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/kotlin/com/redhat/devtools/gateway/openshift/Utils.kt
  • src/main/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePods.kt
  • src/main/kotlin/com/redhat/devtools/gateway/openshift/PodExecSession.kt
  • src/test/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePodsTest.kt

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved command execution in OpenShift pods, including clearer handling of timeouts, cancellations, missing pod details, and nonzero exit statuses.
    • Error messages now include relevant standard error output when commands fail.
    • Improved handling of command output and stream-read failures.
    • Improved cleanup of connections and streams after successful, cancelled, or failed executions.
  • Tests

    • Expanded coverage for command failures, cancellations, timeouts, output handling, stream errors, and resource cleanup.

Walkthrough

DevWorkspacePods.exec now delegates pod command execution to PodExecSession. The session manages Kubernetes execution, stream reading, cancellation, timeouts, exit failures, output collection, and resource cleanup. Tests cover execution outcomes and cleanup behavior.

Changes

Pod command execution

Layer / File(s) Summary
Execution session contract and Kubernetes wiring
src/main/kotlin/com/redhat/devtools/gateway/openshift/PodExecSession.kt, src/main/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePods.kt
PodExecSession validates pod metadata, clones the API client, creates execution state, and starts non-TTY container execution with stdout and stderr callbacks.
Execution lifecycle, cancellation, and results
src/main/kotlin/com/redhat/devtools/gateway/openshift/PodExecSession.kt
The session reads output streams, polls cancellation, handles timeouts and nonzero exit codes, returns stdout on success, and shuts down HTTP resources.
Delegation, stream cleanup, and execution tests
src/main/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePods.kt, src/main/kotlin/com/redhat/devtools/gateway/openshift/Utils.kt, src/test/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePodsTest.kt
DevWorkspacePods.exec delegates to the session. closeQuietly handles resource closure. Tests cover startup failures, cancellation, cleanup, stderr propagation, timeouts, nonzero exits, successful output, and stream-read failures.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DevWorkspacePods
  participant PodExecSession
  participant KubernetesAPI
  participant OutputStreams
  DevWorkspacePods->>PodExecSession: execute command with timeout and cancellation callback
  PodExecSession->>KubernetesAPI: start container execution
  KubernetesAPI-->>OutputStreams: provide stdout and stderr
  PodExecSession->>OutputStreams: read and collect output
  PodExecSession-->>DevWorkspacePods: return stdout or raise execution failure
Loading

Suggested reviewers: vrubezhny, azatsarynnyy

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the fixes for non-zero exits, timeouts, and cancellation in DevWorkspacePods.exec.
Description check ✅ Passed The description accurately explains the execution result order, error handling, and stdout behavior covered by the changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/test/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePodsTest.kt (2)

1093-1098: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for empty stdout with exit code 0.

The PR states that empty output with exit code 0 must be treated as a success. The added tests cover non-empty stdout, non-zero exit, timeout, and stderr, but no test asserts that empty stdout with exit code 0 returns an empty string instead of failing. Add that case to lock the stated behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePodsTest.kt`
around lines 1093 - 1098, Add a test near the existing pods.exec assertions in
DevWorkspacePodsTest that executes a command producing empty stdout with exit
code 0, and assert that exec returns an empty string without failing. Keep the
test focused on the successful empty-output behavior.

905-1099: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated ContainerAwareExec stubbing into a helper.

The four new tests repeat the same setup: mockkConstructor(ContainerAwareExec::class), a fakeHandle, an answers block that casts args[4]/args[5] and drives onOpen/onClosed, the ApiClientUtils.cloneForExec stub, and the testPod literal. The same block also appears in the earlier tests at Lines 556-740. Extract one helper that takes stdout, stderr, and the exit code. The tests then state only the outcome under test, and future signature changes to containerAwareExec need one update instead of eight.

♻️ Sketch of the helper
private fun stubExec(
    stdout: String = "",
    stderr: String = "",
    exitCode: Int? = null,
    error: Throwable? = null,
    afterOpen: (() -> Unit)? = null
): ApiClient {
    mockkConstructor(ContainerAwareExec::class)
    val handle = ContainerAwareExec.ExecHandle(
        future = CompletableFuture.completedFuture(0),
        job = mockk(relaxed = true)
    )
    every {
        anyConstructed<ContainerAwareExec>().containerAwareExec(
            any(), any(), any(), any(), any(), any(), any(), any(), any()
        )
    } answers {
        `@Suppress`("UNCHECKED_CAST")
        val onOpen = it.invocation.args[4] as Consumer<IOTrio>
        `@Suppress`("UNCHECKED_CAST")
        val onClosed = it.invocation.args[5] as BiConsumer<Int, IOTrio>
        `@Suppress`("UNCHECKED_CAST")
        val onError = it.invocation.args[6] as BiConsumer<Throwable, IOTrio>
        val io = IOTrio().apply {
            this.stdout = ByteArrayInputStream(stdout.toByteArray())
            this.stderr = ByteArrayInputStream(stderr.toByteArray())
            this.stdin = mockk(relaxed = true)
        }
        onOpen.accept(io)
        afterOpen?.invoke()
        error?.let { e -> onError.accept(e, io) }
        exitCode?.let { code -> onClosed.accept(code, io) }
        handle
    }
    mockkObject(ApiClientUtils)
    val execClient = mockk<ApiClient>(relaxed = true)
    every { ApiClientUtils.cloneForExec(any()) } returns execClient
    return execClient
}

private fun testPod(name: String = "test-pod", ns: String = "test-ns") =
    V1Pod().apply { metadata = V1ObjectMeta().apply { this.name = name; namespace = ns } }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePodsTest.kt`
around lines 905 - 1099, Extract the duplicated ContainerAwareExec and
ApiClientUtils setup from the four new tests and the earlier exec tests into
shared test helpers, such as stubExec and testPod. Make stubExec accept stdout,
stderr, and exitCode, centralize the callback wiring and handle creation, and
update each test to configure only its scenario and assertion; preserve any
existing error or after-open behavior required by earlier tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePods.kt`:
- Around line 190-192: Update closeStreams to evaluate both forwardResult stream
getters inside the existing failure-tolerant handling, such as runCatching, so
IllegalArgumentException or connection failures are suppressed during cleanup.
Preserve quiet closing for successfully retrieved streams and ensure cleanup
cannot replace the original port-forward failure or interrupt retries.

In `@src/main/kotlin/com/redhat/devtools/gateway/openshift/PodExecSession.kt`:
- Around line 155-170: Update PodExecSession.readStream to read the input in
buffered blocks and decode the bytes explicitly as UTF-8 before appending to
output. Preserve cancellation checks and IOException handling, while avoiding
per-byte reads and ensuring multi-byte UTF-8 output is decoded correctly.

---

Nitpick comments:
In
`@src/test/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePodsTest.kt`:
- Around line 1093-1098: Add a test near the existing pods.exec assertions in
DevWorkspacePodsTest that executes a command producing empty stdout with exit
code 0, and assert that exec returns an empty string without failing. Keep the
test focused on the successful empty-output behavior.
- Around line 905-1099: Extract the duplicated ContainerAwareExec and
ApiClientUtils setup from the four new tests and the earlier exec tests into
shared test helpers, such as stubExec and testPod. Make stubExec accept stdout,
stderr, and exitCode, centralize the callback wiring and handle creation, and
update each test to configure only its scenario and assertion; preserve any
existing error or after-open behavior required by earlier tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 93a9e83b-5a57-419f-b421-99f4470546d0

📥 Commits

Reviewing files that changed from the base of the PR and between b6421cb and f5b4829.

📒 Files selected for processing (4)
  • src/main/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePods.kt
  • src/main/kotlin/com/redhat/devtools/gateway/openshift/PodExecSession.kt
  • src/main/kotlin/com/redhat/devtools/gateway/openshift/Utils.kt
  • src/test/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePodsTest.kt

Comment thread src/main/kotlin/com/redhat/devtools/gateway/openshift/DevWorkspacePods.kt Outdated
@codecov-commenter

codecov-commenter commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.28125% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 34.63%. Comparing base (71098f6) to head (456ddfc).
⚠️ Report is 414 commits behind head on main.

Files with missing lines Patch % Lines
...edhat/devtools/gateway/openshift/PodExecSession.kt 61.46% 31 Missing and 11 partials ⚠️
...hat/devtools/gateway/openshift/DevWorkspacePods.kt 72.22% 0 Missing and 5 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##            main     #356       +/-   ##
==========================================
+ Coverage   0.00%   34.63%   +34.63%     
==========================================
  Files          4      114      +110     
  Lines         26     4862     +4836     
  Branches       0      935      +935     
==========================================
+ Hits           0     1684     +1684     
- Misses        26     2939     +2913     
- Partials       0      239      +239     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@adietish
adietish force-pushed the improve_pod_exec branch 2 times, most recently from 3511d41 to 7720add Compare July 31, 2026 11:22
@adietish adietish changed the title Improve pod exec fix DevWorkspacePods.exec to fail on non-zero exit, timeout, and cancel cleanly (CRW-12138) Jul 31, 2026
…el cleanly (CRW-12138)

- Treat Int.MAX_VALUE and non-zero exit as IOException (with truncated stderr)
- close IOTrio streams on cancel
- use a single joiner gated by streamsReady so pre-open errors and mid-stream failures
  complete once without racing stream readers.
- extracted PodExecSession

Signed-off-by: Andre Dietisheim <adietish@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

@vrubezhny vrubezhny left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Small questions inline...

The biggest one is that I couldn't connect in modal context - when running it as Run Plugin (IDEA), then File->Remote Development...

Image

More is that the Cluster Connection wizard page wasn't showing any clusters from my Kube config... I believe the connection failure is of the same kind.

When I ran it as Run Plugin (which is non-modal) - it connected successfully

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