1 /* Copyright 2002 The gtkmm Development Team
2  *
3  * This library is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU Lesser General Public
5  * License as published by the Free Software Foundation; either
6  * version 2.1 of the License, or (at your option) any later version.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public
14  * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #ifndef GLIBMM_CAN_USE_THREAD_LOCAL
18 #include <glibmm/threads.h>
19 #endif
20 
21 #include <glibmm/dispatcher.h>
22 #include <glibmm/exceptionhandler.h>
23 #include <glibmm/fileutils.h>
24 #include <glibmm/main.h>
25 
26 #include <cerrno>
27 #include <fcntl.h>
28 #include <glib.h>
29 #include <set>
30 #include <utility> // For std::move()
31 
32 #ifdef G_OS_WIN32
33 #include <windows.h>
34 #include <io.h>
35 #include <direct.h>
36 #include <list>
37 #include <mutex>
38 #else
39 #include <unistd.h>
40 #endif
41 
42 // EINTR is not defined on Tru64. I have tried including these:
43 // #include <sys/types.h>
44 // #include <sys/statvfs.h>
45 // #include <signal.h>
46 // danielk:  I think someone should just do a grep on a Tru64 box.  Googling
47 // for "tru64 EINTR" returns lots of hits telling me that handling EINTR is
48 // actually a requirement on Tru64.  So it must exist.
49 #if defined(_tru64) && !defined(EINTR)
50 #define EINTR 0 /* TODO: should use the real define */
51 #endif
52 
53 namespace
54 {
55 
56 struct DispatchNotifyData
57 {
58   Glib::Dispatcher* dispatcher;
59   Glib::DispatchNotifier* notifier;
60 
DispatchNotifyData__anon4836a13d0111::DispatchNotifyData61   DispatchNotifyData() : dispatcher(nullptr), notifier(nullptr) {}
62 
DispatchNotifyData__anon4836a13d0111::DispatchNotifyData63   DispatchNotifyData(Glib::Dispatcher* d, Glib::DispatchNotifier* n) : dispatcher(d), notifier(n) {}
64 };
65 
66 static void
warn_failed_pipe_io(const char * what)67 warn_failed_pipe_io(const char* what)
68 {
69 #ifdef G_OS_WIN32
70   const char* const message = g_win32_error_message(GetLastError());
71 #else
72   const char* const message = g_strerror(errno);
73 #endif
74   g_critical("Error in inter-thread communication: %s() failed: %s", what, message);
75 }
76 
77 #ifdef G_OS_WIN32
78 
79 static void
fd_close_and_invalidate(HANDLE & fd)80 fd_close_and_invalidate(HANDLE& fd)
81 {
82   if (fd != 0)
83   {
84     if (!CloseHandle(fd))
85       warn_failed_pipe_io("CloseHandle");
86 
87     fd = 0;
88   }
89 }
90 #else /* !G_OS_WIN32 */
91 /*
92  * Set the close-on-exec flag on the file descriptor,
93  * so that it won't be leaked if a new process is spawned.
94  */
95 static void
fd_set_close_on_exec(int fd)96 fd_set_close_on_exec(int fd)
97 {
98   const int flags = fcntl(fd, F_GETFD, 0);
99 
100   if (flags < 0 || fcntl(fd, F_SETFD, unsigned(flags) | FD_CLOEXEC) < 0)
101     warn_failed_pipe_io("fcntl");
102 }
103 
104 static void
fd_close_and_invalidate(int & fd)105 fd_close_and_invalidate(int& fd)
106 {
107   if (fd >= 0)
108   {
109     int result;
110 
111     do
112       result = close(fd);
113     while (G_UNLIKELY(result < 0) && errno == EINTR);
114 
115     if (G_UNLIKELY(result < 0))
116       warn_failed_pipe_io("close");
117 
118     fd = -1;
119   }
120 }
121 #endif /* !G_OS_WIN32 */
122 
123 } // anonymous namespace
124 
125 namespace Glib
126 {
127 
128 class DispatchNotifier : public sigc::trackable
129 {
130 public:
131   ~DispatchNotifier() noexcept;
132 
133   // noncopyable
134   DispatchNotifier(const DispatchNotifier&) = delete;
135   DispatchNotifier& operator=(const DispatchNotifier&) = delete;
136 
137   static DispatchNotifier* reference_instance(
138     const Glib::RefPtr<MainContext>& context, const Dispatcher* dispatcher);
139   static void unreference_instance(DispatchNotifier* notifier, const Dispatcher* dispatcher);
140 
141   void send_notification(Dispatcher* dispatcher);
142 
143 protected:
144   // Only used by reference_instance().  Should be private, but that triggers
145   // a silly gcc warning even though DispatchNotifier has static methods.
146   explicit DispatchNotifier(const Glib::RefPtr<MainContext>& context);
147 
148 private:
149 #ifdef GLIBMM_CAN_USE_THREAD_LOCAL
150   static thread_local DispatchNotifier* thread_specific_instance_;
151 #else
152   static Glib::Threads::Private<DispatchNotifier> thread_specific_instance_;
153 #endif
154 
155   std::set<const Dispatcher*> deleted_dispatchers_;
156 
157   long ref_count_;
158   Glib::RefPtr<MainContext> context_;
159 #ifdef G_OS_WIN32
160   std::mutex mutex_;
161   std::list<DispatchNotifyData> notify_queue_;
162   HANDLE fd_receiver_;
163 #else
164   int fd_receiver_;
165   int fd_sender_;
166 #endif
167 
168   void create_pipe();
169   bool pipe_io_handler(Glib::IOCondition condition);
170   bool pipe_is_empty();
171 };
172 
173 /**** Glib::DispatchNotifier ***********************************************/
174 
175 // static
176 
177 #ifdef GLIBMM_CAN_USE_THREAD_LOCAL
178 thread_local DispatchNotifier* DispatchNotifier::thread_specific_instance_ = nullptr;
179 #else
180 Glib::Threads::Private<DispatchNotifier> DispatchNotifier::thread_specific_instance_;
181 #endif
182 
DispatchNotifier(const Glib::RefPtr<MainContext> & context)183 DispatchNotifier::DispatchNotifier(const Glib::RefPtr<MainContext>& context)
184 : deleted_dispatchers_(),
185   ref_count_(0),
186   context_(context),
187 #ifdef G_OS_WIN32
188   mutex_(),
189   notify_queue_(),
190   fd_receiver_(0)
191 #else
192   fd_receiver_(-1),
193   fd_sender_(-1)
194 #endif
195 {
196   create_pipe();
197 
198   try
199   {
200     // PollFD::fd_t is the type of GPollFD::fd.
201     // In Windows, it has the same size as HANDLE, but it's not guaranteed to be the same type.
202     // In Unix, a file descriptor is an int.
203     const auto fd = (PollFD::fd_t)fd_receiver_;
204 
205     // The following code is equivalent to
206     // context_->signal_io().connect(
207     //   sigc::mem_fun(*this, &DispatchNotifier::pipe_io_handler), fd, Glib::IO_IN);
208     // except for source->set_can_recurse(true).
209 
210     const auto source = IOSource::create(fd, Glib::IO_IN);
211 
212     // If the signal emission in pipe_io_handler() starts a new main loop,
213     // the event source shall not be blocked while that loop runs. (E.g. while
214     // a connected slot function shows a modal dialog box.)
215     source->set_can_recurse(true);
216 
217     source->connect(sigc::mem_fun(*this, &DispatchNotifier::pipe_io_handler));
218     g_source_attach(source->gobj(), context_->gobj());
219   }
220   catch (...)
221   {
222 #ifndef G_OS_WIN32
223     fd_close_and_invalidate(fd_sender_);
224 #endif
225     fd_close_and_invalidate(fd_receiver_);
226 
227     throw;
228   }
229 }
230 
~DispatchNotifier()231 DispatchNotifier::~DispatchNotifier() noexcept
232 {
233 #ifndef G_OS_WIN32
234   fd_close_and_invalidate(fd_sender_);
235 #endif
236   fd_close_and_invalidate(fd_receiver_);
237 }
238 
239 void
create_pipe()240 DispatchNotifier::create_pipe()
241 {
242 #ifdef G_OS_WIN32
243 
244   // On Win32, create a synchronization object instead of a pipe and store
245   // its handle as fd_receiver_.  Use a manual-reset event object, so that
246   // we can closely match the behavior on Unix in pipe_io_handler().
247   const HANDLE event = CreateEvent(0, TRUE, FALSE, 0);
248 
249   if (!event)
250   {
251     GError* const error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_FAILED,
252       "Failed to create event for inter-thread communication: %s",
253       g_win32_error_message(GetLastError()));
254     throw Glib::FileError(error);
255   }
256 
257   fd_receiver_ = event;
258 
259 #else /* !G_OS_WIN32 */
260 
261   int filedes[2] = { -1, -1 };
262 
263   if (pipe(filedes) < 0)
264   {
265     GError* const error = g_error_new(G_FILE_ERROR, g_file_error_from_errno(errno),
266       "Failed to create pipe for inter-thread communication: %s", g_strerror(errno));
267     throw Glib::FileError(error);
268   }
269 
270   fd_set_close_on_exec(filedes[0]);
271   fd_set_close_on_exec(filedes[1]);
272 
273   fd_receiver_ = filedes[0];
274   fd_sender_ = filedes[1];
275 
276 #endif /* !G_OS_WIN32 */
277 }
278 
279 // static
280 DispatchNotifier*
reference_instance(const Glib::RefPtr<MainContext> & context,const Dispatcher * dispatcher)281 DispatchNotifier::reference_instance(
282   const Glib::RefPtr<MainContext>& context, const Dispatcher* dispatcher)
283 {
284 #ifdef GLIBMM_CAN_USE_THREAD_LOCAL
285   DispatchNotifier* instance = thread_specific_instance_;
286 #else
287   DispatchNotifier* instance = thread_specific_instance_.get();
288 #endif
289 
290   if (!instance)
291   {
292     instance = new DispatchNotifier(context);
293 #ifdef GLIBMM_CAN_USE_THREAD_LOCAL
294     thread_specific_instance_ = instance;
295 #else
296     thread_specific_instance_.replace(instance);
297 #endif
298   }
299   else
300   {
301     // Prevent massive mess-up.
302     g_return_val_if_fail(instance->context_ == context, nullptr);
303 
304     // In the possible but unlikely case that a new dispatcher gets the same
305     // address as a newly deleted one, if the pipe still contains messages to
306     // the deleted dispatcher, those messages will be delivered to the new one.
307     // Not ideal, but perhaps the best that can be done without breaking ABI.
308     // The alternative would be to remove the following erase(), and risk not
309     // delivering messages sent to the new dispatcher.
310     // TODO: When we can break ABI, a better solution without this drawback can
311     // be implemented. See https://bugzilla.gnome.org/show_bug.cgi?id=651942
312     // especially comment 16.
313     instance->deleted_dispatchers_.erase(dispatcher);
314   }
315 
316   ++instance->ref_count_; // initially 0
317 
318   return instance;
319 }
320 
321 // static
322 void
unreference_instance(DispatchNotifier * notifier,const Dispatcher * dispatcher)323 DispatchNotifier::unreference_instance(DispatchNotifier* notifier, const Dispatcher* dispatcher)
324 {
325 #ifdef GLIBMM_CAN_USE_THREAD_LOCAL
326   DispatchNotifier* const instance = thread_specific_instance_;
327 #else
328   DispatchNotifier* const instance = thread_specific_instance_.get();
329 #endif
330 
331   // Yes, the notifier argument is only used to check for sanity.
332   g_return_if_fail(instance == notifier);
333 
334   if (instance->pipe_is_empty())
335     // No messages in the pipe. No need to keep track of deleted dispatchers.
336     instance->deleted_dispatchers_.clear();
337   else
338     // There are messages in the pipe, possibly to the deleted dispatcher.
339     // Keep its address, so pipe_io_handler() can avoid delivering messages to it.
340     instance->deleted_dispatchers_.insert(dispatcher);
341 
342   if (--instance->ref_count_ <= 0)
343   {
344     g_return_if_fail(instance->ref_count_ == 0); // could be < 0 if messed up
345 
346 #ifdef GLIBMM_CAN_USE_THREAD_LOCAL
347     delete thread_specific_instance_;
348     thread_specific_instance_ = nullptr;
349 #else
350     thread_specific_instance_.replace(nullptr);
351 #endif
352   }
353 }
354 
355 void
send_notification(Dispatcher * dispatcher)356 DispatchNotifier::send_notification(Dispatcher* dispatcher)
357 {
358 #ifdef G_OS_WIN32
359   {
360     const std::lock_guard<std::mutex> lock(mutex_);
361 
362     const bool was_empty = notify_queue_.empty();
363     notify_queue_.emplace_back(DispatchNotifyData(dispatcher, this));
364 
365     if (was_empty)
366     {
367       // The event will stay in signaled state until it is reset
368       // in pipe_io_handler() after processing the last queued event.
369       if (!SetEvent(fd_receiver_))
370         warn_failed_pipe_io("SetEvent");
371     }
372   }
373 #else /* !G_OS_WIN32 */
374 
375   DispatchNotifyData data(dispatcher, this);
376   gssize n_written;
377 
378   do
379     n_written = write(fd_sender_, &data, sizeof(data));
380   while (G_UNLIKELY(n_written < 0) && errno == EINTR);
381 
382   // All data must be written in a single call to write(), otherwise we cannot
383   // guarantee reentrancy since another thread might be scheduled between two
384   // write() calls.  From the glibc manual:
385   //
386   // "Reading or writing pipe data is atomic if the size of data written is not
387   // greater than PIPE_BUF. This means that the data transfer seems to be an
388   // instantaneous unit, in that nothing else in the system can observe a state
389   // in which it is partially complete. Atomic I/O may not begin right away (it
390   // may need to wait for buffer space or for data), but once it does begin it
391   // finishes immediately."
392   //
393   // The minimum value allowed by POSIX for PIPE_BUF is 512, so we are on safe
394   // grounds here.
395 
396   if (G_UNLIKELY(n_written != sizeof(data)))
397     warn_failed_pipe_io("write");
398 
399 #endif /* !G_OS_WIN32 */
400 }
401 
402 bool
pipe_is_empty()403 DispatchNotifier::pipe_is_empty()
404 {
405 #ifdef G_OS_WIN32
406   return notify_queue_.empty();
407 #else
408   PollFD poll_fd(fd_receiver_, Glib::IO_IN);
409   // GPollFD*, number of file descriptors to poll, timeout (ms)
410   g_poll(poll_fd.gobj(), 1, 0);
411   return (poll_fd.get_revents() & Glib::IO_IN) == 0;
412 #endif
413 }
414 
pipe_io_handler(Glib::IOCondition)415 bool DispatchNotifier::pipe_io_handler(Glib::IOCondition)
416 {
417   DispatchNotifyData data;
418 
419 #ifdef G_OS_WIN32
420   {
421     const std::lock_guard<std::mutex> lock(mutex_);
422 
423     // Should never be empty at this point, but let's allow for bogus
424     // notifications with no data available anyway; just to be safe.
425     if (notify_queue_.empty())
426     {
427       if (!ResetEvent(fd_receiver_))
428         warn_failed_pipe_io("ResetEvent");
429 
430       return true;
431     }
432 
433     data = notify_queue_.front();
434     notify_queue_.pop_front();
435 
436     // Handle only a single event with each invocation of the I/O handler,
437     // and reset to nonsignaled state only after the last event in the queue
438     // has been processed.  This matches the behavior on Unix.
439     if (notify_queue_.empty())
440     {
441       if (!ResetEvent(fd_receiver_))
442         warn_failed_pipe_io("ResetEvent");
443     }
444   }
445 #else /* !G_OS_WIN32 */
446 
447   gssize n_read;
448 
449   do
450     n_read = read(fd_receiver_, &data, sizeof(data));
451   while (G_UNLIKELY(n_read < 0) && errno == EINTR);
452 
453   // Pipe I/O of a block size not greater than PIPE_BUF should be atomic.
454   // See the comment on atomicity in send_notification() for details.
455   if (G_UNLIKELY(n_read != sizeof(data)))
456   {
457     // Should probably never be zero, but for safety let's allow for bogus
458     // notifications when no data is actually available.  Although in fact
459     // the read() should block in that case.
460     if (n_read != 0)
461       warn_failed_pipe_io("read");
462 
463     return true;
464   }
465 #endif /* !G_OS_WIN32 */
466 
467   g_return_val_if_fail(data.notifier == this, true);
468 
469   // Drop the received message, if it is addressed to a deleted dispatcher.
470   const bool drop_message =
471     (deleted_dispatchers_.find(data.dispatcher) != deleted_dispatchers_.end());
472 
473   // If the pipe is empty, there can be no messages to deleted dispatchers.
474   // No reason to keep track of them any more.
475   if (!deleted_dispatchers_.empty() && pipe_is_empty())
476     deleted_dispatchers_.clear();
477 
478   if (drop_message)
479   {
480     g_warning("Dropped dispatcher message as the dispatcher no longer exists");
481     return true;
482   }
483 
484   // Actually, we wouldn't need the try/catch block because the Glib::Source
485   // C callback already does it for us.  However, we do it anyway because the
486   // default return value is 'false', which is not what we want.
487   try
488   {
489     data.dispatcher->signal_(); // emit
490   }
491   catch (...)
492   {
493     Glib::exception_handlers_invoke();
494   }
495 
496   return true;
497 }
498 
499 /**** Glib::Dispatcher *****************************************************/
500 
Dispatcher()501 Dispatcher::Dispatcher()
502 : signal_(), notifier_(DispatchNotifier::reference_instance(MainContext::get_default(), this))
503 {
504 }
505 
Dispatcher(const Glib::RefPtr<MainContext> & context)506 Dispatcher::Dispatcher(const Glib::RefPtr<MainContext>& context)
507 : signal_(), notifier_(DispatchNotifier::reference_instance(context, this))
508 {
509 }
510 
~Dispatcher()511 Dispatcher::~Dispatcher() noexcept
512 {
513   DispatchNotifier::unreference_instance(notifier_, this);
514 }
515 
516 void
emit()517 Dispatcher::emit()
518 {
519   notifier_->send_notification(this);
520 }
521 
522 void
operator ()()523 Dispatcher::operator()()
524 {
525   notifier_->send_notification(this);
526 }
527 
528 sigc::connection
connect(const sigc::slot<void> & slot)529 Dispatcher::connect(const sigc::slot<void>& slot)
530 {
531   return signal_.connect(slot);
532 }
533 
534 sigc::connection
connect(sigc::slot<void> && slot)535 Dispatcher::connect(sigc::slot<void>&& slot)
536 {
537   return signal_.connect(std::move(slot));
538 }
539 
540 } // namespace Glib
541