Under RequestBatchExecutionType.Parallel, OBS pairs each result's requestType/requestId with a different request's responseData/requestStatus. No client can correct this: the association is already lost when the response is built. Serial execution is unaffected.
This issue is for tracking on our side. The fix belongs in obs-websocket; the analysis below is written so it can be handed over as-is.
Root cause
src/requesthandler/RequestBatchHandler.cpp collects parallel results in completion order. Each thread-pool task appends as it finishes:
for (auto &request : requests) {
threadPool.start(Utils::Compat::CreateFunctionRunnable([¶llelResults, &request]() {
RequestResult requestResult = parallelResults.requestHandler.ProcessRequest(request);
std::unique_lock<std::mutex> lock(parallelResults.conditionMutex);
parallelResults.results.push_back(requestResult); // completion order
lock.unlock();
parallelResults.condition.notify_one();
}));
}
src/websocketserver/WebSocketServer_Protocol.cpp then zips that vector against the original submission-order request array, by index:
size_t i = 0;
for (auto &requestResult : resultsVector) {
results.push_back(ConstructRequestResult(requestResult, requests[i]));
i++;
}
ConstructRequestResult takes the identity from one and the outcome from the other:
ret["requestType"] = requestJson["requestType"]; // submission order
ret["requestId"] = requestJson["requestId"]; // submission order
ret["requestStatus"] = { ... requestResult ... }; // completion order
ret["responseData"] = requestResult.ResponseData; // completion order
While the two orders differ, every result is mislabelled. The serial paths build their vector in submission order, so they line up.
Note requestStatus is displaced along with responseData, so a parallel batch can report a failure against a request that actually succeeded.
Observed
Raw bytes off the wire for a parallel batch of GetVersion, GetSceneItemList, GetStats:
"requestType":"GetVersion", "responseData":{"sceneItems"...
"requestType":"GetSceneItemList", "responseData":{"activeFps"...
"requestType":"GetStats", "responseData":{"availableRequests"...
Every serial batch in the same run pairs correctly.
Suggested fix
Preserve submission order in the parallel branch: size the results vector up front and have each task write to its own slot, rather than appending. The existing requests[i] zip in WebSocketServer_Protocol.cpp then needs no change, and requestStatus is fixed at the same time.
parallelResults.results.resize(requests.size());
for (size_t index = 0; index < requests.size(); index++) {
auto &request = requests[index];
threadPool.start(Utils::Compat::CreateFunctionRunnable([¶llelResults, &request, index]() {
RequestResult requestResult = parallelResults.requestHandler.ProcessRequest(request);
std::unique_lock<std::mutex> lock(parallelResults.conditionMutex);
parallelResults.results[index] = requestResult; // submission order
parallelResults.completed++;
lock.unlock();
parallelResults.condition.notify_one();
}));
}
The wait condition currently uses results.size() == requestCount, which no longer works once the vector is pre-sized, so it needs a separate completion counter.
Why this cannot be worked around client-side
- Nothing in the response reveals the completion order, so the permutation is not recoverable.
inputVariables/outputVariables would allow a marker, but are rejected in parallel mode.
responseData is built by each request handler and has no passthrough field to stamp an id into.
- Matching payloads by shape fails: 75 of 147 requests return no payload at all, and five groups of requests share identical response shapes (seven of them return just
outputActive). It also cannot recover requestStatus.
Client behaviour meanwhile
BatchResults.Get and TryGet refuse to resolve a reference on a parallel batch and explain why, rather than returning another request's data. Raw remains available.
Under
RequestBatchExecutionType.Parallel, OBS pairs each result'srequestType/requestIdwith a different request'sresponseData/requestStatus. No client can correct this: the association is already lost when the response is built. Serial execution is unaffected.This issue is for tracking on our side. The fix belongs in obs-websocket; the analysis below is written so it can be handed over as-is.
Root cause
src/requesthandler/RequestBatchHandler.cppcollects parallel results in completion order. Each thread-pool task appends as it finishes:src/websocketserver/WebSocketServer_Protocol.cppthen zips that vector against the original submission-order request array, by index:ConstructRequestResulttakes the identity from one and the outcome from the other:While the two orders differ, every result is mislabelled. The serial paths build their vector in submission order, so they line up.
Note
requestStatusis displaced along withresponseData, so a parallel batch can report a failure against a request that actually succeeded.Observed
Raw bytes off the wire for a parallel batch of GetVersion, GetSceneItemList, GetStats:
Every serial batch in the same run pairs correctly.
Suggested fix
Preserve submission order in the parallel branch: size the results vector up front and have each task write to its own slot, rather than appending. The existing
requests[i]zip inWebSocketServer_Protocol.cppthen needs no change, andrequestStatusis fixed at the same time.The wait condition currently uses
results.size() == requestCount, which no longer works once the vector is pre-sized, so it needs a separate completion counter.Why this cannot be worked around client-side
inputVariables/outputVariableswould allow a marker, but are rejected in parallel mode.responseDatais built by each request handler and has no passthrough field to stamp an id into.outputActive). It also cannot recoverrequestStatus.Client behaviour meanwhile
BatchResults.GetandTryGetrefuse to resolve a reference on a parallel batch and explain why, rather than returning another request's data.Rawremains available.