From 1b1b43924547e075da7fd553a41a7b207fd3a76a Mon Sep 17 00:00:00 2001 From: "Christian W. Damus" Date: Fri, 27 Mar 2026 16:30:56 -0400 Subject: [PATCH 1/2] fix trace engine hanging on protocol error When the trace processor shell returns a protocol error response, the engine communicating with it just hangs. The engine must accurately detect the protocol error response and then flush its pending state. This then lets the error be thrown to Sokatoa for reporting to the user. Signed-off-by: Christian W. Damus --- ui/src/trace_processor/engine.ts | 50 +++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/ui/src/trace_processor/engine.ts b/ui/src/trace_processor/engine.ts index 91609592237..79e24c508fd 100644 --- a/ui/src/trace_processor/engine.ts +++ b/ui/src/trace_processor/engine.ts @@ -233,6 +233,19 @@ export abstract class EngineBase implements Engine, Disposable { this.rxSeqId = rpc.seq; + // The protobufjs oneof discriminant for `type` is the string field `rpc.type`. + // When the TraceProcessor rejects an unknown request it sets the `invalid_request` + // oneof case, leaving `rpc.response` as null/0 so the switch below would fall + // through to `default` without ever resolving the pending promise — causing an + // indefinite hang. Detect and fail fast here instead. + if (rpc.type === 'invalidRequest') { + this.fail( + `TraceProcessor rejected RPC request (method=${rpc.invalidRequest}). ` + + `Ensure the trace_processor_shell binary is compatible with this ` + + `version of the UI (ERR:rpc_invalid_request)`, + ); + } + let isFinalResponse = true; switch (rpc.response) { @@ -675,7 +688,42 @@ export abstract class EngineBase implements Engine, Disposable { protected fail(reason: string) { this._failed = reason; - throw new Error(reason); + const error = new Error(reason); + // Reject all pending operations so callers don't hang forever when the + // engine enters a failed state (e.g. after receiving an invalid_request). + for (const p of this.pendingParses) p.reject(error); + this.pendingParses = []; + for (const p of this.pendingEOFs) p.reject(error); + this.pendingEOFs = []; + for (const p of this.pendingResetTraceProcessors) p.reject(error); + this.pendingResetTraceProcessors = []; + for (const p of this.pendingRestoreTables) p.reject(error); + this.pendingRestoreTables = []; + for (const p of this.pendingComputeMetrics) p.reject(error); + this.pendingComputeMetrics = []; + this.pendingReadMetatrace?.reject(error); + this.pendingReadMetatrace = undefined; + this.pendingRegisterSqlPackage?.reject(error); + this.pendingRegisterSqlPackage = undefined; + this.pendingAnalyzeStructuredQueries?.reject(error); + this.pendingAnalyzeStructuredQueries = undefined; + this.pendingTraceSummary?.reject(error); + this.pendingTraceSummary = undefined; + // Complete any pending streaming queries with an error result. + // WritableQueryResult has no reject() method — errors are signalled by + // calling appendResultBatch() with a QueryResult proto that has `error` + // set and a final batch with `is_last_batch=true`. + if (this.pendingQueries.length > 0) { + const errBatch = protos.QueryResult.encode( + protos.QueryResult.create({ + error: reason, + batch: [protos.QueryResult.CellsBatch.create({isLastBatch: true})], + }), + ).finish(); + for (const q of this.pendingQueries) q.appendResultBatch(errBatch); + this.pendingQueries = []; + } + throw error; } get failed(): string | undefined { From 9886f4635bc635e8d9fe652e89437a711f34971d Mon Sep 17 00:00:00 2001 From: "Christian W. Damus" Date: Fri, 27 Mar 2026 19:30:31 -0400 Subject: [PATCH 2/2] tp: work around clang compiler vectorization bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apple Clang 21 (macOS 26.4 / Xcode 26) has a vectorization bug that miscompiles the WebSocket unmasking loop in http_server.cc. The pattern mask[i % sizeof(mask)] with a 4-byte mask is compiled into a vector where every other group of 4 bytes is XORed with zero: bytes 0–3 are correctly unmasked, bytes 4–7 stay masked, bytes 8–11 correct, 12–15 masked, etc. This caused the RPC request field (bytes 4–5 of the inner proto) to be left at its masked value (0x52 instead of 0x10=field-2-varint-tag), which decoded as field_id=10/wire_type=2 instead of field_id=2/varint, making req.request() return 0 (TPM_UNSPECIFIED). The server responded with invalid_request=0. For android-graphics/sokatoa#4933 Signed-off-by: Christian W. Damus --- src/base/http/http_server.cc | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/base/http/http_server.cc b/src/base/http/http_server.cc index d2d3d3fe550..1afce3e3eca 100644 --- a/src/base/http/http_server.cc +++ b/src/base/http/http_server.cc @@ -449,9 +449,23 @@ size_t HttpServer::ParseOneWebsocketFrame(HttpServerConnection* conn) { return 0; // Not enough data to read the payload. uint8_t* const payload_start = rd; - // Unmask the payload. - for (uint32_t i = 0; i < payload_len; ++i) - payload_start[i] ^= mask[i % sizeof(mask)]; + // Unmask the payload. Use explicit 32-bit XOR to avoid a compiler + // vectorization bug (observed in Apple Clang 21 / Xcode 26) where the + // mask[i % 4] pattern produces an incorrect 8-byte vector with the second + // half zeroed, causing every other 4-byte group to be left unmasked. + { + uint32_t mask32; + memcpy(&mask32, mask, sizeof(mask32)); + uint32_t i = 0; + for (; i + 4 <= payload_len; i += 4) { + uint32_t tmp; + memcpy(&tmp, &payload_start[i], sizeof(tmp)); + tmp ^= mask32; + memcpy(&payload_start[i], &tmp, sizeof(tmp)); + } + for (; i < payload_len; ++i) + payload_start[i] ^= mask[i & 3]; + } if (opcode == kOpcodePing) { PERFETTO_DLOG("[HTTP] Websocket PING");