0

I have a rather complex code with std::async calls and std::packaged_task, that fails to execute to the very end. I simplified it to the minimal reproducable example.

Two async functions are called one after another, inside of which there are packaged_tasks executed asynchronously using std::async. Then both we wait for both async functions to finish, using corresponding future.wait() method. The execution stops at futY.wait(); and second packaged_task is never executed (No second Inside handler func log).

#include <iostream>     // std::cout
#include <future>       // std::packaged_task, std::future
#include <exception>
#include <vector>
#include <list>
#include <memory>
#include <functional>

std::list<std::function<bool(const std::vector<int> &data)>> handlers_;

std::future<std::vector<int>> countup(int from, int to) {
    std::function<std::vector<int>(std::exception_ptr, std::vector<int>)> func = [=] (std::exception_ptr ex, std::vector<int> data) {
        std::cout << "Inside handler func " << from << " " << to << std::endl;
        if (ex != nullptr) {
            std::rethrow_exception(ex);
        }
        return data;
    };
    auto packageP = std::make_shared<std::packaged_task<std::vector<int>(std::exception_ptr, std::vector<int>)>>(func);

    auto fut = packageP->get_future();
    handlers_.push_back([packageP] (const std::vector<int> &data) mutable -> bool {
            std::cout << "Calling handler with data, size: " << data.size() << std::endl;

            (*packageP)(nullptr, data);
            return data.size();
        }
    );

    auto fut2 = std::async(std::launch::async, [=, &handlers_] {
            std::cout << "Before handler called " << from << to << std::endl;

            std::vector<int> vec ( to, from );

            auto res = (*handlers_.begin())(vec);

            std::cout << "Handler result " << res << " for " << from << " " << to << std::endl;
        });

    std::cout << "Called async in countup for " << from << " " << to << std::endl;

    return fut;
}

int main ()
{
  auto futX = std::async(std::launch::async, [] {
      auto fut1 = std::async(std::launch::async, [] {
        auto fut2 = countup(0, 2);

        std::cout << "Called X countup and waiting to finish" << std::endl;

        fut2.wait();

        auto vec = fut2.get();
        std::cout << "The X countup returned" << std::endl;
      });
      std::cout << "Called X async internal and waiting to finish" << std::endl;
      fut1.wait();

      return 2;
  });

  std::cout << "Called async X and waiting to finish" << std::endl;

  auto futY = std::async(std::launch::async, [] {
      auto fut1 = std::async(std::launch::async, [] {
        auto fut2 = countup(0, 3);

        std::cout << "Called Y countup and waiting to finish" << std::endl;
        fut2.wait();

        auto vec = fut2.get();
        std::cout << "The Y countup returned " << std::endl;
      });
      std::cout << "Called Y async internal and waiting to finish" << std::endl;
      fut1.wait();

      return 3;
  });

  std::cout << "Called async Y and waiting to finish" << std::endl;

  futX.wait();
  std::cout << "After async X and waiting to finish" << std::endl;

  futY.wait();
  std::cout << "After async Y and waiting to finish" << std::endl;

  int valueX = futX.get();                  // wait for the task to finish and get result
  int valueY = futY.get();                  // wait for the task to finish and get result

  std::cout << "The countdown lasted for " << valueX  << " " << valueY << " seconds" << std::endl;

  return 0;
}

The log is the following:

Called async X and waiting to finish
Called async Y and waiting to finish
Called X async internal and waiting to finish
Called Y async internal and waiting to finish
Called async in countup for Before handler called 02
0 2
Calling handler with data, size: 2
Inside handler func 0Called async in countup for  2
Handler result 01 for 0 2
 Before handler called 03
3Calling handler with data, size: 
Called X countup and waiting to finish
The X countup returned
3
After async X and waiting to finish
Called Y countup and waiting to finish

Could you please clarify why the code is never executed till the end?

rightaway717
  • 2,631
  • 3
  • 29
  • 43

1 Answers1

0

You are re-using the same packaged task when you call (*handlers_.begin()), and I think you have a data race when you handlers_.push_back.

You don't need the global handlers_, you can just capture a local handler.

#include <iostream>     // std::cout
#include <future>       // std::packaged_task, std::future
#include <exception>
#include <vector>
#include <list>
#include <memory>
#include <functional>

std::future<std::vector<int>> countup(int from, int to) {
    auto func = [=] (std::exception_ptr ex, std::vector<int> data) {
        std::cout << "Inside handler func " << from << " " << to << std::endl;
        if (ex != nullptr) {
            std::rethrow_exception(ex);
        }
        return data;
    };
    auto packageP = std::make_shared<std::packaged_task<std::vector<int>(std::exception_ptr, std::vector<int>)>>(func);

    auto fut = packageP->get_future();
    auto handler = [packageP] (const std::vector<int> &data) mutable -> bool {
        std::cout << "Calling handler with data, size: " << data.size() << std::endl;

        (*packageP)(nullptr, data);
        return data.size();
    };

    auto fut2 = std::async(std::launch::async, [=]() mutable {
            std::cout << "Before handler called " << from << to << std::endl;

            std::vector<int> vec ( to, from );

            auto res = handler(vec);

            std::cout << "Handler result " << res << " for " << from << " " << to << std::endl;
        });

    std::cout << "Called async in countup for " << from << " " << to << std::endl;

    return fut;
}

int main ()
{
  auto futX = std::async(std::launch::async, [] {
      auto fut1 = std::async(std::launch::async, [] {
        auto fut2 = countup(0, 2);

        std::cout << "Called X countup and waiting to finish" << std::endl;

        fut2.wait();

        auto vec = fut2.get();
        std::cout << "The X countup returned" << std::endl;
      });
      std::cout << "Called X async internal and waiting to finish" << std::endl;
      fut1.wait();

      return 2;
  });

  std::cout << "Called async X and waiting to finish" << std::endl;

  auto futY = std::async(std::launch::async, [] {
      auto fut1 = std::async(std::launch::async, [] {
        auto fut2 = countup(0, 3);

        std::cout << "Called Y countup and waiting to finish" << std::endl;
        fut2.wait();

        auto vec = fut2.get();
        std::cout << "The Y countup returned " << std::endl;
      });
      std::cout << "Called Y async internal and waiting to finish" << std::endl;
      fut1.wait();

      return 3;
  });

  std::cout << "Called async Y and waiting to finish" << std::endl;

  futX.wait();
  std::cout << "After async X and waiting to finish" << std::endl;

  futY.wait();
  std::cout << "After async Y and waiting to finish" << std::endl;

  int valueX = futX.get();                  // wait for the task to finish and get result
  int valueY = futY.get();                  // wait for the task to finish and get result

  std::cout << "The countdown lasted for " << valueX  << " " << valueY << " seconds" << std::endl;

  return 0;
}

See it on coliru

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • Thank you so much @Caleth for the answer. Indeed, I mistakenly executed only one package and the issue has gone once I remove a list of handlers. Unfortunately this issue has been introduced by me when simplifying the code. As for my original issue, could you please share if there can be any reason why in my example this line `int valueX = futX.get();` could be executed prior to setting value for `futY` ? – rightaway717 Aug 17 '21 at 09:59
  • @rightaway717 what do you mean? You have some indeterminately sequenced output, but it all eventually gets there, in one of the allowable orderings – Caleth Aug 17 '21 at 10:05
  • I mean that for some reason all (there are multiple) `future.wait()` calls are unblocked and execution proceeds to getting values from the futures (`future.get()`) though not all std::async methods for those futures seem to return (no logs for that). And the code is hanging on the first `future.get()` call. – rightaway717 Aug 17 '21 at 10:13
  • @rightaway717 X and Y happen on separate threads. They can interleave. What are you trying to achieve? – Caleth Aug 17 '21 at 10:18
  • Just to clarify it again. First there is a sequence of `std::async` calls which store results in futures. Then I do a series of wait() calls for those futures. So when all async functions return all the futures wait() calls must unblock one by one and I ll be extracting values from the futures by a series of `.get()` calls. Is it possible for ALL futures to unblock on wait() before all corresponding async methods return? – rightaway717 Aug 17 '21 at 10:21
  • @rightaway717 I think you misunderstand what a `std::future` is. It isn't a store of a result, it is a handle to a pending action. `std::async` returns approximately immediately, and the function that it invokes can interleave the caller. – Caleth Aug 17 '21 at 10:23
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/236081/discussion-between-caleth-and-rightaway717). – Caleth Aug 17 '21 at 10:23