Opened 6 years ago

Closed 5 years ago

#12657 closed Support Requests (obsolete)

thread local storage from thread in base object is not available in derived object (TLS_OUT_OF_INDEXES)

Reported by: monfollet.romain@… Owned by: Anthony Williams
Milestone: To Be Determined Component: thread
Version: Boost 1.62.0 Severity: Problem
Keywords: Cc:

Description (last modified by viboes)

Introduction: I have a class A. It implements the Active Object design pattern. Class A has a thread that pops tasks one at a time ande executes it in the thread.

Some class B, inheriting from A, can send tasks to the underlying A active object mechanism. When piping a boost::this_thread::sleep_for(....) into a task, and when finally at the same time the task is being executing in A's thread, and you interrupt A's thread, the call to boost::this_thread::sleep_for() does not throw any boost::thread_interrupted, thus, i'm waiting for the passed time to sleep_for to destroy the object. There's no reason the same should not apply to boost::condition_varible::wait , which leads to program blocking for the destruction of object of Type B.

preconditions:

   class active_object
   {
   public:
      using message = boost::function<void()>;

      FOUNDATION_DLL_API active_object();
      virtual FOUNDATION_DLL_API ~active_object();
      virtual FOUNDATION_DLL_API void stop();
      virtual FOUNDATION_DLL_API void start();

   protected:
      virtual FOUNDATION_DLL_API void put_message(message const&);
      virtual FOUNDATION_DLL_API void process_messages();
      FOUNDATION_DLL_API boost::thread::id get_id() const;
      FOUNDATION_DLL_API void interrupt();
   private:
      using default_thread = boost::scoped_thread<boost::interrupt_and_join_if_joinable>;
      blocking_queue<message> _messages;
      default_thread _worker;

//////////////////////////////////////////////////////////////////
   active_object::active_object() : 
      _messages(3000),
      _continue(true)
   {
      _worker = std::move(default_thread([this](){
         process_messages(); 
      }));


   }


   active_object::~active_object()
   {
      _continue = false;
      _worker.interrupt();
   }

   void active_object::put_message(message const& message){
      _messages.push(message);
   }
   void active_object::interrupt(){
      boost::this_thread::sleep_for(boost::chrono::seconds(10));
   }

   void active_object::process_messages(){

      try{
         while (_continue){
            try{
               auto message = _messages.pop();
               message();
            }
            catch (std::exception const&){
            }
         }
      }
      catch (boost::thread_interrupted const&){
         std::clog << std::endl << __FUNCTION__ << " thread interrupted" << std::endl;
      }
   }

   void active_object::start(){
      if (!_continue){
         _continue = true;
         _worker = std::move(default_thread([this](){process_messages(); }));
      }
   }

   void active_object::stop(){
      _continue = false;
      _worker.interrupt();
   }

   boost::thread::id active_object::get_id() const{
      return _worker.get_id();
   }

//////////////////////////////////////////////////////////////////
   class interrupting_active_object: public foundation::active_object{
   public:
      //virtual ~mock_active_object(){}
      void spend_time(){
         put_message([this](){ 
            active_object::interrupt(); 
         });
      }
//////////////////////////////////////////////////////////////////
   class failing_active_object: public foundation::active_object{
   public:
      //virtual ~mock_active_object(){}
      void spend_time(){
         put_message([this]({
            failing_active_object::wait_impl(boost::chrono::milliseconds(5000));
         });
      }
      private:
      void wait_impl(boost::chrono::milliseconds time){
         boost::this_thread::sleep_for(time);
      }
   }

Observations:

When calling active_object::interrupt(), the interruption works because the sleep_for is in the same memory space defined by object of class A. when calling failing_active_object::wait_impl(boost::chrono::milliseconds time), sleep_for from the derived class object memory, the thread local storage is not available (TLS_OUT_OF_INDEXES). Thus, it's not possible to interrupt the sleep_for call, leading to waiting. If sleep_for were to be replaced by boost::condition_varible::wait, i might very well wait for ever for the object destruction.

Conclusion calls to sleep_for from the object memory space of a derived class, knowing the executing thread is in the base class object memory, leads to Thread Local Storage from boost not being available. interruption behavior is broken through inheritance. calls to sleep_for from the object memory space of the base class, knowing the executing thread is in the base class object memory, leads to Thread Local Storage from boost being available. interruption behavior is preserved.

Change History (7)

comment:1 by viboes, 6 years ago

Component: threadsthread

comment:2 by viboes, 6 years ago

Description: modified (diff)

comment:3 by viboes, 6 years ago

Hi,

please, could you use boost::move instead of std::move.

I don't understand what you mean by "the sleep_for is in the same memory space defined by object of class A". Could you clarify?

What is the type of _continue?

From which thread local storage are you talking off?

comment:4 by viboes, 6 years ago

Please, don't use interrupt to mean sleep_for. It is troubling as Boost.Thread provides thread interruption.

comment:5 by viboes, 6 years ago

Moved to support request until it is clear this is a bug.

comment:6 by viboes, 5 years ago

Type: BugsSupport Requests

comment:7 by viboes, 5 years ago

Resolution: obsolete
Status: newclosed

Closed as there is no follow up.

Note: See TracTickets for help on using tickets.