root/src/xgselect.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. release_select_lock
  2. acquire_select_lock
  3. suppress_xg_select
  4. release_xg_select
  5. xg_select

     1 /* Function for handling the GLib event loop.
     2 
     3 Copyright (C) 2009-2023 Free Software Foundation, Inc.
     4 
     5 This file is part of GNU Emacs.
     6 
     7 GNU Emacs is free software: you can redistribute it and/or modify
     8 it under the terms of the GNU General Public License as published by
     9 the Free Software Foundation, either version 3 of the License, or (at
    10 your option) any later version.
    11 
    12 GNU Emacs is distributed in the hope that it will be useful,
    13 but WITHOUT ANY WARRANTY; without even the implied warranty of
    14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15 GNU General Public License for more details.
    16 
    17 You should have received a copy of the GNU General Public License
    18 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
    19 
    20 #include <config.h>
    21 
    22 #include "xgselect.h"
    23 
    24 #ifdef HAVE_GLIB
    25 
    26 #include <glib.h>
    27 #include <errno.h>
    28 #include "lisp.h"
    29 #include "blockinput.h"
    30 #include "systime.h"
    31 #include "process.h"
    32 
    33 static ptrdiff_t threads_holding_glib_lock;
    34 static GMainContext *glib_main_context;
    35 
    36 /* The depth of xg_select suppression.  */
    37 static int xg_select_suppress_count;
    38 
    39 void
    40 release_select_lock (void)
    41 {
    42 #if GNUC_PREREQ (4, 7, 0)
    43   if (__atomic_sub_fetch (&threads_holding_glib_lock, 1, __ATOMIC_ACQ_REL) == 0)
    44     g_main_context_release (glib_main_context);
    45 #else
    46   if (--threads_holding_glib_lock == 0)
    47     g_main_context_release (glib_main_context);
    48 #endif
    49 }
    50 
    51 static void
    52 acquire_select_lock (GMainContext *context)
    53 {
    54 #if GNUC_PREREQ (4, 7, 0)
    55   if (__atomic_fetch_add (&threads_holding_glib_lock, 1, __ATOMIC_ACQ_REL) == 0)
    56     {
    57       glib_main_context = context;
    58       while (!g_main_context_acquire (context))
    59         {
    60           /* Spin. */
    61         }
    62     }
    63 #else
    64   if (threads_holding_glib_lock++ == 0)
    65     {
    66       glib_main_context = context;
    67       while (!g_main_context_acquire (context))
    68         {
    69           /* Spin. */
    70         }
    71     }
    72 #endif
    73 }
    74 
    75 /* Call this to not use xg_select when using it would be a bad idea,
    76    i.e. during drag-and-drop.  */
    77 void
    78 suppress_xg_select (void)
    79 {
    80   ++xg_select_suppress_count;
    81 }
    82 
    83 void
    84 release_xg_select (void)
    85 {
    86   if (!xg_select_suppress_count)
    87     emacs_abort ();
    88 
    89   --xg_select_suppress_count;
    90 }
    91 
    92 /* `xg_select' is a `pselect' replacement.  Why do we need a separate function?
    93    1. Timeouts.  Glib and Gtk rely on timer events.  If we did pselect
    94       with a greater timeout then the one scheduled by Glib, we would
    95       not allow Glib to process its timer events.  We want Glib to
    96       work smoothly, so we need to reduce our timeout to match Glib.
    97    2. Descriptors.  Glib may listen to more file descriptors than we do.
    98       So we add Glib descriptors to our pselect pool, but we don't change
    99       the value returned by the function.  The return value  matches only
   100       the descriptors passed as arguments, making it compatible with
   101       plain pselect.  */
   102 
   103 int
   104 xg_select (int fds_lim, fd_set *rfds, fd_set *wfds, fd_set *efds,
   105            struct timespec *timeout, sigset_t *sigmask)
   106 {
   107   fd_set all_rfds, all_wfds;
   108   struct timespec tmo;
   109   struct timespec *tmop = timeout;
   110 
   111   GMainContext *context;
   112   bool have_wfds = wfds != NULL;
   113   GPollFD gfds_buf[128];
   114   GPollFD *gfds = gfds_buf;
   115   int gfds_size = ARRAYELTS (gfds_buf);
   116   int n_gfds, retval = 0, our_fds = 0, max_fds = fds_lim - 1;
   117   int i, nfds, tmo_in_millisec, must_free = 0;
   118   bool need_to_dispatch;
   119 #ifdef USE_GTK
   120   bool already_has_events;
   121 #endif
   122 
   123   if (xg_select_suppress_count)
   124     return pselect (fds_lim, rfds, wfds, efds, timeout, sigmask);
   125 
   126   context = g_main_context_default ();
   127   acquire_select_lock (context);
   128 
   129 #ifdef USE_GTK
   130   already_has_events = g_main_context_pending (context);
   131 #ifndef HAVE_PGTK
   132   already_has_events = already_has_events && x_gtk_use_native_input;
   133 #endif
   134 #endif
   135 
   136   if (rfds) all_rfds = *rfds;
   137   else FD_ZERO (&all_rfds);
   138   if (wfds) all_wfds = *wfds;
   139   else FD_ZERO (&all_wfds);
   140 
   141   n_gfds = g_main_context_query (context, G_PRIORITY_LOW, &tmo_in_millisec,
   142                                  gfds, gfds_size);
   143 
   144   if (gfds_size < n_gfds)
   145     {
   146       /* Avoid using SAFE_NALLOCA, as that implicitly refers to the
   147          current thread.  Using xnmalloc avoids thread-switching
   148          problems here.  */
   149       gfds = xnmalloc (n_gfds, sizeof *gfds);
   150       must_free = 1;
   151       gfds_size = n_gfds;
   152       n_gfds = g_main_context_query (context, G_PRIORITY_LOW, &tmo_in_millisec,
   153                                      gfds, gfds_size);
   154     }
   155 
   156   for (i = 0; i < n_gfds; ++i)
   157     {
   158       if (gfds[i].events & G_IO_IN)
   159         {
   160           FD_SET (gfds[i].fd, &all_rfds);
   161           if (gfds[i].fd > max_fds) max_fds = gfds[i].fd;
   162         }
   163       if (gfds[i].events & G_IO_OUT)
   164         {
   165           FD_SET (gfds[i].fd, &all_wfds);
   166           if (gfds[i].fd > max_fds) max_fds = gfds[i].fd;
   167           have_wfds = true;
   168         }
   169     }
   170 
   171   if (must_free)
   172     xfree (gfds);
   173 
   174   if (n_gfds >= 0 && tmo_in_millisec >= 0)
   175     {
   176       tmo = make_timespec (tmo_in_millisec / 1000,
   177                            1000 * 1000 * (tmo_in_millisec % 1000));
   178       if (!timeout || timespec_cmp (tmo, *timeout) < 0)
   179         tmop = &tmo;
   180     }
   181 
   182 #ifndef USE_GTK
   183   fds_lim = max_fds + 1;
   184   nfds = thread_select (pselect, fds_lim,
   185                         &all_rfds, have_wfds ? &all_wfds : NULL, efds,
   186                         tmop, sigmask);
   187 #else
   188   /* On PGTK, when you type a key, the key press event are received,
   189      and one more key press event seems to be received internally.
   190 
   191      The same can happen with GTK native input, which makes input
   192      slow.
   193 
   194      The second event is not sent via the display connection, so the
   195      following is the case:
   196 
   197        - socket read buffer is empty
   198        - a key press event is pending
   199 
   200      In that case, we should not sleep in pselect, and dispatch the
   201      event immediately.  (Bug#52761) */
   202   if (!already_has_events)
   203     {
   204       fds_lim = max_fds + 1;
   205       nfds = thread_select (pselect, fds_lim,
   206                             &all_rfds, have_wfds ? &all_wfds : NULL, efds,
   207                             tmop, sigmask);
   208     }
   209   else
   210     {
   211       /* Emulate return values */
   212       nfds = 1;
   213       FD_ZERO (&all_rfds);
   214       if (have_wfds)
   215         FD_ZERO (&all_wfds);
   216       if (efds)
   217         FD_ZERO (efds);
   218       our_fds++;
   219     }
   220 #endif
   221 
   222   if (nfds < 0)
   223     retval = nfds;
   224   else if (nfds > 0)
   225     {
   226       for (i = 0; i < fds_lim; ++i)
   227         {
   228           if (FD_ISSET (i, &all_rfds))
   229             {
   230               if (rfds && FD_ISSET (i, rfds)) ++retval;
   231               else ++our_fds;
   232             }
   233           else if (rfds)
   234             FD_CLR (i, rfds);
   235 
   236           if (have_wfds && FD_ISSET (i, &all_wfds))
   237             {
   238               if (wfds && FD_ISSET (i, wfds)) ++retval;
   239               else ++our_fds;
   240             }
   241           else if (wfds)
   242             FD_CLR (i, wfds);
   243 
   244           if (efds && FD_ISSET (i, efds))
   245             ++retval;
   246         }
   247     }
   248 
   249   /* If Gtk+ is in use eventually gtk_main_iteration will be called,
   250      unless retval is zero.  */
   251 #ifdef USE_GTK
   252   need_to_dispatch = retval == 0;
   253 #else
   254   need_to_dispatch = true;
   255 #endif
   256 
   257   /* xwidgets make heavy use of GLib subprocesses, which add their own
   258      SIGCHLD handler at arbitrary locations.  That doesn't play well
   259      with Emacs's own handler, so once GLib does its thing with its
   260      subprocesses we restore our own SIGCHLD handler (which chains the
   261      GLib handler) here.
   262 
   263      There is an obvious race condition, but we can't really do
   264      anything about that, except hope a SIGCHLD arrives soon to clear
   265      up the situation.  */
   266 
   267 #ifdef HAVE_XWIDGETS
   268   catch_child_signal ();
   269 #endif
   270 
   271   if (need_to_dispatch)
   272     {
   273       acquire_select_lock (context);
   274 
   275       int pselect_errno = errno;
   276       /* Prevent g_main_dispatch recursion, that would occur without
   277          block_input wrapper, because event handlers call
   278          unblock_input.  Event loop recursion was causing Bug#15801.  */
   279       block_input ();
   280       while (g_main_context_pending (context))
   281         g_main_context_dispatch (context);
   282       unblock_input ();
   283       errno = pselect_errno;
   284       release_select_lock ();
   285     }
   286 
   287   /* To not have to recalculate timeout, return like this.  */
   288   if ((our_fds > 0 || (nfds == 0 && tmop == &tmo)) && (retval == 0))
   289     {
   290       retval = -1;
   291       errno = EINTR;
   292     }
   293 
   294   return retval;
   295 }
   296 #endif /* HAVE_GLIB */

/* [<][>][^][v][top][bottom][index][help] */