Pubmatic multi inference - #8842
Conversation
Greptile SummaryThis PR adds Pubmatic-specific multi-inference extensions to the Triton HTTP server: a generic
Confidence Score: 2/5Not safe to merge: both new HTTP handlers contain a use-after-free that can be triggered in production whenever Triton rejects a shard's async submission mid-loop, and the startup sequence can abandon the DB refresh thread with its pool freed beneath it. The partial-scheduling error paths in HandleMultiInfer and HandlePredict free MultiInferShardRequest objects whose InferResponseComplete callbacks are still in-flight in the Triton thread pool, producing dangling-pointer dereferences. Separately, StartTritonModelsRefreshThread runs before the FAIL_IF_ERR call that may exit(1) without joining it, causing the atexit handler to destroy the ODBC pool while the thread is mid-query. Both defects are in the hot path of the new endpoints. Files Needing Attention: src/multi_infer.cc (both HandleMultiInfer and HandlePredict scheduling loops) and src/main.cc (StartTritonModelsRefreshThread / InitializeReadyModelNames ordering). Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant HTTPServer
participant HandleMultiInfer
participant MultiInferAggregator
participant TritonServer
participant InferResponseComplete
Client->>HTTPServer: POST /v2/multi_infer
HTTPServer->>HandleMultiInfer: dispatch
HandleMultiInfer->>MultiInferAggregator: create(n slots)
loop for each slot i
HandleMultiInfer->>TritonServer: ScheduleInferAsync(shard_i)
note over HandleMultiInfer: shard_i released only after ALL slots scheduled
end
TritonServer-->>InferResponseComplete: callback(slot_i, flags)
InferResponseComplete->>MultiInferAggregator: OnShardDone(slot_i, shard_json)
note over MultiInferAggregator: when done_count == n
MultiInferAggregator->>HTTPServer: evthr_defer(FinishThunk)
HTTPServer->>Client: "HTTP 200 {responses:[]}"
Reviews (2): Last reviewed commit: "Separated predict and multi_infer endpoi..." | Re-trigger Greptile |
| std::string BuildMySqlDriverConnectString(const DatabaseConfig& c) | ||
| { | ||
| std::string driver = c.odbc_driver_name; | ||
| if (driver.empty()) { | ||
| driver = "MySQL ODBC 9.7 Unicode Driver"; | ||
| } | ||
| std::ostringstream conn; | ||
| conn << "DRIVER={" << driver << "};" << "SERVER=" << c.database_ip << ";" << "PORT=" << c.database_port << ";" << "UID={" << c.dsn_user_name << "};" << "PWD={" << c.dsn_user_password << "};"; | ||
| return conn.str(); | ||
| } |
There was a problem hiding this comment.
…ating the response. Added minor optimisations
| TRITONSERVER_ErrorDelete(err); | ||
| evhtp_request_resume(req); | ||
| return; | ||
| } | ||
|
|
||
| shard_holders.push_back(std::move(shard)); | ||
| release_holders.push_back(std::move(rel)); | ||
| } | ||
|
|
||
| for (size_t i = 0; i < n; ++i) { |
There was a problem hiding this comment.
Use-after-free when shard scheduling fails mid-loop
In both HandleMultiInfer and HandlePredict, once shard slot i is successfully scheduled via ScheduleInferAsync, the corresponding MultiInferShardRequest is owned by shard_holders (a unique_ptr). If scheduling a later slot i+1 fails, the function returns early — destroying shard_holders[0..i] — while the Triton server still holds in-flight async work for those earlier slots. Their InferResponseComplete callback receives userp as a raw MultiInferShardRequest*, so when the callback fires it dereferences a freed object.
CancelAllSubRequests only marks requests for cancellation and does not guarantee callbacks are suppressed; the callbacks will still fire (with an error), using the dangling pointer.
The fix is to release() each shard/release holder immediately after its ScheduleInferAsync succeeds, rather than deferring all releases to a final loop.
| exit(1); | ||
| } |
There was a problem hiding this comment.
Refresh thread left running when
FAIL_IF_ERR calls exit(1)
StartTritonModelsRefreshThread is called before FAIL_IF_ERR(InitializeReadyModelNames(...)). If InitializeReadyModelNames returns an error, FAIL_IF_ERR calls exit(1) without calling JoinTritonModelsRefreshThread. The refresh thread is still alive and iterating through UpdateTritonModelsData, which calls into the global ODBC pool. The TritonDmOdbcPoolAtExit handler registered via std::atexit then nulls out and destroys that pool while the thread is mid-use — a use-after-free.
The simplest fix is to swap the order: call InitializeReadyModelNames first (fail fast before any thread is started), then StartTritonModelsRefreshThread.
Thanks for submitting a PR to Triton!
Please go the the
Previewtab above this description box and select the appropriate sub-template:If you already created the PR, please replace this message with one of
and fill it out.