Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What goes wrong when passing a std::sub_match as argument to a std::thread?

I'm passing a std::sub_match as an argument to a std::thread (see my example code below). The thread function expects a const string reference. A sub_match can be converted to a string. So everything compiles fine.

But sometimes the function receives the wrong string. When I convert the sub_match to a string before passing it to the thread it works as expected. What is the difference?

I think this is a race condition as the original sub_match may not exist anymore when the thread executes. But I thought arguments to threads would be copied anyway. How can I find out which arguments are safe to pass to a thread and which not?

#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <regex>
#include <unistd.h>

class test_t {
  public:
    test_t(void) {}
    ~test_t(void) {}

    void start(void){
     //-------------------------------------------------
     // Do some memory allocation.
     // The error seems to appear faster with that.
     std::vector<std::string> vec;
     for(unsigned int i = 0; i < 1000; ++i) {
        vec.push_back("test_test_test");
     }
     //-------------------------------------------------

     std::string event = "operating";
     std::smatch match;
     std::regex expr("\\(operating\\)",
         std::regex_constants::icase | 
         std::regex_constants::basic);

     if(std::regex_match(event, match, expr)) {
        std::cout << "start thread" << std::endl;
        m_thread = std::thread(&test_t::thread_func, this, match[1]);              //NOK
//        m_thread = std::thread(&test_t::thread_func, this, match[1].str());        // OK
//        m_thread = std::thread(&test_t::thread_func, this, (std::string)match[1]); // OK
        m_thread.detach();
        std::cout << "thread started" << std::endl;
     }
    }

  private:
    std::thread m_thread;

    void thread_func(const std::string& string) {
     if(string != "operating") {
        std::cout << "ERROR: string: \"" << string << "\"" << std::endl;
        exit(EXIT_FAILURE);
     } else {
        std::cout << "string: \"" << string << "\"" << std::endl;
     }
    }
};

int main(int argc, char** argv) {
  test_t test;
  while(1) {
    test.start();
    usleep(100);
  }
  return 0;
}

Compilation Message:

Compiled with: g++ --std=c++11 -pthread -o test main.cpp
g++ --version: g++ (SUSE Linux) 4.8.5

Expected output:

start thread
thread started
string: "operating"
(repeat)

Actual output:

start thread
thread started
string: "operating"
ERROR: string: "test_test"
like image 982
Markus R. Avatar asked Jul 10 '19 11:07

Markus R.


2 Answers

operator[] for std::smatch returns sub_match which can be treated as pair of iterators to matched characters.

After regex_match was called, you can use operator[] to access sub-matches as long as event exists. When event is deleted (you are not joining your thread, so start returns immediately and event is destroyed), sub-matches have dangling pointers and should not be accessed.


m_thread = std::thread(&test_t::thread_func, this, match[1]);

this doesn't work because when function goes out of scope, event is deleted and sub-match has dangling pointers.


m_thread = std::thread(&test_t::thread_func, this, match[1].str());

this works because str() returns copy of matched string.


m_thread = std::thread(&test_t::thread_func, this, (std::string)match[1]);

this also works because temporary string is created based on sub-match match[1], and temp is passed into thread.

like image 55
rafix07 Avatar answered Nov 18 '22 07:11

rafix07


From some docs:

Because std::match_results holds std::sub_matches, each of which is a pair of iterators into the original character sequence that was matched, it's undefined behavior to examine std::match_results if the original character sequence was destroyed or iterators to it were invalidated for other reasons.

… and the same page teaches us that std::smatch is an alias for std::match_results<std::string::const_iterator>.

You'll need to take a copy of the character range referred to by these iterators, and pass that to std::thread.

It is true that thread_func will already be doing this copy during argument conversion (since the function takes a const std::string&, not a std::sub_match), but this occurs on the thread by which point it's too late, as your pointers are already [potentially] dangling.

like image 4
Lightness Races in Orbit Avatar answered Nov 18 '22 08:11

Lightness Races in Orbit