After investigation into this, I discovered that there was a race condition in TemporalFormat::match():
TemporalFormat::_re is the compiled PCRE regular expression object inside each of the TemporalFormat objects, which are shared among all threads and live in global scope.
Unfortunately, TemporalFormat::match() was using TemporalFormat::_match_vector as its match state. At high concurrency, this means that the following could happen:
Thread 1 executes pcre_exec() and finds a match, therefore populating TemporalFormat::_match_vector of integers with the position offsets of the matched pieces of the temporal object.
Thread 1, during construction of the Temporal output of TemporalFormat::match(), uses these _match_vector position offsets in calling std::string::substr on a copy of the matched string, essentially "cutting up" the string into year, month, day, etc.
Thread 2 executes pcre_exec() and also finds a match, thereby changing TemporalFormat::_match_vector to something different
Thread 1 continues trying to use std::string::substr(), but now uses offsets that are invalid for its string, thereby producing an out_of_range exception.
The solution is to pull the TemporalFormat::_match_vector member variable and instead put a function-scope-level match_vector variable on the stack inside TemporalFormat::match().
After investigation into this, I discovered that there was a race condition in TemporalFormat: :match( ):
TemporalFormat::_re is the compiled PCRE regular expression object inside each of the TemporalFormat objects, which are shared among all threads and live in global scope.
Unfortunately, TemporalFormat: :match( ) was using TemporalFormat: :_match_ vector as its match state. At high concurrency, this means that the following could happen:
Thread 1 executes pcre_exec() and finds a match, therefore populating TemporalFormat: :_match_ vector of integers with the position offsets of the matched pieces of the temporal object.
Thread 1, during construction of the Temporal output of TemporalFormat: :match( ), uses these _match_vector position offsets in calling std::string::substr on a copy of the matched string, essentially "cutting up" the string into year, month, day, etc.
Thread 2 executes pcre_exec() and also finds a match, thereby changing TemporalFormat: :_match_ vector to something different
Thread 1 continues trying to use std::string: :substr( ), but now uses offsets that are invalid for its string, thereby producing an out_of_range exception.
The solution is to pull the TemporalFormat: :_match_ vector member variable and instead put a function- scope-level match_vector variable on the stack inside TemporalFormat: :match( ).