Skip to content

Commit 64d201f

Browse files
authored
Merge pull request swiftlang#514 from swiftlang/owenv/genericparserperf
Fix quadratic behavior when GenericOutputParser is written to many times per line
2 parents 8eecb15 + d9d64a6 commit 64d201f

File tree

1 file changed

+6
-7
lines changed

1 file changed

+6
-7
lines changed

Sources/SWBCore/SpecImplementations/CommandLineToolSpec.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,7 +1441,7 @@ open class GenericOutputParser : TaskOutputParser {
14411441
public let toolBasenames: Set<String>
14421442

14431443
/// Buffered output that has not yet been parsed (parsing is line-by-line, so we buffer incomplete lines until we receive more output).
1444-
private var unparsedBytes: [UInt8] = []
1444+
private var unparsedBytes: ArraySlice<UInt8> = []
14451445

14461446
/// The Diagnostic that is being constructed, possibly across multiple lines of input.
14471447
private var inProgressDiagnostic: Diagnostic?
@@ -1488,19 +1488,18 @@ open class GenericOutputParser : TaskOutputParser {
14881488
// Forward the unparsed bytes immediately (without line buffering).
14891489
delegate.emitOutput(bytes)
14901490

1491-
// Append the new output to whatever partial line of output we might already have buffered.
1492-
unparsedBytes.append(contentsOf: bytes.bytes)
1493-
14941491
// Split the buffer into slices separated by newlines. The last slice represents the partial last line (there always is one, even if it's empty).
1495-
let lines = unparsedBytes.split(separator: UInt8(ascii: "\n"), maxSplits: .max, omittingEmptySubsequences: false)
1492+
var lines = bytes.split(separator: UInt8(ascii: "\n"), maxSplits: .max, omittingEmptySubsequences: false)
1493+
// Any unparsed bytes belong to the first line. We don't want to run `split` over these because it can lead to accidentally quadratic behavior if write is called many times per line.
1494+
lines[0] = unparsedBytes + lines[0]
14961495

14971496
// Parse any complete lines of output.
14981497
for line in lines.dropLast() {
14991498
parseLine(line)
15001499
}
15011500

1502-
// Remove any complete lines from the buffer, leaving only the last partial line (if any).
1503-
unparsedBytes.removeFirst(unparsedBytes.count - lines.last!.count)
1501+
// Track the last, incomplete line to as the unparsed bytes.
1502+
unparsedBytes = lines.last ?? []
15041503
}
15051504

15061505
/// Regex to extract location information from a diagnostic prefix (capture group 0 is the name, 1 is the line number, and 2 is the column).

0 commit comments

Comments
 (0)