Updated libglibmm2 to glibmm-2.12.5
[ardour.git] / libs / glibmm2 / glibmm / dispatcher.cc
1 // -*- c++ -*-
2 /* $Id$ */
3
4 /* Copyright 2002 The gtkmm Development Team
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the Free
18  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20
21 #include <glibmm/dispatcher.h>
22 #include <glibmm/exceptionhandler.h>
23 #include <glibmm/fileutils.h>
24 #include <glibmm/main.h>
25 #include <glibmm/thread.h>
26
27 #include <cerrno>
28 #include <fcntl.h>
29 #include <glib.h>
30
31 #ifndef G_OS_WIN32
32 #include <unistd.h>
33
34 #if defined(_tru64) //TODO: Use the real define
35 //EINTR is not defined on Tru64
36 //I have tried including these
37 //#include <sys/types.h>
38 //#include <sys/statvfs.h>
39 //#include <signal.h>
40   #ifndef EINTR
41   #define EINTR 0
42   #endif
43 #endif
44
45 #else
46 #include <windows.h>
47 #include <io.h>
48 #include <direct.h>
49 #include <list>
50 #endif /* G_OS_WIN32 */
51
52
53 namespace
54 {
55
56 struct DispatchNotifyData
57 {
58   unsigned long           tag;
59   Glib::Dispatcher*       dispatcher;
60   Glib::DispatchNotifier* notifier;
61
62   DispatchNotifyData()
63     : tag (0), dispatcher (0), notifier (0) {}
64
65   DispatchNotifyData(unsigned long tag_, Glib::Dispatcher* dispatcher_, Glib::DispatchNotifier* notifier_)
66     : tag (tag_), dispatcher (dispatcher_), notifier (notifier_) {}
67 };
68
69 static void warn_failed_pipe_io(const char* what, int err_no)
70 {
71 #ifdef G_OS_WIN32
72   const char *const message = g_win32_error_message(err_no);
73 #else
74   const char *const message = g_strerror(err_no);
75 #endif
76   g_critical("Error in inter-thread communication: %s() failed: %s", what, message);
77 }
78
79 #ifndef G_OS_WIN32
80 /*
81  * Try to set the close-on-exec flag of the file descriptor,
82  * so that it won't be leaked if a new process is spawned.
83  */
84 static void fd_set_close_on_exec(int fd)
85 {
86   const int flags = fcntl(fd, F_GETFD, 0);
87   g_return_if_fail(flags >= 0);
88
89   fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
90 }
91 #endif /* !G_OS_WIN32 */
92
93 /*
94  * One word: paranoia.
95  */
96 #ifdef G_OS_WIN32
97 static void fd_close_and_invalidate(HANDLE& fd)
98 {
99   if(fd != 0)
100   {
101     if(!CloseHandle(fd))
102       warn_failed_pipe_io("CloseHandle", GetLastError());
103
104     fd = 0;
105   }
106 }
107 #else /* !G_OS_WIN32 */
108 static void fd_close_and_invalidate(int& fd)
109 {
110   if(fd >= 0)
111   {
112     int result;
113
114     do
115       result = close(fd);
116     while(result < 0 && errno == EINTR);
117
118     if(result < 0)
119       warn_failed_pipe_io("close", errno);
120
121     fd = -1;
122   }
123 }
124 #endif /* !G_OS_WIN32 */
125
126 } // anonymous namespace
127
128
129 namespace Glib
130 {
131
132 class DispatchNotifier : public sigc::trackable
133 {
134 public:
135   ~DispatchNotifier();
136
137   static DispatchNotifier* reference_instance(const Glib::RefPtr<MainContext>& context);
138   static void unreference_instance(DispatchNotifier* notifier);
139
140   void send_notification(Dispatcher* dispatcher);
141
142 protected:
143   // Only used by reference_instance().  Should be private, but that triggers
144   // a silly gcc warning even though DispatchNotifier has static methods.
145   explicit DispatchNotifier(const Glib::RefPtr<MainContext>& context);
146
147 private:
148   static Glib::StaticPrivate<DispatchNotifier> thread_specific_instance_;
149
150   Glib::RefPtr<MainContext> context_;
151   int                       ref_count_;
152 #ifdef G_OS_WIN32
153   HANDLE                        fd_receiver_;
154   Glib::Mutex                   mutex_;
155   std::list<DispatchNotifyData> notify_queue_;
156 #else
157   int                       fd_receiver_;
158   int                       fd_sender_;
159 #endif /* !G_OS_WIN32 */
160   sigc::connection          conn_io_handler_;
161
162   void create_pipe();
163   bool pipe_io_handler(Glib::IOCondition condition);
164
165   // noncopyable
166   DispatchNotifier(const DispatchNotifier&);
167   DispatchNotifier& operator=(const DispatchNotifier&);
168 };
169
170
171 /**** Glib::DispatchNotifier ***********************************************/
172
173 Glib::StaticPrivate<DispatchNotifier>
174 DispatchNotifier::thread_specific_instance_ = GLIBMM_STATIC_PRIVATE_INIT;
175
176 DispatchNotifier::DispatchNotifier(const Glib::RefPtr<MainContext>& context)
177 :
178   context_      (context),
179   ref_count_    (0),
180 #ifdef G_OS_WIN32
181   fd_receiver_  (0),
182   mutex_        (),
183   notify_queue_ ()
184 #else
185   fd_receiver_  (-1),
186   fd_sender_    (-1)
187 #endif
188 {
189   create_pipe();
190
191   #ifdef GLIBMM_EXCEPTIONS_ENABLED
192   try
193   {
194   #endif //GLIBMM_EXCEPTIONS_ENABLED
195 #ifdef G_OS_WIN32
196     conn_io_handler_ = context_->signal_io().connect(
197         sigc::mem_fun(*this, &DispatchNotifier::pipe_io_handler),
198         GPOINTER_TO_INT(fd_receiver_), Glib::IO_IN);
199 #else /* !G_OS_WIN32 */
200     conn_io_handler_ = context_->signal_io().connect(
201         sigc::mem_fun(*this, &DispatchNotifier::pipe_io_handler),
202         fd_receiver_, Glib::IO_IN);
203 #endif /* !G_OS_WIN32 */
204   #ifdef GLIBMM_EXCEPTIONS_ENABLED
205   }
206   catch(...)
207   {
208 #ifndef G_OS_WIN32
209     fd_close_and_invalidate(fd_sender_);
210 #endif /* !G_OS_WIN32 */
211     fd_close_and_invalidate(fd_receiver_);
212
213     throw;
214   }
215   #endif //GLIBMM_EXCEPTIONS_ENABLED
216 }
217
218 DispatchNotifier::~DispatchNotifier()
219 {
220   // Disconnect manually because we don't inherit from sigc::trackable
221   conn_io_handler_.disconnect();
222
223 #ifndef G_OS_WIN32
224   fd_close_and_invalidate(fd_sender_);
225 #endif /* !G_OS_WIN32 */
226   fd_close_and_invalidate(fd_receiver_);
227 }
228
229 void DispatchNotifier::create_pipe()
230 {
231 #ifdef G_OS_WIN32
232   // On Win32 we are using synchronization object instead of pipe
233   // thus storing its handle as fd_receiver_.
234   fd_receiver_ = CreateEvent(0, FALSE, FALSE, 0);
235
236   if(!fd_receiver_)
237   {
238 #ifdef GLIBMM_EXCEPTIONS_ENABLED
239     GError* const error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_FAILED,
240                                       "Failed to create event for inter-thread communication: %s",
241                                       g_win32_error_message(GetLastError()));
242     throw Glib::FileError(error);
243 #else 
244     return; //TODO: Provide an alternative to the exception.
245 #endif //GLIBMM_EXCEPTIONS_ENABLED
246   }
247
248 #else /* !G_OS_WIN32 */
249   int filedes[2] = { -1, -1 };
250
251   if(pipe(filedes) < 0)
252   {
253 #ifdef GLIBMM_EXCEPTIONS_ENABLED
254     GError* const error = g_error_new(G_FILE_ERROR, g_file_error_from_errno(errno),
255                                       "Failed to create pipe for inter-thread communication: %s",
256                                       g_strerror(errno));
257     throw Glib::FileError(error);
258 #else 
259     return; //TODO: Provide an alternative to the exception.
260 #endif //GLIBMM_EXCEPTIONS_ENABLED
261   }
262
263   fd_set_close_on_exec(filedes[0]);
264   fd_set_close_on_exec(filedes[1]);
265
266   fd_receiver_ = filedes[0];
267   fd_sender_   = filedes[1];
268 #endif /* !G_OS_WIN32 */
269 }
270
271 // static
272 DispatchNotifier* DispatchNotifier::reference_instance(const Glib::RefPtr<MainContext>& context)
273 {
274   DispatchNotifier* instance = thread_specific_instance_.get();
275
276   if(!instance)
277   {
278     instance = new DispatchNotifier(context);
279     thread_specific_instance_.set(instance);
280   }
281   else
282   {
283     // Prevent massive mess-up.
284     g_return_val_if_fail(instance->context_ == context, 0);
285   }
286
287   ++instance->ref_count_; // initially 0
288
289   return instance;
290 }
291
292 // static
293 void DispatchNotifier::unreference_instance(DispatchNotifier* notifier)
294 {
295   DispatchNotifier *const instance = thread_specific_instance_.get();
296
297   // Yes, the notifier argument is only used to check for sanity.
298   g_return_if_fail(instance == notifier);
299
300   if(--instance->ref_count_ <= 0)
301   {
302     g_return_if_fail(instance->ref_count_ == 0); // could be < 0 if messed up
303
304     // This will cause deletion of the notifier object.
305     thread_specific_instance_.set(0);
306   }
307 }
308
309 void DispatchNotifier::send_notification(Dispatcher* dispatcher)
310 {
311 #ifdef G_OS_WIN32
312   {
313     Glib::Mutex::Lock lock (mutex_);
314     notify_queue_.push_back(DispatchNotifyData(0xdeadbeef, dispatcher, this));
315   }
316
317   // Send notification event to GUI-thread.
318   if(!SetEvent(fd_receiver_))
319   {
320     warn_failed_pipe_io("SetEvent", GetLastError());
321     return;
322   }
323 #else /* !G_OS_WIN32 */
324   DispatchNotifyData data (0xdeadbeef, dispatcher, this);
325   gssize n_written;
326
327   do
328     n_written = write(fd_sender_, &data, sizeof(data));
329   while(n_written < 0 && errno == EINTR);
330
331   if(n_written < 0)
332   {
333     warn_failed_pipe_io("write", errno);
334     return;
335   }
336
337   // All data must be written in a single call to write(), otherwise we can't
338   // guarantee reentrancy since another thread might be scheduled between two
339   // write() calls.  The manpage is a bit unclear about this -- but I hope
340   // it's safe to assume immediate success for the tiny amount of data we're
341   // writing.
342   g_return_if_fail(n_written == sizeof(data));
343 #endif /* !G_OS_WIN32 */
344 }
345
346 bool DispatchNotifier::pipe_io_handler(Glib::IOCondition)
347 {
348 #ifdef G_OS_WIN32
349   DispatchNotifyData data;
350
351   for(;;)
352   {
353     {
354       Glib::Mutex::Lock lock (mutex_);
355
356       if(notify_queue_.empty())
357         break;
358
359       data = notify_queue_.front();
360       notify_queue_.pop_front();
361     }
362
363     g_return_val_if_fail(data.tag == 0xdeadbeef, true);
364     g_return_val_if_fail(data.notifier == this,  true);
365
366     // Actually, we wouldn't need the try/catch block because the Glib::Source
367     // C callback already does it for us.  However, we do it anyway because the
368     // default return value is 'false', which is not what we want.
369     #ifdef GLIBMM_EXCEPTIONS_ENABLED
370     try
371     {
372     #endif //GLIBMM_EXCEPTIONS_ENABLED
373       data.dispatcher->signal_();
374     #ifdef GLIBMM_EXCEPTIONS_ENABLED
375     }
376     catch(...)
377     {
378       Glib::exception_handlers_invoke();
379     }
380     #endif //GLIBMM_EXCEPTIONS_ENABLED
381   }
382 #else /* !G_OS_WIN32 */
383   DispatchNotifyData data;
384   gsize n_read = 0;
385
386   do
387   {
388     void * const buffer = reinterpret_cast<guint8*>(&data) + n_read;
389     const gssize result = read(fd_receiver_, buffer, sizeof(data) - n_read);
390
391     if(result < 0)
392     {
393       if(errno == EINTR)
394         continue;
395
396       warn_failed_pipe_io("read", errno);
397       return true;
398     }
399
400     n_read += result;
401   }
402   while(n_read < sizeof(data));
403
404   g_return_val_if_fail(data.tag == 0xdeadbeef, true);
405   g_return_val_if_fail(data.notifier == this,  true);
406
407   // Actually, we wouldn't need the try/catch block because the Glib::Source
408   // C callback already does it for us.  However, we do it anyway because the
409   // default return value is 'false', which is not what we want.
410   #ifdef GLIBMM_EXCEPTIONS_ENABLED
411   try
412   {
413   #endif //GLIBMM_EXCEPTIONS_ENABLED
414     data.dispatcher->signal_(); // emit
415   #ifdef GLIBMM_EXCEPTIONS_ENABLED
416   }
417   catch(...)
418   {
419     Glib::exception_handlers_invoke();
420   }
421   #endif //GLIBMM_EXCEPTIONS_ENABLED
422 #endif /* !G_OS_WIN32 */
423
424   return true;
425 }
426
427
428 /**** Glib::Dispatcher *****************************************************/
429
430 Dispatcher::Dispatcher()
431 :
432   signal_   (),
433   notifier_ (DispatchNotifier::reference_instance(MainContext::get_default()))
434 {}
435
436 Dispatcher::Dispatcher(const Glib::RefPtr<MainContext>& context)
437 :
438   signal_   (),
439   notifier_ (DispatchNotifier::reference_instance(context))
440 {}
441
442 Dispatcher::~Dispatcher()
443 {
444   DispatchNotifier::unreference_instance(notifier_);
445 }
446
447 void Dispatcher::emit()
448 {
449   notifier_->send_notification(this);
450 }
451
452 void Dispatcher::operator()()
453 {
454   emit();
455 }
456
457 sigc::connection Dispatcher::connect(const sigc::slot<void>& slot)
458 {
459   return signal_.connect(slot);
460 }
461
462 } // namespace Glib
463