Opened 10 years ago

Closed 9 years ago

#7461 closed Bugs (fixed)

detail::win32::ReleaseSemaphore may be called with count_to_release equal to 0

Reported by: Jesper Storm Bache <jsbache@…> Owned by: viboes
Milestone: Boost 1.55.0 Component: thread
Version: Boost 1.51.0 Severity: Problem
Keywords: Cc:

Description

ReleaseSemaphore is documented by Microsoft as: "... lReleaseCount [in] The amount by which the semaphore object's current count is to be increased. The value must be greater than zero" http://msdn.microsoft.com/en-us/library/windows/desktop/ms685071(v=vs.85).aspx

When I run boundschecker on boost threads, I get a error because count_to_release is 0 in the following:

void release(unsigned count_to_release) {

notified=true; detail::win32::ReleaseSemaphore(semaphore,count_to_release,0);

}

(this is line 71 in boost_1_51_0/boost/thread/win32/condition_variable.hpp)

Change History (18)

comment:1 by Jesper Storm Bache <jsbache@…>, 10 years ago

Summary: detail::win32::ReleaseSemaphore may be called with count_to_release euql to 0detail::win32::ReleaseSemaphore may be called with count_to_release equal to 0

comment:2 by viboes, 10 years ago

Owner: changed from Anthony Williams to viboes
Status: newassigned
Version: Boost 1.52.0Boost 1.51.0

comment:3 by viboes, 10 years ago

Hi,

I don't know how the tool you are using has deduced such an issue.

The count_to_release parameter seems to be always > 0.

Please let me know if I'm missing something.

Either it is called with 1

                    for(generation_list::iterator it=generations.begin(),
                            end=generations.end();
                        it!=end;++it)
                    {
                        (*it)->release(1);
                    }

or with the contents of waiters.

            void release_waiters()
            {
                release(detail::interlocked_read_acquire(&waiters));
            }

This variable is initialized as follows

                waiters(1),notified(false),references(0)

and updated by

            void add_waiter()
            {
                BOOST_INTERLOCKED_INCREMENT(&waiters);
            }

            void remove_waiter()
            {
                BOOST_INTERLOCKED_DECREMENT(&waiters);
            }

But remove_waiter is always called after add_waiter

            entry_ptr get_wait_entry()
            {
                boost::lock_guard<boost::mutex> internal_lock(internal_mutex);

                if(!wake_sem)
                {
                    wake_sem=detail::win32::create_anonymous_semaphore(0,LONG_MAX);
                    BOOST_ASSERT(wake_sem);
                }

                detail::interlocked_write_release(&total_count,total_count+1);
                if(generations.empty() || generations.back()->is_notified())
                {
                    entry_ptr new_entry(new list_entry(wake_sem));
                    generations.push_back(new_entry);
                    return new_entry;
                }
                else
                {
                    generations.back()->add_waiter();
                    return generations.back();
                }
            }

            struct entry_manager
            {
                entry_ptr const entry;

                BOOST_THREAD_NO_COPYABLE(entry_manager)
                entry_manager(entry_ptr const& entry_):
                    entry(entry_)
                {}

                ~entry_manager()
                {
                    entry->remove_waiter();
                }

                list_entry* operator->()
                {
                    return entry.get();
                }
            };


        protected:
            template<typename lock_type>
            bool do_wait(lock_type& lock,timeout abs_time)
            {
                relocker<lock_type> locker(lock);

                entry_manager entry(get_wait_entry());

                locker.unlock();

                bool woken=false;
                while(!woken)
                {
                    if(!entry->wait(abs_time))
                    {
                        return false;
                    }

                    woken=entry->woken();
                }
                return woken;
            }

Any deep explanation of on which circumstances this issue appear is welcome.

comment:4 by jsbache@…, 10 years ago

I apologize for the terseness. Here are a few detais. When I notice a zero count, the stack is:

c4test.exe!boost::detail::basic_cv_list_entry::release(unsigned int count_to_release=0) Line 68 c4test.exe!boost::detail::basic_cv_list_entry::release_waiters() Line 73

c4test.exe!boost::detail::basic_condition_variable::notify_all() Line 285 + 0x1a bytes

My conditional breakpoint (condition: "count_to_release == 0) is on the following line (before we modify notified)

   void release(unsigned count_to_release)
   {
   --break--       notified=true;

When I hit this, code I have the following state of the local variables. Note that notified = true at this point in time,

it	{px=0x0000000001dde7b0 }
	[ptr]	0x0000000001dd75b8 {px=0x0000000001dde7b0 }
	px	0x0000000001dde7b0
	semaphore	{handle_to_manage=0x0000000000000100 }
	wake_sem	{handle_to_manage=0x0000000000000184 }
	waiters		0	
	notified	true
	references	1

this	0x00000000003dee28
	internal_mutex	{...}	boost::mutex
	total_count	0	long
	active_generation_count	0	unsigned int
	generations	[2]({px=0x0000000001dde630 },{px=0x0000000001dde7b0 })
	wake_sem	{handle_to_manage=0x0000000000000198 }

comment:5 by viboes, 10 years ago

Thanks for the details. Please, could you attach the example? Could you see when waiters become 0?

Last edited 10 years ago by viboes (previous) (diff)

comment:6 by viboes, 10 years ago

Hi again,

I think I have an hint. Could you check if defining ~entry_manager as follows avoid the problem?

                ~entry_manager()
                {
                  if(! entry->is_notified())
                  {
                    entry->remove_waiter();
                  }
                }

comment:7 by jsbache@…, 10 years ago

The proposed solution does solve the issue, meaning that with this change I no longer observe a call to basic_cv_list_entry::release with count_to_release equal to 0.

in reply to:  7 comment:8 by viboes, 10 years ago

Replying to jsbache@…:

The proposed solution does solve the issue, meaning that with this change I no longer observe a call to basic_cv_list_entry::release with count_to_release equal to 0.

Thanks for checking. I will apply the modification and run the test on windows as soon as i have access to a windows machine.

comment:9 by viboes, 10 years ago

Milestone: To Be DeterminedBoost 1.52.0

comment:10 by viboes, 10 years ago

Resolution: fixed
Status: assignedclosed

Committed revision [81024].

comment:11 by viboes, 10 years ago

Resolution: fixed
Status: closedreopened

Hi,

it seems that the patch introduce a severe restriction, see #7657. I will rollback the change and take the time to analyze your issue.

comment:12 by viboes, 10 years ago

Milestone: Boost 1.52.0To Be Determined

comment:13 by viboes, 9 years ago

Status: reopenednew

comment:14 by viboes, 9 years ago

Status: newassigned

comment:15 by viboes, 9 years ago

Hi,

I don't reach to understand how your issue can arrive.

Could you add a small example reproduce the issue?

Last edited 9 years ago by viboes (previous) (diff)

comment:16 by viboes, 9 years ago

I suspect that the following sequence is possible.

  1. thread A call wait and it stands in [1] below. The value for waiters is 1.
  2. thread B notify_all and after wake the waiters in [2] the scheduler choose the ready thread A.
  3. thread A ends the call to do_wait and the entry_manager destructor calls. The value for waiters is 0. in [3] the scheduler choose the ready thread B.
  4. thread B calls release_waiters() in [4] which calls to release(0).

Current Code

            template<typename lock_type>
            bool do_wait(lock_type& lock,timeout abs_time)
            {
                relocker<lock_type> locker(lock);

                entry_manager entry(get_wait_entry());

                locker.unlock();

                bool woken=false;
                while(!woken)
                {
                    if(!entry->wait(abs_time)) // [1]
                    {
                        return false;
                    }

                    woken=entry->woken();
                }
                return woken;
            } // [3]

            void notify_all() BOOST_NOEXCEPT
            {
                if(detail::interlocked_read_acquire(&total_count))
                {
                    boost::lock_guard<boost::mutex> internal_lock(internal_mutex);
                    if(!total_count)
                    {
                        return;
                    }
                    wake_waiters(total_count); // [2]
                    for(generation_list::iterator it=generations.begin(),
                            end=generations.end();
                        it!=end;++it)
                    {
                        (*it)->release_waiters(); [4]
                    }
                    generations.clear();
                    wake_sem=detail::win32::handle(0);
                }
            }

I suspect that the call to entry->remove_waiter() inside entry_manager() must be protected

Could some one check if the following patch for boost/thread/win32/condition_variable.hpp works

===================================================================
--- condition_variable.hpp	(revision 85663)
+++ condition_variable.hpp	(working copy)
@@ -191,18 +191,17 @@
             struct entry_manager
             {
                 entry_ptr const entry;
+                boost::mutex& internal_mutex;
 
                 BOOST_THREAD_NO_COPYABLE(entry_manager)
-                entry_manager(entry_ptr const& entry_):
-                    entry(entry_)
+                entry_manager(entry_ptr const& entry_, boost::mutex& mutex_):
+                    entry(entry_), internal_mutex(mutex_)
                 {}
 
                 ~entry_manager()
                 {
-                  //if(! entry->is_notified()) // several regression #7657
-                  {
+                    boost::lock_guard<boost::mutex> internal_lock(internal_mutex);
                     entry->remove_waiter();
-                  }
                 }
 
                 list_entry* operator->()
@@ -218,7 +217,7 @@
             {
                 relocker<lock_type> locker(lock);
 
-                entry_manager entry(get_wait_entry());
+                entry_manager entry(get_wait_entry(), internal_mutex);
 
                 locker.unlock();
 

comment:17 by viboes, 9 years ago

Milestone: To Be DeterminedBoost 1.55.0

Committed revision [85733].

comment:18 by viboes, 9 years ago

Resolution: fixed
Status: assignedclosed

Committed revision [85815].

Note: See TracTickets for help on using tickets.