|
1 | | -// Copyright 2019-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| 1 | +// Copyright 2019-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
2 | 2 | // |
3 | 3 | // Redistribution and use in source and binary forms, with or without |
4 | 4 | // modification, are permitted provided that the following conditions |
@@ -482,6 +482,10 @@ class EnsembleContext { |
482 | 482 | // FinishEnsemble for details). |
483 | 483 | bool response_sent_{false}; |
484 | 484 |
|
| 485 | + // Prevents duplicate error responses or status annotation when FinishEnsemble |
| 486 | + // is invoked multiple times while steps are still in-flight. |
| 487 | + bool error_response_sent_{false}; |
| 488 | + |
485 | 489 | // The allocator that will be used to allocate buffers for the |
486 | 490 | // inference result tensors. |
487 | 491 | std::unique_ptr< |
@@ -958,28 +962,39 @@ EnsembleContext::PrepareSteps( |
958 | 962 | // Initialization error, ensemble status will be not ok since the beginning |
959 | 963 | if (completed_step == nullptr && !ensemble_status_.IsOk()) { |
960 | 964 | ensemble_status_ = FinishEnsemble(); |
| 965 | + return ensemble_status_; |
961 | 966 | } |
962 | 967 |
|
| 968 | + // Always process completed steps via UpdateEnsembleState to decrement the |
| 969 | + // inflight counter, even if the ensemble has already failed. This allows |
| 970 | + // proper cleanup after errors. |
| 971 | + std::set<std::pair<std::string, IterationCount>> updated_tensors; |
| 972 | + auto update_status = UpdateEnsembleState(completed_step, &updated_tensors); |
| 973 | + |
| 974 | + // Preserve existing error status; only update ensemble_status_ if |
| 975 | + // UpdateEnsembleState fails. |
| 976 | + if (!update_status.IsOk()) { |
| 977 | + ensemble_status_ = update_status; |
| 978 | + } |
| 979 | + |
| 980 | + // Only compute and schedule next steps if ensemble is still OK; this |
| 981 | + // prevents scheduling new steps after an error, as the condition now |
| 982 | + // gates both GetNextSteps and subsequent FinishEnsemble logic. |
963 | 983 | if (ensemble_status_.IsOk()) { |
964 | | - StepList res; |
965 | | - std::set<std::pair<std::string, IterationCount>> updated_tensors; |
966 | | - ensemble_status_ = UpdateEnsembleState(completed_step, &updated_tensors); |
967 | | - if (ensemble_status_.IsOk()) { |
968 | | - ensemble_status_ = GetNextSteps(updated_tensors, ready_steps); |
969 | | - } |
| 984 | + ensemble_status_ = GetNextSteps(updated_tensors, ready_steps); |
| 985 | + } |
970 | 986 |
|
971 | | - // Reaching the end of processing completed step, check if the ensemble |
972 | | - // should send response / error. |
973 | | - if (!ensemble_status_.IsOk()) { |
974 | | - ensemble_status_ = FinishEnsemble(); |
975 | | - } else { |
976 | | - std::unique_ptr<InferenceResponse> response; |
977 | | - if (!updated_tensors.empty()) { |
978 | | - ensemble_status_ = |
979 | | - CheckAndSetEnsembleOutput(updated_tensors, &response); |
980 | | - } |
981 | | - ensemble_status_ = FinishEnsemble(std::move(response)); |
| 987 | + // Reaching the end of processing completed step, check if the ensemble |
| 988 | + // should send response / error. |
| 989 | + if (!ensemble_status_.IsOk()) { |
| 990 | + ensemble_status_ = FinishEnsemble(); |
| 991 | + } else { |
| 992 | + std::unique_ptr<InferenceResponse> response; |
| 993 | + if (!updated_tensors.empty()) { |
| 994 | + ensemble_status_ = |
| 995 | + CheckAndSetEnsembleOutput(updated_tensors, &response); |
982 | 996 | } |
| 997 | + ensemble_status_ = FinishEnsemble(std::move(response)); |
983 | 998 | } |
984 | 999 | return ensemble_status_; |
985 | 1000 | } |
@@ -1270,7 +1285,7 @@ EnsembleContext::FinishEnsemble(std::unique_ptr<InferenceResponse>&& response) |
1270 | 1285 | } |
1271 | 1286 |
|
1272 | 1287 | // Add ensemble name to make error message more trackable |
1273 | | - if (!ensemble_status_.IsOk()) { |
| 1288 | + if (!ensemble_status_.IsOk() && !error_response_sent_) { |
1274 | 1289 | ensemble_status_ = Status( |
1275 | 1290 | ensemble_status_.StatusCode(), "in ensemble '" + info_->ensemble_name_ + |
1276 | 1291 | "', " + ensemble_status_.Message()); |
@@ -1309,14 +1324,17 @@ EnsembleContext::FinishEnsemble(std::unique_ptr<InferenceResponse>&& response) |
1309 | 1324 | } |
1310 | 1325 | } |
1311 | 1326 | } else { |
1312 | | - if (response != nullptr) { |
1313 | | - InferenceResponse::SendWithStatus( |
1314 | | - std::move(response), TRITONSERVER_RESPONSE_COMPLETE_FINAL, |
1315 | | - ensemble_status_); |
1316 | | - } else { |
1317 | | - InferenceRequest::RespondIfError( |
1318 | | - request_tracker_->Request(), ensemble_status_, |
1319 | | - false /* release_requests */, FailureReason::OTHER); |
| 1327 | + if (!error_response_sent_ && inflight_step_counter_ == 0) { |
| 1328 | + if (response != nullptr) { |
| 1329 | + InferenceResponse::SendWithStatus( |
| 1330 | + std::move(response), TRITONSERVER_RESPONSE_COMPLETE_FINAL, |
| 1331 | + ensemble_status_); |
| 1332 | + } else { |
| 1333 | + InferenceRequest::RespondIfError( |
| 1334 | + request_tracker_->Request(), ensemble_status_, |
| 1335 | + false /* release_requests */, FailureReason::OTHER); |
| 1336 | + } |
| 1337 | + error_response_sent_ = true; |
1320 | 1338 | } |
1321 | 1339 | } |
1322 | 1340 |
|
@@ -1540,6 +1558,8 @@ EnsembleContext::ScheduleSteps( |
1540 | 1558 | } else { |
1541 | 1559 | std::lock_guard<std::mutex> lock(context->mutex_); |
1542 | 1560 | context->ensemble_status_ = step_status; |
| 1561 | + LOG_WARNING << "Ensemble '" << context->info_->ensemble_name_ |
| 1562 | + << "' failed to schedule step : " << step_status.Message(); |
1543 | 1563 | } |
1544 | 1564 | } |
1545 | 1565 |
|
|
0 commit comments