Search code examples
c++c++11exception

How to propagate exceptions from sub-functions in a threaded program?


I am trying to understand how the exceptions can be propagated between different functions and returned to main function in C++. I have a small setup:

main.cpp:

int run () {
.
.
try {
  testException(file);
} catch (const std::exception &e) {
    std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}

test.cpp

std::exception_ptr g_exceptionPtr = nullptr;

void testThread() {
   std::this_thread::sleep_for(std::chrono::milliseconds(100));
   .
   .
}

void testException(const std::string &file) {
  TestCSV csv(file);
  try {
    std::thread input(testThread);
    csv.writeToCSV(file, a, b, c);
    input.join();
  } catch (const std::runtime_error &e) {
      g_exceptionPtr = std::current_exception();
      std::rethrow_exception(g_exceptionPtr);
  }
}

In test_csv.cpp:

TestCSV::writeToCSV(const std::string &file, const std::string &a, const std::string &b, const std::string &c) {
.
.
std::ofstream outFile(file);
    if (!outFile.is_open()) {
      throw std::runtime_error("Unable to open file for writing.");
    }
}

Now I want to propagate the error from writeToCSV function and handle it in main.cpp. But, currently, this exception is caught in test.cpp but is not re-thrown to main.cpp.

What is the issue and how can I resolve it?

P.S: Above code is just an example and please let me know if any info is missing


Solution

  • You are calling writeToCSV() in the context of the same thread that is calling run(), so any exception that writeToCSV() throws and is not caught will already propagate to run() as expected. You don't need to do anything extra for that. Simply don't catch the exception at all, or if you do then just re-throw it (not std::rethrow_exception()). Your usage of std::thread is irrelevant in this situation.

    int run () {
      ...
      try {
        testException(file);
      } catch (const std::exception &e) {
        // this should catch whatever testException() throws...
        std::cerr << "Error: " << e.what() << std::endl;
      }
      return 0;
    }
    
    void testException(const std::string &file) {
      TestCSV csv(file);
      csv.writeToCSV(file, a, b, c);
    
      // or:
    
      try {
        TestCSV csv(file);
        csv.writeToCSV(file, a, b, c);
      }
      catch (const std::exception &e) {
        ...
        throw;
      }
    }
    

    On the other hand, if your question is about propagating exceptions across threads, then you should change your test to reflect that. Moving writeToCSV() into the std::thread would be a better test of std::current_exception and std::rethrow_exception.

    std::current_exception allows you to capture a caught exception so you can access it outside of the catch. You can capture the exception in the original thread that threw it, move it to the desired thread, and then call std::rethrow_exception in that thread. In this case, after you have join'ed the std::thread if the exception_ptr was assigned, eg:

    void testThread(const std::string &file, std::exception_ptr &exceptionPtr) {
      try {
        TestCSV csv(file);
        csv.writeToCSV(...);
      } catch (const std::exception &) {
        exceptionPtr = std::current_exception();
      }
    }
    
    void testException(const std::string &file) {
      std::exception_ptr exceptionPtr = nullptr;
      std::thread writer(testThread, file, std::ref(exceptionPtr));
      ...
      writer.join();
      if (exceptionPtr)
        std::rethrow_exception(exceptionPtr);
    }
    
    int run () {
      ...
      try {
        testException(file);
      } catch (const std::exception &e) {
        // this should catch whatever testThread() throws...
        std::cerr << "Error: " << e.what() << std::endl;
      }
      return 0;
    }