Fix pipe deadlock: drain stdout/stderr concurrently in exec()#596
Conversation
… 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
There was a problem hiding this comment.
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
Processexecution flow to avoid a pipe-related deadlock by changing stdout/stderr read timing. - Corrects group counting behavior in
ExpressionParserand fixes HTML markup errors inindex.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.
| try process.run() | ||
| process.waitUntilExit() | ||
|
|
||
| let stdoutData = standardOutput.fileHandleForReading.readDataToEndOfFile() | ||
| let stderrData = standardError.fileHandleForReading.readDataToEndOfFile() | ||
|
|
||
| process.waitUntilExit() | ||
|
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@copilot apply changes based on this feedback
There was a problem hiding this comment.
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.
| 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: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@copilot apply changes based on this feedback
Agent-Logs-Url: https://github.com/SwiftFiddle/swiftregex/sessions/0581c0b2-a6c3-4457-a084-a2f15745cd99 Co-authored-by: kishikawakatsumi <40610+kishikawakatsumi@users.noreply.github.com>
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 sequentialreadDataToEndOfFile()with concurrentreadabilityHandlercallbacks backed by aDispatchGroup. Both pipes are drained in parallel;group.wait()ensures EOF on both beforewaitUntilExit()is called.