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