Using WaitForSingleObject on Windows but supporting boost thread interrupts

872 views Asked by At

Boost thread's have the convenient feature of being "interruptible". The framework introduces interruption points when you sleep, etc. However, using blocking Win32 calls circumvent this feature. For example, WaitForSingleObject will block a thread but won't let it be interrupted by boost thread's interruption mechanism.

Is there some way to wrap WaitForSingleObject or tell boost to wait on a Win32 event handle so that I can regain the interruption point?

1

There are 1 answers

0
sehe On BEST ANSWER

detail::win32::interruptible_wait implements just this.

As you can see it awaits 3 handles (2 in addition to the one specified by the caller) to honour interruption.

See specifically

  • the WaitForMultipleObjectsEx call
  • the block

    else if(notified_index==interruption_index)
    {
        detail::win32::ResetEvent(detail::get_current_thread_data()->interruption_handle);
        throw thread_interrupted();
    }
    

For reference, Boost License:

bool interruptible_wait(detail::win32::handle handle_to_wait_for,detail::timeout target_time)
{
    detail::win32::handle handles[3]={0};
    unsigned handle_count=0;
    unsigned wait_handle_index=~0U;
#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS
    unsigned interruption_index=~0U;
#endif
    unsigned timeout_index=~0U;
    if(handle_to_wait_for!=detail::win32::invalid_handle_value)
    {
        wait_handle_index=handle_count;
        handles[handle_count++]=handle_to_wait_for;
    }
#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS
    if(detail::get_current_thread_data() && detail::get_current_thread_data()->interruption_enabled)
    {
        interruption_index=handle_count;
        handles[handle_count++]=detail::get_current_thread_data()->interruption_handle;
    }
#endif
    detail::win32::handle_manager timer_handle;

#ifndef UNDER_CE
#if !BOOST_PLAT_WINDOWS_RUNTIME
    unsigned const min_timer_wait_period=20;

    if(!target_time.is_sentinel())
    {
        detail::timeout::remaining_time const time_left=target_time.remaining_milliseconds();
        if(time_left.milliseconds > min_timer_wait_period)
        {
            // for a long-enough timeout, use a waitable timer (which tracks clock changes)
            timer_handle=CreateWaitableTimer(NULL,false,NULL);
            if(timer_handle!=0)
            {
                LARGE_INTEGER due_time=get_due_time(target_time);

                bool const set_time_succeeded=SetWaitableTimer(timer_handle,&due_time,0,0,0,false)!=0;
                if(set_time_succeeded)
                {
                    timeout_index=handle_count;
                    handles[handle_count++]=timer_handle;
                }
            }
        }
        else if(!target_time.relative)
        {
            // convert short absolute-time timeouts into relative ones, so we don't race against clock changes
            target_time=detail::timeout(time_left.milliseconds);
        }
    }
#endif
#endif

    bool const using_timer=timeout_index!=~0u;
    detail::timeout::remaining_time time_left(0);

    do
    {
        if(!using_timer)
        {
            time_left=target_time.remaining_milliseconds();
        }

        if(handle_count)
        {
            unsigned long const notified_index=detail::win32::WaitForMultipleObjectsEx(handle_count,handles,false,using_timer?INFINITE:time_left.milliseconds, 0);
            if(notified_index<handle_count)
            {
                if(notified_index==wait_handle_index)
                {
                    return true;
                }
#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS
                else if(notified_index==interruption_index)
                {
                    detail::win32::ResetEvent(detail::get_current_thread_data()->interruption_handle);
                    throw thread_interrupted();
                }
#endif
                else if(notified_index==timeout_index)
                {
                    return false;
                }
            }
        }
        else
        {
            detail::win32::sleep(time_left.milliseconds);
        }
        if(target_time.relative)
        {
            target_time.milliseconds-=detail::timeout::max_non_infinite_wait;
        }
    }
    while(time_left.more);
    return false;
}