Skip to content

Fix pipe deadlock: drain stdout/stderr concurrently in exec()#596

Merged
kishikawakatsumi merged 2 commits into
mainfrom
fix
Apr 8, 2026
Merged

Fix pipe deadlock: drain stdout/stderr concurrently in exec()#596
kishikawakatsumi merged 2 commits into
mainfrom
fix

Conversation

@kishikawakatsumi
Copy link
Copy Markdown
Member

@kishikawakatsumi kishikawakatsumi commented Apr 8, 2026

Sequential readDataToEndOfFile() calls for stdout then stderr can deadlock when the child process fills the unread pipe's OS buffer while the parent is blocked on the first read.

Changes

  • Sources/App/routes.swift: Replace sequential readDataToEndOfFile() with concurrent readabilityHandler callbacks backed by a DispatchGroup. Both pipes are drained in parallel; group.wait() ensures EOF on both before waitUntilExit() is called.
group.enter()
standardOutput.fileHandleForReading.readabilityHandler = { handle in
    let chunk = handle.availableData
    if chunk.isEmpty {
        standardOutput.fileHandleForReading.readabilityHandler = nil
        group.leave()
    } else {
        stdoutData.append(chunk)
    }
}
// same pattern for standardError …

try process.run()
group.wait()
process.waitUntilExit()

… count, and HTML errors

- Fix event listener leak in debugger by registering input handler once in init()
- Fix string vs number comparison for backtrack count in debugger
- Fix pipe deadlock by reading stdout/stderr before waitUntilExit()
- Fix incorrect groupCount increment for non-capturing groups in ExpressionParser
- Fix unclosed thead tag and stray closing span in index.html
Copilot AI review requested due to automatic review settings April 8, 2026 19:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses several correctness and UI-validity issues across the regex debugger, server-side process execution, expression parsing, and the public HTML template.

Changes:

  • Fixes a debugger UI event listener leak and corrects backtrack comparisons by using numeric parsing.
  • Adjusts server-side Process execution flow to avoid a pipe-related deadlock by changing stdout/stderr read timing.
  • Corrects group counting behavior in ExpressionParser and fixes HTML markup errors in index.html.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
Sources/ExpressionParser/ExpressionParser.swift Stops incrementing groupCount for non-capturing/lookaround group kinds to keep capture numbering correct.
Sources/App/routes.swift Reorders stdout/stderr reads vs waitUntilExit() in exec(...) to mitigate a process pipe deadlock.
Public/js/app.js Registers the debugger step-range input handler once during init and fixes backtrack comparison by coercing prior value to Number.
Public/index.html Fixes invalid HTML by closing <thead> and removing an extra stray closing </span>.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Sources/App/routes.swift Outdated
Comment on lines +190 to +196
try process.run()
process.waitUntilExit()

let stdoutData = standardOutput.fileHandleForReading.readDataToEndOfFile()
let stderrData = standardError.fileHandleForReading.readDataToEndOfFile()

process.waitUntilExit()

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

Reading stdout and stderr sequentially with readDataToEndOfFile can still deadlock if the child process writes enough data to the other pipe to fill its buffer while the parent is blocked reading the first pipe. To fully address the deadlock, drain stdout and stderr concurrently (e.g., readabilityHandler/DispatchQueue reads into buffers, or redirect both to temporary files) and then wait for termination.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in eaff15d. Both stdout and stderr are now drained concurrently using readabilityHandler callbacks that accumulate data into separate buffers. A DispatchGroup tracks when each pipe signals EOF (empty availableData), and group.wait() ensures both streams are fully drained before waitUntilExit() is called — eliminating the buffer-fill deadlock.

Comment on lines 118 to 133
case .nonCapture:
category = "groups"
key = "noncapgroup"
case .nonCaptureReset:
groupCount += 1
category = "groups"
key = "branchreset"
case .atomicNonCapturing:
groupCount += 1
category = "groups"
key = "atomic"
case .lookahead:
category = "lookaround"
key = "poslookahead"
case .negativeLookahead:
groupCount += 1
category = "lookaround"
key = "neglookahead"
case .nonAtomicLookahead:
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

The groupCount semantics changed so that non-capturing/lookaround groups no longer increment the capture numbering. There isn’t currently a test assertion that would catch regressions here (e.g., ensuring the first capturing group after a non-capturing group is still numbered 1). Consider adding an XCTest that parses a pattern like '(?:a)(b)' (and optionally '(?|a(b)|c(d))') and asserts the tooltip substitution/group numbering in the emitted tokens.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copilot AI changed the title Fix multiple bugs: debugger event listener leak, pipe deadlock, group… Fix pipe deadlock: drain stdout/stderr concurrently in exec() Apr 8, 2026
@kishikawakatsumi kishikawakatsumi merged commit a0b3263 into main Apr 8, 2026
3 checks passed
@kishikawakatsumi kishikawakatsumi deleted the fix branch April 8, 2026 20:18
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