/* Copyright 2002 The gtkmm Development Team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include #include #include #include #include #include #include #include #include #include // For std::move() #ifdef G_OS_WIN32 #include #include #include #include #include #else #include #endif // EINTR is not defined on Tru64. I have tried including these: // #include // #include // #include // danielk: I think someone should just do a grep on a Tru64 box. Googling // for "tru64 EINTR" returns lots of hits telling me that handling EINTR is // actually a requirement on Tru64. So it must exist. #if defined(_tru64) && !defined(EINTR) #define EINTR 0 /* TODO: should use the real define */ #endif namespace Glib { class DispatchNotifier; } namespace { struct DispatchNotifyData { Glib::Dispatcher::Impl* dispatcher_impl; Glib::DispatchNotifier* notifier; DispatchNotifyData() : dispatcher_impl(nullptr), notifier(nullptr) {} DispatchNotifyData(Glib::Dispatcher::Impl* d, Glib::DispatchNotifier* n) : dispatcher_impl(d), notifier(n) {} }; static void warn_failed_pipe_io(const char* what) { #ifdef G_OS_WIN32 const char* const message = g_win32_error_message(GetLastError()); #else const char* const message = g_strerror(errno); #endif g_critical("Error in inter-thread communication: %s() failed: %s", what, message); } #ifdef G_OS_WIN32 static void fd_close_and_invalidate(HANDLE& fd) { if (fd != 0) { if (!CloseHandle(fd)) warn_failed_pipe_io("CloseHandle"); fd = 0; } } #else /* !G_OS_WIN32 */ /* * Set the close-on-exec flag on the file descriptor, * so that it won't be leaked if a new process is spawned. */ static void fd_set_close_on_exec(int fd) { const int flags = fcntl(fd, F_GETFD, 0); if (flags < 0 || fcntl(fd, F_SETFD, unsigned(flags) | FD_CLOEXEC) < 0) warn_failed_pipe_io("fcntl"); } static void fd_close_and_invalidate(int& fd) { if (fd >= 0) { int result; do result = close(fd); while (G_UNLIKELY(result < 0) && errno == EINTR); if (G_UNLIKELY(result < 0)) warn_failed_pipe_io("close"); fd = -1; } } #endif /* !G_OS_WIN32 */ } // anonymous namespace namespace Glib { // The most important reason for having the dispatcher implementation in a separate // class is that its deletion can be delayed until it's safe to delete it. // Deletion is safe when the pipe does not contain any message to the dispatcher // to delete. When the pipe is empty, it's surely safe. struct Dispatcher::Impl { public: sigc::signal signal_; DispatchNotifier* notifier_; explicit Impl(const Glib::RefPtr& context); // noncopyable Impl(const Impl&) = delete; Impl& operator=(const Impl&) = delete; }; class DispatchNotifier : public sigc::trackable { public: ~DispatchNotifier() noexcept; // noncopyable DispatchNotifier(const DispatchNotifier&) = delete; DispatchNotifier& operator=(const DispatchNotifier&) = delete; static DispatchNotifier* reference_instance(const Glib::RefPtr& context); static void unreference_instance(DispatchNotifier* notifier, Dispatcher::Impl* dispatcher_impl); void send_notification(Dispatcher::Impl* dispatcher_impl); protected: // Only used by reference_instance(). Should be private, but that triggers // a silly gcc warning even though DispatchNotifier has static methods. explicit DispatchNotifier(const Glib::RefPtr& context); private: static thread_local DispatchNotifier* thread_specific_instance_; using UniqueImplPtr = std::unique_ptr; std::forward_list orphaned_dispatcher_impl_; long ref_count_; Glib::RefPtr context_; #ifdef G_OS_WIN32 std::mutex mutex_; std::list notify_queue_; HANDLE fd_receiver_; #else int fd_receiver_; int fd_sender_; #endif void create_pipe(); bool pipe_io_handler(Glib::IOCondition condition); bool pipe_is_empty(); }; /**** Glib::DispatchNotifier ***********************************************/ thread_local DispatchNotifier* DispatchNotifier::thread_specific_instance_ = nullptr; DispatchNotifier::DispatchNotifier(const Glib::RefPtr& context) : orphaned_dispatcher_impl_(), ref_count_(0), context_(context), #ifdef G_OS_WIN32 mutex_(), notify_queue_(), fd_receiver_(0) #else fd_receiver_(-1), fd_sender_(-1) #endif { create_pipe(); try { // PollFD::fd_t is the type of GPollFD::fd. // In Windows, it has the same size as HANDLE, but it's not guaranteed to be the same type. // In Unix, a file descriptor is an int. const auto fd = (PollFD::fd_t)fd_receiver_; // The following code is equivalent to // context_->signal_io().connect( // sigc::mem_fun(*this, &DispatchNotifier::pipe_io_handler), fd, Glib::IOCondition::IO_IN); // except for source->set_can_recurse(true). const auto source = IOSource::create(fd, Glib::IOCondition::IO_IN); // If the signal emission in pipe_io_handler() starts a new main loop, // the event source shall not be blocked while that loop runs. (E.g. while // a connected slot function shows a modal dialog box.) source->set_can_recurse(true); source->connect(sigc::mem_fun(*this, &DispatchNotifier::pipe_io_handler)); g_source_attach(source->gobj(), context_->gobj()); } catch (...) { #ifndef G_OS_WIN32 fd_close_and_invalidate(fd_sender_); #endif fd_close_and_invalidate(fd_receiver_); throw; } } DispatchNotifier::~DispatchNotifier() noexcept { #ifndef G_OS_WIN32 fd_close_and_invalidate(fd_sender_); #endif fd_close_and_invalidate(fd_receiver_); } void DispatchNotifier::create_pipe() { #ifdef G_OS_WIN32 // On Win32, create a synchronization object instead of a pipe and store // its handle as fd_receiver_. Use a manual-reset event object, so that // we can closely match the behavior on Unix in pipe_io_handler(). const HANDLE event = CreateEvent(0, TRUE, FALSE, 0); if (!event) { GError* const error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_FAILED, "Failed to create event for inter-thread communication: %s", g_win32_error_message(GetLastError())); throw Glib::FileError(error); } fd_receiver_ = event; #else /* !G_OS_WIN32 */ int filedes[2] = { -1, -1 }; if (pipe(filedes) < 0) { GError* const error = g_error_new(G_FILE_ERROR, g_file_error_from_errno(errno), "Failed to create pipe for inter-thread communication: %s", g_strerror(errno)); throw Glib::FileError(error); } fd_set_close_on_exec(filedes[0]); fd_set_close_on_exec(filedes[1]); fd_receiver_ = filedes[0]; fd_sender_ = filedes[1]; #endif /* !G_OS_WIN32 */ } // static DispatchNotifier* DispatchNotifier::reference_instance( const Glib::RefPtr& context) { DispatchNotifier* instance = thread_specific_instance_; if (!instance) { instance = new DispatchNotifier(context); thread_specific_instance_ = instance; } else { // Prevent massive mess-up. g_return_val_if_fail(instance->context_ == context, nullptr); } ++instance->ref_count_; // initially 0 return instance; } // static void DispatchNotifier::unreference_instance( DispatchNotifier* notifier, Dispatcher::Impl* dispatcher_impl) { DispatchNotifier* const instance = thread_specific_instance_; // Yes, the notifier argument is only used to check for sanity. g_return_if_fail(instance == notifier); if (instance->pipe_is_empty()) { // No messages in the pipe. Delete the Dispatcher::Impl immediately. delete dispatcher_impl; instance->orphaned_dispatcher_impl_.clear(); } else { // There are messages in the pipe, possibly to the orphaned Dispatcher::Impl. // Keep it around until it can safely be deleted. // Delete all slots connected to the Dispatcher. Then the signal emission // in pipe_io_handler() will do nothing. dispatcher_impl->signal_.clear(); instance->orphaned_dispatcher_impl_.push_front(UniqueImplPtr(dispatcher_impl)); } if (--instance->ref_count_ <= 0) { g_return_if_fail(instance->ref_count_ == 0); // could be < 0 if messed up delete thread_specific_instance_; thread_specific_instance_ = nullptr; } } void DispatchNotifier::send_notification(Dispatcher::Impl* dispatcher_impl) { #ifdef G_OS_WIN32 { const std::lock_guard lock(mutex_); const bool was_empty = notify_queue_.empty(); notify_queue_.emplace_back(DispatchNotifyData(dispatcher_impl, this)); if (was_empty) { // The event will stay in signaled state until it is reset // in pipe_io_handler() after processing the last queued event. if (!SetEvent(fd_receiver_)) warn_failed_pipe_io("SetEvent"); } } #else /* !G_OS_WIN32 */ DispatchNotifyData data(dispatcher_impl, this); gssize n_written; do n_written = write(fd_sender_, &data, sizeof(data)); while (G_UNLIKELY(n_written < 0) && errno == EINTR); // All data must be written in a single call to write(), otherwise we cannot // guarantee reentrancy since another thread might be scheduled between two // write() calls. From the glibc manual: // // "Reading or writing pipe data is atomic if the size of data written is not // greater than PIPE_BUF. This means that the data transfer seems to be an // instantaneous unit, in that nothing else in the system can observe a state // in which it is partially complete. Atomic I/O may not begin right away (it // may need to wait for buffer space or for data), but once it does begin it // finishes immediately." // // The minimum value allowed by POSIX for PIPE_BUF is 512, so we are on safe // grounds here. if (G_UNLIKELY(n_written != sizeof(data))) warn_failed_pipe_io("write"); #endif /* !G_OS_WIN32 */ } bool DispatchNotifier::pipe_is_empty() { #ifdef G_OS_WIN32 return notify_queue_.empty(); #else PollFD poll_fd(fd_receiver_, Glib::IOCondition::IO_IN); // GPollFD*, number of file descriptors to poll, timeout (ms) g_poll(poll_fd.gobj(), 1, 0); return static_cast(poll_fd.get_revents() & Glib::IOCondition::IO_IN) == 0; #endif } bool DispatchNotifier::pipe_io_handler(Glib::IOCondition) { DispatchNotifyData data; #ifdef G_OS_WIN32 { const std::lock_guard lock(mutex_); // Should never be empty at this point, but let's allow for bogus // notifications with no data available anyway; just to be safe. if (notify_queue_.empty()) { if (!ResetEvent(fd_receiver_)) warn_failed_pipe_io("ResetEvent"); return true; } data = notify_queue_.front(); notify_queue_.pop_front(); // Handle only a single event with each invocation of the I/O handler, // and reset to nonsignaled state only after the last event in the queue // has been processed. This matches the behavior on Unix. if (notify_queue_.empty()) { if (!ResetEvent(fd_receiver_)) warn_failed_pipe_io("ResetEvent"); } } #else /* !G_OS_WIN32 */ gssize n_read; do n_read = read(fd_receiver_, &data, sizeof(data)); while (G_UNLIKELY(n_read < 0) && errno == EINTR); // Pipe I/O of a block size not greater than PIPE_BUF should be atomic. // See the comment on atomicity in send_notification() for details. if (G_UNLIKELY(n_read != sizeof(data))) { // Should probably never be zero, but for safety let's allow for bogus // notifications when no data is actually available. Although in fact // the read() should block in that case. if (n_read != 0) warn_failed_pipe_io("read"); return true; } #endif /* !G_OS_WIN32 */ g_return_val_if_fail(data.notifier == this, true); // Actually, we wouldn't need the try/catch block because the Glib::Source // C callback already does it for us. However, we do it anyway because the // default return value is 'false', which is not what we want. try { data.dispatcher_impl->signal_(); // emit } catch (...) { Glib::exception_handlers_invoke(); } if (!orphaned_dispatcher_impl_.empty() && pipe_is_empty()) orphaned_dispatcher_impl_.clear(); return true; } /**** Glib::Dispatcher and Glib::Dispatcher::Impl **************************/ Dispatcher::Impl::Impl(const Glib::RefPtr& context) : signal_(), notifier_(DispatchNotifier::reference_instance(context)) { } Dispatcher::Dispatcher() : impl_(new Dispatcher::Impl(MainContext::get_default())) {} Dispatcher::Dispatcher(const Glib::RefPtr& context) : impl_(new Dispatcher::Impl(context)) { } Dispatcher::~Dispatcher() noexcept { DispatchNotifier::unreference_instance(impl_->notifier_, impl_); } #ifndef GLIBMM_DISABLE_DEPRECATED void Dispatcher::emit() { impl_->notifier_->send_notification(impl_); } void Dispatcher::operator()() { impl_->notifier_->send_notification(impl_); } #endif // GLIBMM_DISABLE_DEPRECATED void Dispatcher::emit() const { impl_->notifier_->send_notification(impl_); } void Dispatcher::operator()() const { impl_->notifier_->send_notification(impl_); } sigc::connection Dispatcher::connect(const sigc::slot& slot) { return impl_->signal_.connect(slot); } sigc::connection Dispatcher::connect(sigc::slot&& slot) { return impl_->signal_.connect(std::move(slot)); } } // namespace Glib