Hi Corinna,

On Wed, 12 Mar 2025 16:37:41 +0100
Corinna Vinschen wrote:
> On Mar 13 00:08, Takashi Yano wrote:
> > On Wed, 12 Mar 2025 12:16:12 +0100
> > Corinna Vinschen wrote:
> > > Hi Takashi,
> > > 
> > > On Mar 12 12:27, Takashi Yano wrote:
> > > > The previous implementation of the signal queue behaves as:
> > > > 1) Signals in the queue are processed in a disordered manner.
> > > > 2) If the same signal is already in the queue, new signal is discarded.
> > > > 
> > > > Strictly speaking, these behaviours do not violate POSIX. However,
> > > > these could be a cause of unexpected behaviour in some software. In
> > > > Linux, some important signals such as SIGSTOP/SIGCONT do not seem to
> > > > behave like that.
> > > > This patch prevents SIGKILL, SIGSTOP, SIGCONT, and SIGRT* from that
> > > > issue. Moreover, if SA_SIGINFO is set in sa_flags, the signal is
> > > > treated almost as the same.
> > > > 
> > > > Addresses: https://cygwin.com/pipermail/cygwin/2025-March/257582.html
> > > > Fixes: 7ac6173643b1 ("(pending_signals): New class.")
> > > > Reported by: Christian Franke <christian.fra...@t-online.de>
> > > > Reviewed-by: Corinna Vinschen <cori...@vinschen.de>, Christian Franke 
> > > > <christian.fra...@t-online.de>
> > > > Signed-off-by: Takashi Yano <takashi.y...@nifty.ne.jp>
> > > > ---
> > > >  winsup/cygwin/sigproc.cc | 128 ++++++++++++++++++++++++++++++++-------
> > > >  1 file changed, 106 insertions(+), 22 deletions(-)
> > > > 
> > > > diff --git a/winsup/cygwin/sigproc.cc b/winsup/cygwin/sigproc.cc
> > > > index 8739f18f5..ab3acfd24 100644
> > > > --- a/winsup/cygwin/sigproc.cc
> > > > +++ b/winsup/cygwin/sigproc.cc
> > > > @@ -21,6 +21,7 @@ details. */
> > > >  #include "cygtls.h"
> > > >  #include "ntdll.h"
> > > >  #include "exception.h"
> > > > +#include <assert.h>
> > > >  
> > > >  /*
> > > >   * Convenience defines
> > > > @@ -28,6 +29,10 @@ details. */
> > > >  #define WSSC             60000 // Wait for signal completion
> > > >  #define WPSP             40000 // Wait for proc_subproc mutex
> > > >  
> > > > +#define PIPE_DEPTH _NSIG /* Historically, the pipe size is _NSIG 
> > > > packet */
> > > > +#define SIGQ_ROOM 4
> > > > +#define SIGQ_DEPTH (PIPE_DEPTH + SIGQ_ROOM)
> > > 
> > > I'm missing a comment here.  Why adding SIGQ_ROOM?
> > 
> > First, I thought signal queue length must be larger than pipe size
> > to send __SIGFLUSHFAST safely. However, on second thought, it is
> > not enough for some cases while it is not necessary for other cases.
> > [PATCH v3 4/6] Cygwin: signal: Do not send __SIGFLUSHFAST if the pipe/queue 
> > is full
> > ensure the safety for sending __SIGFLUSHFAST.
> > 
> > > Other than that, LGTM.
> > 
> > Thanks for reviewing.
> > I would be happy if you could review also [PATCH v3 2/6]-[PATCH v3 6/6].
> 
> I did, sorry.  They all LGTM, too.  I'm a bit put off by having to add
> two events and one mutex to accomplish a safe __SIGFLUSHFAST (rather
> hoping a single semaphore would do the trick), but I see how you use the
> objects and it makes sense to me.
> 
> > > In terms of the queue size, I wonder if we really have to restrict the
> > > queue to a small number of queued signals, 69 right now.  The pipe used
> > > for communication will take 64K, one allocation granularity slot, anyway.
> > > Linux, for instance, queues more than 60K signals.
> > > 
> > > So, wouldn't it make sense to raise the queu depth to some higher
> > > value and the pipe size so that it it's <= 64K?
> > > 
> > > While looking into your patch, it occured to me that we have a
> > > long-standing bug: We never changed __SIGQUEUE_MAX/SIGQUEUE_MAX in
> > > include/cygwin/limits.h when we started to support 64 signals (we only
> > > did that for 64 bit Cygwin).
> > > 
> > > We can't change that for existing binaries actually referring the
> > > SIGQUEUE_MAX macro, but we should change this, so that
> > > sysconf( _SC_SIGQUEUE_MAX) returns the right value, isn't it?
> > 
> > I don't understand what you are concious of. If we change the value
> > of SIGQUEUE_MAX, what happens in terms of the binary compatibility?
> 
> I don't think we get a problem in terms of binary compat.  Old apps
> get a too small value, but I'm not really sure this is a problem at
> all.  I was just stumbling over this and found that this should have
> been changed to 64 (or 65) already ages ago.

If the patch attached (v4 1/6), modified regarding SIGQUEUE_MAX,
is as you intend, I'll push the patch seriese. May I?

-- 
Takashi Yano <takashi.y...@nifty.ne.jp>
From 6bc234a6cb6b350431bc79cdf3829d0f7a2acfe5 Mon Sep 17 00:00:00 2001
From: Takashi Yano <takashi.y...@nifty.ne.jp>
Date: Fri, 7 Mar 2025 17:15:38 +0900
Subject: [PATCH v4 1/6] Cygwin: signal: Redesign signal queue handling

The previous implementation of the signal queue behaves as:
1) Signals in the queue are processed in a disordered manner.
2) If the same signal is already in the queue, new signal is discarded.

Strictly speaking, these behaviours do not violate POSIX. However,
these could be a cause of unexpected behaviour in some software. In
Linux, some important signals such as SIGSTOP/SIGCONT do not seem to
behave like that.
This patch prevents SIGKILL, SIGSTOP, SIGCONT, and SIGRT* from that
issue. Moreover, if SA_SIGINFO is set in sa_flags, the signal is
treated almost as the same.

Addresses: https://cygwin.com/pipermail/cygwin/2025-March/257582.html
Fixes: 7ac6173643b1 ("(pending_signals): New class.")
Reported by: Christian Franke <christian.fra...@t-online.de>
Reviewed-by: Corinna Vinschen <cori...@vinschen.de>, Christian Franke 
<christian.fra...@t-online.de>
Signed-off-by: Takashi Yano <takashi.y...@nifty.ne.jp>
---
 winsup/cygwin/include/cygwin/limits.h |   2 +-
 winsup/cygwin/sigproc.cc              | 126 +++++++++++++++++++++-----
 2 files changed, 105 insertions(+), 23 deletions(-)

diff --git a/winsup/cygwin/include/cygwin/limits.h 
b/winsup/cygwin/include/cygwin/limits.h
index ea3e2836a..204154da9 100644
--- a/winsup/cygwin/include/cygwin/limits.h
+++ b/winsup/cygwin/include/cygwin/limits.h
@@ -41,7 +41,7 @@ details. */
 
 #define __RTSIG_MAX 33
 #define __SEM_VALUE_MAX 1147483648
-#define __SIGQUEUE_MAX 32
+#define __SIGQUEUE_MAX 1024
 #define __STREAM_MAX 20
 #define __SYMLOOP_MAX 10
 #define __TIMER_MAX 32
diff --git a/winsup/cygwin/sigproc.cc b/winsup/cygwin/sigproc.cc
index 8739f18f5..cf5fcbca0 100644
--- a/winsup/cygwin/sigproc.cc
+++ b/winsup/cygwin/sigproc.cc
@@ -21,6 +21,7 @@ details. */
 #include "cygtls.h"
 #include "ntdll.h"
 #include "exception.h"
+#include <assert.h>
 
 /*
  * Convenience defines
@@ -28,6 +29,8 @@ details. */
 #define WSSC             60000 // Wait for signal completion
 #define WPSP             40000 // Wait for proc_subproc mutex
 
+#define PIPE_DEPTH ((DWORD) 65536 / sizeof (sigpacket))
+
 /*
  * Global variables
  */
@@ -104,15 +107,16 @@ static void wait_sig (VOID *arg);
 
 class pending_signals
 {
-  sigpacket sigs[_NSIG + 1];
+  sigpacket sigs[SIGQUEUE_MAX];
   sigpacket start;
+  int queue_left;
   SRWLOCK queue_lock;
   bool retry;
   void lock () { AcquireSRWLockExclusive (&queue_lock); }
   void unlock () { ReleaseSRWLockExclusive (&queue_lock); }
 
 public:
-  pending_signals (): queue_lock (SRWLOCK_INIT) {}
+  pending_signals (): queue_left (SIGQUEUE_MAX), queue_lock (SRWLOCK_INIT) {}
   void add (sigpacket&);
   bool pending () {retry = !!start.next; return retry;}
   void clear (int sig, bool need_lock);
@@ -441,15 +445,22 @@ sig_clear (int sig, bool need_lock)
 void
 pending_signals::clear (int sig, bool need_lock)
 {
-  sigpacket *q = sigs + sig;
-  if (!sig || !q->si.si_signo)
+  sigpacket *q = &start;
+
+  if (!sig)
     return;
+
   if (need_lock)
     lock ();
-  q->si.si_signo = 0;
-  q->prev->next = q->next;
-  if (q->next)
-    q->next->prev = q->prev;
+  while ((q = q->next))
+    if (q->si.si_signo == sig)
+      {
+       q->si.si_signo = 0;
+       q->prev->next = q->next;
+       if (q->next)
+         q->next->prev = q->prev;
+       queue_left++;
+      }
   if (need_lock)
     unlock ();
 }
@@ -469,6 +480,7 @@ pending_signals::clear (_cygtls *tls)
        q->prev->next = q->next;
        if (q->next)
          q->next->prev = q->prev;
+       queue_left++;
       }
   unlock ();
 }
@@ -509,7 +521,7 @@ sigproc_init ()
   char char_sa_buf[1024];
   PSECURITY_ATTRIBUTES sa = sec_user_nih ((PSECURITY_ATTRIBUTES) char_sa_buf, 
cygheap->user.sid());
   DWORD err = fhandler_pipe::create (sa, &my_readsig, &my_sendsig,
-                                    _NSIG * sizeof (sigpacket), "sigwait",
+                                    PIPE_DEPTH * sizeof (sigpacket), "sigwait",
                                     PIPE_ADD_PID);
   if (err)
     {
@@ -1311,23 +1323,85 @@ talktome (siginfo_t *si)
     new cygthread (commune_process, size, si, "commune");
 }
 
-/* Add a packet to the beginning of the queue.
+static inline bool
+is_sigsys (int sig)
+{
+  return sig == SIGKILL || sig == SIGSTOP || sig == SIGCONT;
+}
+
+static inline bool
+is_sigrt (int sig)
+{
+  return sig >= SIGRTMIN && sig <= SIGRTMAX;
+}
+
+static inline bool
+is_sigsysrt (int sig)
+{
+  return is_sigsys (sig) || is_sigrt (sig);
+}
+
+/* Add a packet to the end of the queue to process signals
+   in the order they are issued except for SIGRT*.
    Should only be called from signal thread.  */
 void
 pending_signals::add (sigpacket& pack)
 {
-  sigpacket *se;
+  sigpacket *se = NULL, *q = &start;
+  bool queue_once = !is_sigsysrt (pack.si.si_signo)
+    && !(global_sigs[pack.si.si_signo].sa_flags & SA_SIGINFO);
 
-  se = sigs + pack.si.si_signo;
-  if (se->si.si_signo)
-    return;
-  *se = pack;
   lock ();
-  se->next = start.next;
-  se->prev = &start;
-  se->prev->next = se;
-  if (se->next)
-    se->next->prev = se;
+  if (pack.si.si_signo != SIGKILL)
+    while (q->next)
+      {
+       /* Linux man signal(7) says:
+          "If different real-time signals are sent to a process, they are
+          delivered starting with the lowest-numbered signal." */
+       if (is_sigrt (q->next->si.si_signo) && is_sigrt (pack.si.si_signo)
+           && q->next->si.si_signo > pack.si.si_signo)
+         break;
+       /* Linux man signal(7) says:
+          "If both standard and real-time signals are pending for a process,
+          POSIX leaves it unspecified which is delivered first.  Linux, like
+          many other implementations, gives priority to standard signals in
+          this case." */
+       if (is_sigrt (q->next->si.si_signo) && !is_sigrt (pack.si.si_signo))
+         break;
+       q = q->next;
+       /* Linux man signal(7) says:
+          "if multiple instances of a standard signal are delivered while
+          that signal is currently blocked, then only one instance is
+          queued." */
+       /* POSIX.1-2004 says on sigaction():
+          "If SA_SIGINFO is set in sa_flags, then subsequent occurrences
+          of sig generated by sigqueue() or as a result of any signal-
+          generating function that supports the specification of an
+          application-defined value (when sig is already pending) shall
+          be queued in FIFO order until delivered or accepted;" */
+       if (queue_once && q->si.si_signo == pack.si.si_signo)
+         {
+           unlock ();
+           return;
+         }
+      }
+
+  assert (queue_left > 0);
+  for (int i = 0; i < SIGQUEUE_MAX; i++)
+    if (sigs[i].si.si_signo == 0)
+      {
+       se = sigs + i;
+       *se = pack;
+       break;
+      }
+  assert (se != NULL);
+  queue_left--;
+
+  if (q->next)
+    q->next->prev = se;
+  se->next = q->next;
+  se->prev = q;
+  q->next = se;
   unlock ();
 }
 
@@ -1354,12 +1428,12 @@ wait_sig (VOID *)
     {
       DWORD nb;
       sigpacket pack = {};
-      if (sigq.retry)
+      if (sigq.retry || sigq.queue_left == 0)
        pack.si.si_signo = __SIGFLUSH;
       else if (sigq.start.next
               && PeekNamedPipe (my_readsig, NULL, 0, NULL, &nb, NULL) && !nb)
        {
-         Sleep (GetTickCount () - t0 > 10 ? 1 : 0);
+         Sleep ((sig_held || GetTickCount () - t0 > 10) ? 1 : 0);
          pack.si.si_signo = __SIGFLUSH;
        }
       else if (!ReadFile (my_readsig, &pack, sizeof (pack), &nb, NULL))
@@ -1505,7 +1579,15 @@ wait_sig (VOID *)
                      q->prev->next = q->next;
                      if (q->next)
                        q->next->prev = q->prev;
+                     sigq.queue_left++;
                    }
+                 else if (is_sigsysrt (q->si.si_signo)
+                      || ((global_sigs[q->si.si_signo].sa_flags & SA_SIGINFO)
+                          && NOTSTATE (myself, PID_STOPPED)))
+                   /* Stop processing further to prevent the signals from
+                      being processed in a disorderd manner if the signal
+                      is a realtime signal or SA_SIGINFO is set. */
+                   break;
                }
              sigq.unlock ();
              /* At least one signal still queued?  The event is used in select
-- 
2.45.1

Reply via email to