Skip to content

fix: collect adb errors when launching MjpegServer or AvcServer#168

Open
gmegidish wants to merge 1 commit intomainfrom
fix-collect-adb-appprocess-errors
Open

fix: collect adb errors when launching MjpegServer or AvcServer#168
gmegidish wants to merge 1 commit intomainfrom
fix-collect-adb-appprocess-errors

Conversation

@gmegidish
Copy link
Member

@gmegidish gmegidish commented Feb 9, 2026

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Enhanced Android screen capture with improved error detection during server startup and clearer error messaging.
    • Improved streaming reliability with better process monitoring and graceful termination handling.

@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

Walkthrough

Adds server startup monitoring and output streaming capabilities to Android screen capture functionality. Introduces helper functions for stderr/stdout parsing, error detection, and asynchronous startup monitoring. Reworks StartScreenCapture to establish pipes, spawn monitoring goroutines, wait for readiness, and stream output.

Changes

Cohort / File(s) Summary
Server Startup Monitoring and Error Handling
devices/android.go
Introduces helper functions (containsServerError, parseServerError, monitorServerStartup, waitForServerStartup) for detecting startup failures, parsing error messages, and monitoring stderr asynchronously via channels. Adds serverStartupTimeout constant and error-detection logic.
Output Streaming and Data Handling
devices/android.go
Adds streamServerOutput function to asynchronously read stdout and feed data to callback while validating server readiness. Introduces buffering constants and io import for pipe-based communication.
StartScreenCapture Refactor
devices/android.go
Restructures command building with multi-line format, establishes stdout/stderr pipes, spawns startup-monitoring goroutine, waits for startup completion, and implements new streaming workflow with process termination handling.

Possibly related PRs

  • feat: support android avc #112: Modifies devices/android.go with Android screen-capture startup/streaming logic including stdout/stderr monitoring and OnData callback streaming with server-class handling.
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: improving error collection when launching MjpegServer or AvcServer on Android devices. The summary confirms the addition of error-detection helpers and startup error monitoring logic.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-collect-adb-appprocess-errors

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@devices/android.go`:
- Around line 915-933: In monitorServerStartup, stderr is read in chunks so
containsServerError can miss tokens split across reads; change the loop to
accumulate reads into a growing buffer (e.g., bytes.Buffer or a
bufio.Reader/Scanner that emits full lines) and run containsServerError against
the accumulated data (or each complete line) so multi-chunk patterns like
"ClassNotFoundException" are detected; keep using parseServerError and errorChan
for reporting, and add a reasonable cap or truncation to the buffer (to avoid
unbounded growth) using the existing stderrBufferSize as a guide.
- Around line 946-974: The streamServerOutput function (and similarly
waitForServerStartup) currently kills the process but never calls cmd.Wait(),
leaking OS resources; update streamServerOutput to call cmd.Wait() after any
call to cmd.Process.Kill() and also call cmd.Wait() on the normal exit path
(after the read loop) to reap the child, and ensure waitForServerStartup
likewise calls cmd.Wait() after killing the process or when the process exits;
ignore or log the returned error from cmd.Wait() as appropriate but always
invoke it to prevent zombies.
🧹 Nitpick comments (2)
devices/android.go (2)

885-900: The "Error:" pattern is broad and may cause false positives.

The other patterns are specific (ClassNotFoundException, SIGABRT, etc.), but "Error:" could match benign log output. Consider using a more specific pattern or removing it in favor of the already-specific patterns handled in parseServerError.


946-964: First-read error check on stdout duplicates stderr monitoring logic.

streamServerOutput checks the first stdout read for server error patterns. This means server errors must appear on stdout (not just stderr) for this check to trigger. If the server writes errors only to stderr, this check is dead code. If errors appear on both stdout and stderr, there's a race between this check and monitorServerStartup. Consider clarifying which stream carries error messages and relying on a single detection path.

Comment on lines +915 to +933
func (d *AndroidDevice) monitorServerStartup(stderr io.Reader, serverClass string, errorChan chan<- error) {
buffer := make([]byte, stderrBufferSize)

for {
n, err := stderr.Read(buffer)
if n > 0 {
msg := string(buffer[:n])
utils.Verbose("stderr: %s", msg)

if containsServerError(msg) {
errorChan <- d.parseServerError(msg, serverClass)
return
}
}
if err != nil {
return
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Stderr error detection can miss errors split across read boundaries.

stderr.Read(buffer) may return partial data. If an error string like "ClassNotFoundException" is split across two reads, containsServerError won't match it on either chunk. Consider accumulating stderr into a bytes.Buffer (or using bufio.Scanner with line-based reading) so pattern matching operates on the full output.

Proposed fix using accumulated buffer
 func (d *AndroidDevice) monitorServerStartup(stderr io.Reader, serverClass string, errorChan chan<- error) {
-	buffer := make([]byte, stderrBufferSize)
+	var accumulated strings.Builder
+	readBuf := make([]byte, stderrBufferSize)
 
 	for {
-		n, err := stderr.Read(buffer)
+		n, err := stderr.Read(readBuf)
 		if n > 0 {
-			msg := string(buffer[:n])
+			accumulated.Write(readBuf[:n])
+			msg := accumulated.String()
 			utils.Verbose("stderr: %s", msg)
 
 			if containsServerError(msg) {
 				errorChan <- d.parseServerError(msg, serverClass)
 				return
 			}
 		}
 		if err != nil {
 			return
 		}
 	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (d *AndroidDevice) monitorServerStartup(stderr io.Reader, serverClass string, errorChan chan<- error) {
buffer := make([]byte, stderrBufferSize)
for {
n, err := stderr.Read(buffer)
if n > 0 {
msg := string(buffer[:n])
utils.Verbose("stderr: %s", msg)
if containsServerError(msg) {
errorChan <- d.parseServerError(msg, serverClass)
return
}
}
if err != nil {
return
}
}
}
func (d *AndroidDevice) monitorServerStartup(stderr io.Reader, serverClass string, errorChan chan<- error) {
var accumulated strings.Builder
readBuf := make([]byte, stderrBufferSize)
for {
n, err := stderr.Read(readBuf)
if n > 0 {
accumulated.Write(readBuf[:n])
msg := accumulated.String()
utils.Verbose("stderr: %s", msg)
if containsServerError(msg) {
errorChan <- d.parseServerError(msg, serverClass)
return
}
}
if err != nil {
return
}
}
}
🤖 Prompt for AI Agents
In `@devices/android.go` around lines 915 - 933, In monitorServerStartup, stderr
is read in chunks so containsServerError can miss tokens split across reads;
change the loop to accumulate reads into a growing buffer (e.g., bytes.Buffer or
a bufio.Reader/Scanner that emits full lines) and run containsServerError
against the accumulated data (or each complete line) so multi-chunk patterns
like "ClassNotFoundException" are detected; keep using parseServerError and
errorChan for reporting, and add a reasonable cap or truncation to the buffer
(to avoid unbounded growth) using the existing stderrBufferSize as a guide.

Comment on lines +946 to +974
func (d *AndroidDevice) streamServerOutput(stdout io.Reader, cmd *exec.Cmd, config ScreenCaptureConfig) error {
buffer := make([]byte, stdoutBufferSize)
firstRead := true

for {
n, err := stdout.Read(buffer)
if err != nil {
break
}

if n > 0 {
if firstRead {
firstRead = false
output := string(buffer[:n])
if containsServerError(output) {
_ = cmd.Process.Kill()
return fmt.Errorf("server error: %s", strings.TrimSpace(output))
}
}

if !config.OnData(buffer[:n]) {
break
}
}
}

_ = cmd.Process.Kill()
return nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

cmd.Wait() is never called — process resources will leak.

After cmd.Process.Kill() (or when the process exits naturally), cmd.Wait() must be called to release associated OS resources (file descriptors, process table entry). Without it, the process becomes a zombie. This applies to both the normal exit path and the error path in waitForServerStartup.

Proposed fix
 func (d *AndroidDevice) streamServerOutput(stdout io.Reader, cmd *exec.Cmd, config ScreenCaptureConfig) error {
 	buffer := make([]byte, stdoutBufferSize)
 	firstRead := true
 
 	for {
 		n, err := stdout.Read(buffer)
 		if err != nil {
 			break
 		}
 
 		if n > 0 {
 			if firstRead {
 				firstRead = false
 				output := string(buffer[:n])
 				if containsServerError(output) {
 					_ = cmd.Process.Kill()
+					_ = cmd.Wait()
 					return fmt.Errorf("server error: %s", strings.TrimSpace(output))
 				}
 			}
 
 			if !config.OnData(buffer[:n]) {
 				break
 			}
 		}
 	}
 
 	_ = cmd.Process.Kill()
+	_ = cmd.Wait()
 	return nil
 }

Also add _ = cmd.Wait() in waitForServerStartup after the Kill:

 func (d *AndroidDevice) waitForServerStartup(errorChan <-chan error, cmd *exec.Cmd) error {
 	time.Sleep(serverStartupTimeout)
 	select {
 	case startupErr := <-errorChan:
 		_ = cmd.Process.Kill()
+		_ = cmd.Wait()
 		return startupErr
 	default:
 		return nil
 	}
 }
🤖 Prompt for AI Agents
In `@devices/android.go` around lines 946 - 974, The streamServerOutput function
(and similarly waitForServerStartup) currently kills the process but never calls
cmd.Wait(), leaking OS resources; update streamServerOutput to call cmd.Wait()
after any call to cmd.Process.Kill() and also call cmd.Wait() on the normal exit
path (after the read loop) to reap the child, and ensure waitForServerStartup
likewise calls cmd.Wait() after killing the process or when the process exits;
ignore or log the returned error from cmd.Wait() as appropriate but always
invoke it to prevent zombies.

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.

1 participant