Naios / continuable

C++14 asynchronous allocation aware futures (supporting then, exception handling, coroutines and connections)
https://naios.github.io/continuable/
MIT License
815 stars 44 forks source link

Unexpected (undefined?) behaviour when using `::to_future` with a canceled continuable #63

Open LanderN opened 1 year ago

LanderN commented 1 year ago

@Naios


Commit Hash

f7f304e971e05d5cae24edc6a0c93116588edd0b (4.2.1)

Expected Behavior

Calling .get() on an std::future obtained by applying cti::transforms::to_future() should result in an exception.

Actual Behavior

.get() returns without an exception, returning some (random?) value or crashing (probably undefined behaviour).

Steps to Reproduce

Run the following program:

#include <iostream>

#include <continuable/continuable.hpp>

enum Result { READY, EXCEPTION, CANCELLATION };

cti::continuable<int> get_result(Result type) {
  switch (type) {
  case READY:
    std::cout << "Creating ready continuable..." << std::endl;
    return cti::make_ready_continuable<int>(1);
  case EXCEPTION:
    std::cout << "Creating exceptional continuable..." << std::endl;
    return cti::make_exceptional_continuable<int>(
        std::make_exception_ptr(std::runtime_error("Exception!")));
  case CANCELLATION:
  default:
    std::cout << "Creating cancelling continuable..." << std::endl;
    return cti::make_cancelling_continuable<int>();
    break;
  }
}

void test(Result type) {
  auto cti_value = get_result(type);
  auto future = std::move(cti_value).apply(cti::transforms::to_future());

  if (!future.valid()) {
    std::cout << "Future not valid!" << std::endl;
    exit(1);
  }

  future.wait();
  try {
    if (!future.valid()) {
      std::cout << "Future not valid!" << std::endl;
      exit(1);
    }
    auto value = future.get();
    std::cout << "Result is a value: " << value << "\n" << std::endl;
    // Do something with the value
  } catch (...) {
    std::cout << "Result is an exception!\n" << std::endl;
    // Do something with the exception
  }
}

int main() {
  test(READY);
  test(EXCEPTION);
  test(CANCELLATION);
}

Output (on my system):

Creating ready continuable...
Result is a value: 1

Creating exceptional continuable...
Result is an exception!

Creating cancelling continuable...
Result is a value: 1

I'm not sure what the expected behaviour is when calling .get() on a "cancelled" future, but it probably shouldn't return 1 in this case. Maybe this use case is unsupported? In that case, I would expect this to be reflected in the documentation.

Your Environment

Naios commented 1 year ago

Thanks for your report.

It might be possible that to_future is missing the correct logic for the cancellation.