2 * Copyright Andrey Semashev 2016.
3 * Distributed under the Boost Software License, Version 1.0.
4 * (See accompanying file LICENSE_1_0.txt or copy at
5 * http://www.boost.org/LICENSE_1_0.txt)
8 * \file windows/ipc_sync_wrappers.cpp
9 * \author Andrey Semashev
12 * \brief This header is the Boost.Log library implementation, see the library documentation
13 * at http://www.boost.org/doc/libs/release/libs/log/doc/html/index.html.
16 #include <boost/log/detail/config.hpp>
17 #include <boost/detail/winapi/access_rights.hpp>
18 #include <boost/detail/winapi/handles.hpp>
19 #include <boost/detail/winapi/event.hpp>
20 #include <boost/detail/winapi/semaphore.hpp>
21 #include <boost/detail/winapi/wait.hpp>
22 #include <boost/detail/winapi/dll.hpp>
23 #include <boost/detail/winapi/time.hpp>
24 #include <boost/detail/winapi/get_last_error.hpp>
25 #include <boost/detail/winapi/character_code_conversion.hpp>
26 #include <windows.h> // for error codes
31 #include <boost/assert.hpp>
32 #include <boost/throw_exception.hpp>
33 #include <boost/checked_delete.hpp>
34 #include <boost/memory_order.hpp>
35 #include <boost/atomic/atomic.hpp>
36 #include <boost/log/detail/snprintf.hpp>
37 #include "unique_ptr.hpp"
38 #include "windows/ipc_sync_wrappers.hpp"
39 #include <boost/log/detail/header.hpp>
43 BOOST_LOG_OPEN_NAMESPACE
47 //! Hex character table, defined in dump.cpp
48 extern const char g_hex_char_table
[2][16];
56 void interprocess_event::create(const wchar_t* name
, bool manual_reset
, permissions
const& perms
)
58 #if BOOST_USE_WINAPI_VERSION >= BOOST_WINAPI_VERSION_WIN6
59 boost::detail::winapi::HANDLE_ h
= boost::detail::winapi::CreateEventExW
61 reinterpret_cast< boost::detail::winapi::SECURITY_ATTRIBUTES_
* >(perms
.get_native()),
63 boost::detail::winapi::CREATE_EVENT_MANUAL_RESET_
* manual_reset
,
64 boost::detail::winapi::SYNCHRONIZE_
| boost::detail::winapi::EVENT_MODIFY_STATE_
67 boost::detail::winapi::HANDLE_ h
= boost::detail::winapi::CreateEventW
69 reinterpret_cast< boost::detail::winapi::SECURITY_ATTRIBUTES_
* >(perms
.get_native()),
75 if (BOOST_UNLIKELY(h
== NULL
))
77 boost::detail::winapi::DWORD_ err
= boost::detail::winapi::GetLastError();
78 BOOST_LOG_THROW_DESCR_PARAMS(boost::log::system_error
, "Failed to create an interprocess event object", (err
));
84 void interprocess_event::create_or_open(const wchar_t* name
, bool manual_reset
, permissions
const& perms
)
86 #if BOOST_USE_WINAPI_VERSION >= BOOST_WINAPI_VERSION_WIN6
87 boost::detail::winapi::HANDLE_ h
= boost::detail::winapi::CreateEventExW
89 reinterpret_cast< boost::detail::winapi::SECURITY_ATTRIBUTES_
* >(perms
.get_native()),
91 boost::detail::winapi::CREATE_EVENT_MANUAL_RESET_
* manual_reset
,
92 boost::detail::winapi::SYNCHRONIZE_
| boost::detail::winapi::EVENT_MODIFY_STATE_
95 boost::detail::winapi::HANDLE_ h
= boost::detail::winapi::CreateEventW
97 reinterpret_cast< boost::detail::winapi::SECURITY_ATTRIBUTES_
* >(perms
.get_native()),
105 const boost::detail::winapi::DWORD_ err
= boost::detail::winapi::GetLastError();
106 if (BOOST_LIKELY(err
== ERROR_ALREADY_EXISTS
))
113 BOOST_LOG_THROW_DESCR_PARAMS(boost::log::system_error
, "Failed to create an interprocess event object", (err
));
120 void interprocess_event::open(const wchar_t* name
)
122 boost::detail::winapi::HANDLE_ h
= boost::detail::winapi::OpenEventW(boost::detail::winapi::SYNCHRONIZE_
| boost::detail::winapi::EVENT_MODIFY_STATE_
, false, name
);
123 if (BOOST_UNLIKELY(h
== NULL
))
125 const boost::detail::winapi::DWORD_ err
= boost::detail::winapi::GetLastError();
126 BOOST_LOG_THROW_DESCR_PARAMS(boost::log::system_error
, "Failed to open an interprocess event object", (err
));
132 boost::atomic
< interprocess_semaphore::is_semaphore_zero_count_t
> interprocess_semaphore::is_semaphore_zero_count(&interprocess_semaphore::is_semaphore_zero_count_init
);
133 interprocess_semaphore::nt_query_semaphore_t
interprocess_semaphore::nt_query_semaphore
= NULL
;
135 void interprocess_semaphore::create_or_open(const wchar_t* name
, permissions
const& perms
)
137 #if BOOST_USE_WINAPI_VERSION >= BOOST_WINAPI_VERSION_WIN6
138 boost::detail::winapi::HANDLE_ h
= boost::detail::winapi::CreateSemaphoreExW
140 reinterpret_cast< boost::detail::winapi::SECURITY_ATTRIBUTES_
* >(perms
.get_native()),
142 (std::numeric_limits
< boost::detail::winapi::LONG_
>::max
)(), // max count
145 boost::detail::winapi::SYNCHRONIZE_
| boost::detail::winapi::SEMAPHORE_MODIFY_STATE_
| boost::detail::winapi::SEMAPHORE_QUERY_STATE_
148 boost::detail::winapi::HANDLE_ h
= boost::detail::winapi::CreateSemaphoreW
150 reinterpret_cast< boost::detail::winapi::SECURITY_ATTRIBUTES_
* >(perms
.get_native()),
152 (std::numeric_limits
< boost::detail::winapi::LONG_
>::max
)(), // max count
158 boost::detail::winapi::DWORD_ err
= boost::detail::winapi::GetLastError();
159 if (BOOST_LIKELY(err
== ERROR_ALREADY_EXISTS
))
166 BOOST_LOG_THROW_DESCR_PARAMS(boost::log::system_error
, "Failed to create an interprocess semaphore object", (err
));
173 void interprocess_semaphore::open(const wchar_t* name
)
175 boost::detail::winapi::HANDLE_ h
= boost::detail::winapi::OpenSemaphoreW(boost::detail::winapi::SYNCHRONIZE_
| boost::detail::winapi::SEMAPHORE_MODIFY_STATE_
| boost::detail::winapi::SEMAPHORE_QUERY_STATE_
, false, name
);
176 if (BOOST_UNLIKELY(h
== NULL
))
178 const boost::detail::winapi::DWORD_ err
= boost::detail::winapi::GetLastError();
179 BOOST_LOG_THROW_DESCR_PARAMS(boost::log::system_error
, "Failed to open an interprocess semaphore object", (err
));
185 bool interprocess_semaphore::is_semaphore_zero_count_init(boost::detail::winapi::HANDLE_ h
)
187 is_semaphore_zero_count_t impl
= &interprocess_semaphore::is_semaphore_zero_count_emulated
;
189 // Check if ntdll.dll provides NtQuerySemaphore, see: http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FNT%20Objects%2FSemaphore%2FNtQuerySemaphore.html
190 boost::detail::winapi::HMODULE_ ntdll
= boost::detail::winapi::GetModuleHandleW(L
"ntdll.dll");
193 nt_query_semaphore_t ntqs
= (nt_query_semaphore_t
)boost::detail::winapi::get_proc_address(ntdll
, "NtQuerySemaphore");
196 nt_query_semaphore
= ntqs
;
197 impl
= &interprocess_semaphore::is_semaphore_zero_count_nt_query_semaphore
;
201 is_semaphore_zero_count
.store(impl
, boost::memory_order_release
);
206 bool interprocess_semaphore::is_semaphore_zero_count_nt_query_semaphore(boost::detail::winapi::HANDLE_ h
)
208 semaphore_basic_information info
= {};
209 NTSTATUS_ err
= nt_query_semaphore
212 0u, // SemaphoreBasicInformation
217 if (BOOST_UNLIKELY(err
!= 0u))
219 char buf
[sizeof(unsigned int) * 2u + 4u];
220 boost::log::aux::snprintf(buf
, sizeof(buf
), "0x%08x", static_cast< unsigned int >(err
));
221 BOOST_LOG_THROW_DESCR_PARAMS(boost::log::system_error
, std::string("Failed to test an interprocess semaphore object for zero count, NT status: ") + buf
, (ERROR_INVALID_HANDLE
));
224 return info
.current_count
== 0u;
227 bool interprocess_semaphore::is_semaphore_zero_count_emulated(boost::detail::winapi::HANDLE_ h
)
229 const boost::detail::winapi::DWORD_ retval
= boost::detail::winapi::WaitForSingleObject(h
, 0u);
230 if (retval
== boost::detail::winapi::wait_timeout
)
234 else if (BOOST_UNLIKELY(retval
!= boost::detail::winapi::wait_object_0
))
236 const boost::detail::winapi::DWORD_ err
= boost::detail::winapi::GetLastError();
237 BOOST_LOG_THROW_DESCR_PARAMS(boost::log::system_error
, "Failed to test an interprocess semaphore object for zero count", (err
));
240 // Restore the decremented counter
241 BOOST_VERIFY(!!boost::detail::winapi::ReleaseSemaphore(h
, 1, NULL
));
246 #if !defined(BOOST_MSVC) || _MSC_VER >= 1800
247 BOOST_CONSTEXPR_OR_CONST
uint32_t interprocess_mutex::lock_flag_bit
;
248 BOOST_CONSTEXPR_OR_CONST
uint32_t interprocess_mutex::event_set_flag_bit
;
249 BOOST_CONSTEXPR_OR_CONST
uint32_t interprocess_mutex::lock_flag_value
;
250 BOOST_CONSTEXPR_OR_CONST
uint32_t interprocess_mutex::event_set_flag_value
;
251 BOOST_CONSTEXPR_OR_CONST
uint32_t interprocess_mutex::waiter_count_mask
;
254 void interprocess_mutex::lock_slow()
256 uint32_t old_state
= m_shared_state
->m_lock_state
.load(boost::memory_order_relaxed
);
257 mark_waiting_and_try_lock(old_state
);
259 if ((old_state
& lock_flag_value
) != 0u) try
264 clear_waiting_and_try_lock(old_state
);
266 while ((old_state
& lock_flag_value
) != 0u);
270 m_shared_state
->m_lock_state
.fetch_sub(1u, boost::memory_order_acq_rel
);
275 bool interprocess_mutex::lock_slow(boost::detail::winapi::HANDLE_ abort_handle
)
277 uint32_t old_state
= m_shared_state
->m_lock_state
.load(boost::memory_order_relaxed
);
278 mark_waiting_and_try_lock(old_state
);
280 if ((old_state
& lock_flag_value
) != 0u) try
284 if (!m_event
.wait(abort_handle
))
286 // Wait was interrupted
287 m_shared_state
->m_lock_state
.fetch_sub(1u, boost::memory_order_acq_rel
);
291 clear_waiting_and_try_lock(old_state
);
293 while ((old_state
& lock_flag_value
) != 0u);
297 m_shared_state
->m_lock_state
.fetch_sub(1u, boost::memory_order_acq_rel
);
304 inline void interprocess_mutex::mark_waiting_and_try_lock(uint32_t& old_state
)
309 uint32_t was_locked
= (old_state
& lock_flag_value
);
312 // Avoid integer overflows
313 if (BOOST_UNLIKELY((old_state
& waiter_count_mask
) == waiter_count_mask
))
314 BOOST_LOG_THROW_DESCR(limitation_error
, "Too many waiters on an interprocess mutex");
316 new_state
= old_state
+ 1u;
320 new_state
= old_state
| lock_flag_value
;
323 while (!m_shared_state
->m_lock_state
.compare_exchange_weak(old_state
, new_state
, boost::memory_order_acq_rel
, boost::memory_order_relaxed
));
326 inline void interprocess_mutex::clear_waiting_and_try_lock(uint32_t& old_state
)
328 old_state
&= ~lock_flag_value
;
329 old_state
|= event_set_flag_value
;
333 new_state
= ((old_state
& lock_flag_value
) ? old_state
: ((old_state
- 1u) | lock_flag_value
)) & ~event_set_flag_value
;
335 while (!m_shared_state
->m_lock_state
.compare_exchange_strong(old_state
, new_state
, boost::memory_order_acq_rel
, boost::memory_order_relaxed
));
339 bool interprocess_condition_variable::wait(interprocess_mutex::optional_unlock
& lock
, boost::detail::winapi::HANDLE_ abort_handle
)
341 int32_t waiters
= m_shared_state
->m_waiters
;
344 // We need to select a new semaphore to block on
345 m_current_semaphore
= get_unused_semaphore();
346 ++m_shared_state
->m_generation
;
347 m_shared_state
->m_semaphore_id
= m_current_semaphore
->m_id
;
352 // Avoid integer overflow
353 if (BOOST_UNLIKELY(waiters
>= ((std::numeric_limits
< int32_t >::max
)() - 1)))
354 BOOST_LOG_THROW_DESCR(limitation_error
, "Too many waiters on an interprocess condition variable");
356 // Make sure we use the right semaphore to block on
357 const uint32_t id
= m_shared_state
->m_semaphore_id
;
358 if (m_current_semaphore
->m_id
!= id
)
359 m_current_semaphore
= get_semaphore(id
);
362 m_shared_state
->m_waiters
= waiters
+ 1;
363 const uint32_t generation
= m_shared_state
->m_generation
;
365 boost::detail::winapi::HANDLE_ handles
[2u] = { m_current_semaphore
->m_semaphore
.get_handle(), abort_handle
};
367 interprocess_mutex
* const mutex
= lock
.disengage();
370 boost::detail::winapi::DWORD_ retval
= boost::detail::winapi::WaitForMultipleObjects(2u, handles
, false, boost::detail::winapi::INFINITE_
);
372 if (BOOST_UNLIKELY(retval
== boost::detail::winapi::WAIT_FAILED_
))
374 const boost::detail::winapi::DWORD_ err
= boost::detail::winapi::GetLastError();
376 // Although highly unrealistic, it is possible that it took so long for the current thread to enter WaitForMultipleObjects that
377 // another thread has managed to destroy the semaphore. This can happen if the semaphore remains in a non-zero state
378 // for too long, which means that another process died while being blocked on the semaphore, and the semaphore was signalled,
379 // and the non-zero state timeout has passed. In this case the most logical behavior for the wait function is to return as
380 // if because of a wakeup.
381 if (err
== ERROR_INVALID_HANDLE
)
382 retval
= boost::detail::winapi::WAIT_OBJECT_0_
;
384 BOOST_LOG_THROW_DESCR_PARAMS(boost::log::system_error
, "Failed to block on an interprocess semaphore object", (err
));
387 // Have to unconditionally lock the mutex here
391 if (generation
== m_shared_state
->m_generation
&& m_shared_state
->m_waiters
> 0)
392 --m_shared_state
->m_waiters
;
394 return retval
== boost::detail::winapi::WAIT_OBJECT_0_
;
397 //! Finds or opens a semaphore with the specified id
398 interprocess_condition_variable::semaphore_info
* interprocess_condition_variable::get_semaphore(uint32_t id
)
400 semaphore_info_set::insert_commit_data insert_state
;
401 std::pair
< semaphore_info_set::iterator
, bool > res
= m_semaphore_info_set
.insert_check(id
, semaphore_info::order_by_id(), insert_state
);
404 // We need to open the semaphore. It is possible that the semaphore does not exist because all processes that had it opened terminated.
405 // Because of this we also attempt to create it.
406 boost::log::aux::unique_ptr
< semaphore_info
> p(new semaphore_info(id
));
407 generate_semaphore_name(id
);
408 p
->m_semaphore
.create_or_open(m_semaphore_name
.c_str(), m_perms
);
410 res
.first
= m_semaphore_info_set
.insert_commit(*p
, insert_state
);
411 m_semaphore_info_list
.push_back(*p
);
417 // Move the semaphore to the end of the list so that the next time we are less likely to use it
418 semaphore_info
& info
= *res
.first
;
419 m_semaphore_info_list
.erase(m_semaphore_info_list
.iterator_to(info
));
420 m_semaphore_info_list
.push_back(info
);
426 //! Finds or creates a semaphore with zero counter
427 interprocess_condition_variable::semaphore_info
* interprocess_condition_variable::get_unused_semaphore()
429 // Be optimistic, check the current semaphore first
430 if (m_current_semaphore
&& m_current_semaphore
->m_semaphore
.is_zero_count())
432 mark_unused(*m_current_semaphore
);
433 return m_current_semaphore
;
436 const tick_count_clock::time_point now
= tick_count_clock::now();
438 semaphore_info_list::iterator it
= m_semaphore_info_list
.begin(), end
= m_semaphore_info_list
.end();
441 if (is_overflow_less(m_next_semaphore_id
, it
->m_id
) || m_next_semaphore_id
== it
->m_id
)
442 m_next_semaphore_id
= it
->m_id
+ 1u;
444 if (it
->m_semaphore
.is_zero_count())
446 semaphore_info
& info
= *it
;
450 else if (it
->check_non_zero_timeout(now
))
452 // The semaphore is non-zero for too long. A blocked process must have crashed. Close it.
453 m_semaphore_info_set
.erase(m_semaphore_info_set
.iterator_to(*it
));
454 m_semaphore_info_list
.erase_and_dispose(it
++, boost::checked_deleter
< semaphore_info
>());
462 // No semaphore found, create a new one
463 for (uint32_t semaphore_id
= m_next_semaphore_id
, semaphore_id_end
= semaphore_id
- 1u; semaphore_id
!= semaphore_id_end
; ++semaphore_id
)
465 interprocess_semaphore sem
;
468 generate_semaphore_name(semaphore_id
);
469 sem
.create_or_open(m_semaphore_name
.c_str(), m_perms
);
470 if (!sem
.is_zero_count())
475 // Ignore errors, try the next one
479 semaphore_info
* p
= NULL
;
480 semaphore_info_set::insert_commit_data insert_state
;
481 std::pair
< semaphore_info_set::iterator
, bool > res
= m_semaphore_info_set
.insert_check(semaphore_id
, semaphore_info::order_by_id(), insert_state
);
484 p
= new semaphore_info(semaphore_id
);
485 p
->m_semaphore
.swap(sem
);
487 res
.first
= m_semaphore_info_set
.insert_commit(*p
, insert_state
);
488 m_semaphore_info_list
.push_back(*p
);
492 // Some of our currently open semaphores must have been released by another thread
497 m_next_semaphore_id
= semaphore_id
+ 1u;
502 BOOST_LOG_THROW_DESCR(limitation_error
, "Too many semaphores are actively used for an interprocess condition variable");
503 BOOST_LOG_UNREACHABLE_RETURN(NULL
);
506 //! Marks the semaphore info as unused and moves to the end of list
507 inline void interprocess_condition_variable::mark_unused(semaphore_info
& info
) BOOST_NOEXCEPT
509 // Restart the timeout for non-zero state next time we search for an unused semaphore
510 info
.m_checked_for_zero
= false;
511 // Move to the end of the list so that we consider this semaphore last
512 m_semaphore_info_list
.erase(m_semaphore_info_list
.iterator_to(info
));
513 m_semaphore_info_list
.push_back(info
);
516 //! Generates semaphore name according to id
517 inline void interprocess_condition_variable::generate_semaphore_name(uint32_t id
) BOOST_NOEXCEPT
519 // Note: avoid anything that involves locale to make semaphore names as stable as possible
520 BOOST_ASSERT(m_semaphore_name
.size() >= 8u);
522 wchar_t* p
= &m_semaphore_name
[m_semaphore_name
.size() - 8u];
523 *p
++ = boost::log::aux::g_hex_char_table
[0][id
>> 28];
524 *p
++ = boost::log::aux::g_hex_char_table
[0][(id
>> 24) & 0x0000000Fu
];
526 *p
++ = boost::log::aux::g_hex_char_table
[0][(id
>> 20) & 0x0000000Fu
];
527 *p
++ = boost::log::aux::g_hex_char_table
[0][(id
>> 16) & 0x0000000Fu
];
529 *p
++ = boost::log::aux::g_hex_char_table
[0][(id
>> 12) & 0x0000000Fu
];
530 *p
++ = boost::log::aux::g_hex_char_table
[0][(id
>> 8) & 0x0000000Fu
];
532 *p
++ = boost::log::aux::g_hex_char_table
[0][(id
>> 4) & 0x0000000Fu
];
533 *p
= boost::log::aux::g_hex_char_table
[0][id
& 0x0000000Fu
];
540 BOOST_LOG_CLOSE_NAMESPACE
// namespace log
544 #include <boost/log/detail/footer.hpp>