From 079fac47d7d68bd62c9c3ab6ebdf04ca379823ed Mon Sep 17 00:00:00 2001
From: ulfvonbelow <strilen@tilde.club>
Date: Sat, 28 Jan 2023 15:30:54 -0600
Subject: [PATCH 1/2] UTIL: add test demonstrating scheduler bug, don't run it
 by default.

These demonstrate a bug in the scheduler by which a task can prevent any other
task from running for an arbitrarily long time despite regularly yielding to
the scheduler.  It is caused by a faulty check in GNUNET_SCHEDULER_do_work
that assumes that the task that was the last in the queue when the pass began
will still be in the same relative position when the pass ends, and uses this
assumption to detect the end of the current pass.  This assumption fails when
the last task of the current pass is canceled after the pass has started.  It
also fails when we schedule a higher-priority task to run immediately, causing
work_priority to immediately switch such that we now process a queue that
doesn't contain the pass-ending task we're looking for.

These tests are built, but not run by 'make check' yet, since they currently
fail.  You can manually verify that they do currently fail.
---
 src/util/Makefile.am                       | 12 +++++
 src/util/test_scheduler_hogging_cancel.c   | 51 ++++++++++++++++++++
 src/util/test_scheduler_hogging_priority.c | 55 ++++++++++++++++++++++
 3 files changed, 118 insertions(+)
 create mode 100644 src/util/test_scheduler_hogging_cancel.c
 create mode 100644 src/util/test_scheduler_hogging_priority.c

diff --git a/src/util/Makefile.am b/src/util/Makefile.am
index ed01558eb..81b8a93b7 100644
--- a/src/util/Makefile.am
+++ b/src/util/Makefile.am
@@ -174,6 +174,8 @@ endif
 noinst_PROGRAMS = \
  gnunet-config-diff \
  test_common_logging_dummy \
+ test_scheduler_hogging_cancel \
+ test_scheduler_hogging_priority \
  gnunet-crypto-tvg
 
 if ENABLE_TEST_RUN
@@ -587,6 +589,16 @@ test_scheduler_delay_SOURCES = \
 test_scheduler_delay_LDADD = \
  libgnunetutil.la
 
+test_scheduler_hogging_cancel_SOURCES = \
+ test_scheduler_hogging_cancel.c
+test_scheduler_hogging_cancel_LDADD = \
+ libgnunetutil.la
+
+test_scheduler_hogging_priority_SOURCES = \
+ test_scheduler_hogging_priority.c
+test_scheduler_hogging_priority_LDADD = \
+ libgnunetutil.la
+
 test_service_SOURCES = \
  test_service.c
 test_service_LDADD = \
diff --git a/src/util/test_scheduler_hogging_cancel.c b/src/util/test_scheduler_hogging_cancel.c
new file mode 100644
index 000000000..7611338b3
--- /dev/null
+++ b/src/util/test_scheduler_hogging_cancel.c
@@ -0,0 +1,51 @@
+#include "gnunet_util_lib.h"
+#include <unistd.h>
+
+static int count = 0;
+static int final_count;
+static struct GNUNET_SCHEDULER_Task *t4;
+
+static void end (void *cls)
+{
+  final_count = count;
+  count = 5000;
+  GNUNET_SCHEDULER_shutdown ();
+}
+
+static void self_rescheduling (void *cls)
+{
+  if (0 == count)
+  {
+    GNUNET_SCHEDULER_cancel (t4);
+    GNUNET_SCHEDULER_add_delayed_with_priority (GNUNET_TIME_UNIT_MILLISECONDS,
+                                                GNUNET_SCHEDULER_PRIORITY_URGENT,
+                                                &end,
+                                                NULL);
+    sleep (1);
+    /* end should be added to ready queue on next scheduler pass for certain
+       now */
+  }
+  if (++count < 5000)
+    {
+      GNUNET_SCHEDULER_add_now (&self_rescheduling, NULL);
+    }
+}
+
+static void to_be_canceled (void *cls)
+{
+  /* Don't run me! */
+}
+
+
+static void init (void *cls)
+{
+  GNUNET_SCHEDULER_add_now (&self_rescheduling, NULL);
+  t4 = GNUNET_SCHEDULER_add_now (&to_be_canceled, NULL);
+}
+
+
+int main (int argc, char **argv)
+{
+  GNUNET_SCHEDULER_run (&init, NULL);
+  return final_count < 5000 ? 0 : 1;
+}
diff --git a/src/util/test_scheduler_hogging_priority.c b/src/util/test_scheduler_hogging_priority.c
new file mode 100644
index 000000000..217a39ce7
--- /dev/null
+++ b/src/util/test_scheduler_hogging_priority.c
@@ -0,0 +1,55 @@
+#include "gnunet_util_lib.h"
+#include <unistd.h>
+
+static int count = 0;
+static int final_count;
+
+static void end (void *cls)
+{
+  final_count = count;
+  count = 5000;
+  GNUNET_SCHEDULER_shutdown ();
+}
+
+static void self_rescheduling (void *cls)
+{
+  if (count == 0)
+  {
+    GNUNET_SCHEDULER_add_delayed_with_priority (GNUNET_TIME_UNIT_MILLISECONDS,
+                                                GNUNET_SCHEDULER_PRIORITY_URGENT,
+                                                &end,
+                                                NULL);
+    sleep(1);
+    /* end should be added to ready queue on next scheduler pass for certain
+       now */
+  }
+  if (++count < 5000)
+  {
+    GNUNET_SCHEDULER_add_now (&self_rescheduling, NULL);
+  }
+}
+
+
+static void noop (void *cls)
+{
+}
+
+static void indirection (void *cls)
+{
+  GNUNET_SCHEDULER_add_with_reason_and_priority (&self_rescheduling, NULL,
+                                                 GNUNET_SCHEDULER_REASON_STARTUP,
+                                                 GNUNET_SCHEDULER_PRIORITY_HIGH);
+}
+
+static void init (void *cls)
+{
+  GNUNET_SCHEDULER_add_now (&indirection, NULL);
+  GNUNET_SCHEDULER_add_now (&noop, NULL);
+}
+
+
+int main (int argc, char **argv)
+{
+  GNUNET_SCHEDULER_run (&init, NULL);
+  return final_count < 5000 ? 0 : 1;
+}
-- 
2.38.1

