@@ -2461,7 +2461,7 @@ class TCancelableBoundedConcurrencyRunner
2461
2461
, ConcurrencyLimit_(concurrencyLimit)
2462
2462
, Futures_(Callbacks_.size(), VoidFuture)
2463
2463
, Results_(Callbacks_.size())
2464
- , CurrentIndex_(std::min<int >(ConcurrencyLimit_, ssize(Callbacks_)))
2464
+ , CurrentIndex_(std::min<int >(ConcurrencyLimit_, std:: ssize(Callbacks_)))
2465
2465
, FailOnFirstError_(failOnError)
2466
2466
{ }
2467
2467
@@ -2499,56 +2499,70 @@ class TCancelableBoundedConcurrencyRunner
2499
2499
2500
2500
void RunCallback (int index)
2501
2501
{
2502
- auto future = Callbacks_[index]();
2502
+ // ORD-2002: Avoid calling OnResult directly to prevent unbounded
2503
+ // recursive chains like RunCallback -> OnResult -> RunCallback...
2504
+ while (true ) {
2505
+ auto future = Callbacks_[index]();
2506
+ if (!future.IsSet ()) {
2507
+ {
2508
+ auto guard = Guard (SpinLock_);
2509
+ if (Error_) {
2510
+ guard.Release ();
2511
+ future.Cancel (*Error_);
2512
+ return ;
2513
+ }
2503
2514
2504
- if (future.IsSet ()) {
2505
- OnResult (index, std::move (future.Get ()));
2506
- return ;
2507
- }
2515
+ Futures_[index] = future.template As <void >();
2516
+ }
2508
2517
2509
- {
2510
- auto guard = Guard (SpinLock_);
2511
- if (Error_) {
2512
- guard.Release ();
2513
- future.Cancel (*Error_);
2514
- return ;
2518
+ future.Subscribe (
2519
+ BIND_NO_PROPAGATE (&TCancelableBoundedConcurrencyRunner::OnResult, MakeStrong (this ), index)
2520
+ // NB: Sync invoker protects from unbounded recursion.
2521
+ .Via (GetSyncInvoker ()));
2522
+ break ;
2515
2523
}
2516
2524
2517
- Futures_[index] = future.template As <void >();
2518
- }
2525
+ auto suggestedIndex = HandleResultAndSuggestNextIndex (index, std::move (future.Get ()));
2526
+ if (!suggestedIndex) {
2527
+ break ;
2528
+ }
2519
2529
2520
- future.Subscribe (
2521
- BIND_NO_PROPAGATE (&TCancelableBoundedConcurrencyRunner::OnResult, MakeStrong (this ), index)
2522
- // NB: Sync invoker protects from unbounded recursion.
2523
- .Via (GetSyncInvoker ()));
2530
+ index = *suggestedIndex;
2531
+ }
2524
2532
}
2525
2533
2526
- void OnResult (int index, const NYT::TErrorOr<T>& result)
2534
+ [[nodiscard]]
2535
+ std::optional<int > HandleResultAndSuggestNextIndex (int index, const TErrorOr<T>& result)
2527
2536
{
2528
2537
if (FailOnFirstError_ && !result.IsOK ()) {
2529
2538
OnError (result);
2530
- return ;
2539
+ return std::nullopt ;
2531
2540
}
2532
2541
2533
2542
int newIndex;
2534
2543
int finishedCount;
2535
2544
{
2536
2545
auto guard = Guard (SpinLock_);
2537
2546
if (Error_) {
2538
- return ;
2547
+ return std::nullopt ;
2539
2548
}
2540
2549
2541
2550
newIndex = CurrentIndex_++;
2542
2551
finishedCount = ++FinishedCount_;
2543
2552
Results_[index] = result;
2544
2553
}
2545
2554
2546
- if (finishedCount == ssize (Callbacks_)) {
2555
+ if (finishedCount == std:: ssize (Callbacks_)) {
2547
2556
Promise_.TrySet (Results_);
2548
2557
}
2549
2558
2550
- if (newIndex < ssize (Callbacks_)) {
2551
- RunCallback (newIndex);
2559
+ return newIndex < std::ssize (Callbacks_) ? std::optional (newIndex) : std::nullopt;
2560
+ }
2561
+
2562
+ void OnResult (int index, const TErrorOr<T>& result)
2563
+ {
2564
+ if (auto suggestedIndex = HandleResultAndSuggestNextIndex (index, result)) {
2565
+ RunCallback (*suggestedIndex);
2552
2566
}
2553
2567
}
2554
2568
@@ -2616,26 +2630,44 @@ class TBoundedConcurrencyRunner
2616
2630
2617
2631
void RunCallback (int index)
2618
2632
{
2619
- auto future = Callbacks_[index]();
2620
- if (future.IsSet ()) {
2621
- OnResult (index, future.Get ());
2622
- } else {
2623
- future.Subscribe (
2624
- BIND_NO_PROPAGATE (&TBoundedConcurrencyRunner::OnResult, MakeStrong (this ), index));
2633
+ // ORD-2002: Avoid calling OnResult directly to prevent unbounded
2634
+ // recursive chains like RunCallback -> OnResult -> RunCallback...
2635
+ while (true ) {
2636
+ auto future = Callbacks_[index]();
2637
+ if (!future.IsSet ()) {
2638
+ future.Subscribe (
2639
+ BIND_NO_PROPAGATE (&TBoundedConcurrencyRunner::OnResult, MakeStrong (this ), index)
2640
+ // NB: Sync invoker protects from unbounded recursion.
2641
+ .Via (GetSyncInvoker ()));
2642
+ break ;
2643
+ }
2644
+
2645
+ auto suggestedIndex = HandleResultAndSuggestNextIndex (index, future.Get ());
2646
+ if (!suggestedIndex) {
2647
+ break ;
2648
+ }
2649
+
2650
+ index = *suggestedIndex;
2625
2651
}
2626
2652
}
2627
2653
2628
- void OnResult (int index, const NYT::TErrorOr<T>& result)
2654
+ [[nodiscard]]
2655
+ std::optional<int > HandleResultAndSuggestNextIndex (int index, const TErrorOr<T>& result)
2629
2656
{
2630
2657
Results_[index] = result;
2631
2658
2632
2659
int newIndex = CurrentIndex_++;
2633
- if (newIndex < static_cast < ssize_t > (Callbacks_. size () )) {
2634
- RunCallback (newIndex );
2660
+ if (++FinishedCount_ == std::ssize (Callbacks_)) {
2661
+ Promise_. Set (Results_ );
2635
2662
}
2636
2663
2637
- if (++FinishedCount_ == static_cast <ssize_t >(Callbacks_.size ())) {
2638
- Promise_.Set (Results_);
2664
+ return newIndex < std::ssize (Callbacks_) ? std::optional (newIndex) : std::nullopt;
2665
+ }
2666
+
2667
+ void OnResult (int index, const TErrorOr<T>& result)
2668
+ {
2669
+ if (auto suggestedIndex = HandleResultAndSuggestNextIndex (index, result)) {
2670
+ RunCallback (*suggestedIndex);
2639
2671
}
2640
2672
}
2641
2673
};
0 commit comments