[RFC,v2,25/27] Add Infinity notes implementing libpthread::thr_iter

Message ID 1465814311-31470-26-git-send-email-gbenson@redhat.com
State New, archived
Headers

Commit Message

Gary Benson June 13, 2016, 10:38 a.m. UTC
  This commit adds the Infinity function libpthread::thr_iter,
the Infinity equivalent of libthread_db's td_ta_thr_iter.
---
 nptl/Makefile                 |    2 +-
 nptl/infinity-thr_iter.i8     |  187 ++++++++++++++++++++++++++++++++++
 nptl/tst-infinity-thr_iter.py |  221 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 409 insertions(+), 1 deletions(-)
 create mode 100644 nptl/infinity-thr_iter.i8
 create mode 100644 nptl/tst-infinity-thr_iter.py
  

Patch

diff --git a/nptl/Makefile b/nptl/Makefile
index 9beb879..6967940 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -139,7 +139,7 @@  libpthread-routines = nptl-init vars events version pt-interp \
 #		      pthread_setresgid
 
 ifeq ($(build-infinity),yes)
-infinity-routines = infinity-map_lwp2thr
+infinity-routines = infinity-map_lwp2thr infinity-thr_iter
 
 libpthread-routines += $(infinity-routines)
 endif
diff --git a/nptl/infinity-thr_iter.i8 b/nptl/infinity-thr_iter.i8
new file mode 100644
index 0000000..7c079fe
--- /dev/null
+++ b/nptl/infinity-thr_iter.i8
@@ -0,0 +1,187 @@ 
+/* Iterate over a process's threads.
+   Copyright (C) 2003-2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include "infinity-nptl.i8"
+
+/* Call CALLBACK (TD, CBDATA_P) for each thread in the list starting
+   at HEAD.  Our first return value will be TD_OK on success, or a
+   non-TD_OK td_err_e code indicating the reason for failure.  If our
+   first return value was TD_OK then our second return value will be
+   TRUE if the list is uninitialized or empty; FALSE otherwise.  If
+   our first return value was not TD_OK then our second return value
+   is undefined.  */
+
+define MODULE_NAME::__iterate_thread_list returns td_err_e, bool
+	argument thr_iter_f callback
+	argument opaque cbdata_p
+	argument int ti_pri
+	argument ptr head
+	argument pid_t match_pid
+	argument bool fake_if_empty
+
+  /* Load the first descriptor in the list.  If it's NULL then
+     __pthread_initialize_minimal has not gotten far enough and
+     we may need to fake a descriptor for the main thread.  */
+	deref LIST_T_NEXT_OFFSET(head), ptr
+	dup
+	beq NULL, libpthread_maybe_uninitialized
+	dup
+	beq head, libpthread_maybe_uninitialized
+
+  /* Load our second return value (FALSE, to indicate that the supplied
+     list was not uninitialized or empty).  */
+	load FALSE
+	swap
+
+  /* Main loop.  ToS is a pointer to a list_t, either the list field
+     of a struct pthread, or to the list head if we're at the end.  */
+loop:
+	dup
+	beq head, end_of_list
+	sub PTHREAD_LIST_OFFSET
+	name 0, descr
+
+  /* Verify that this thread's pid field matches the child PID.  If
+     its pid field is negative, it's about to do a fork or it's the
+     sole thread in a fork child. */
+	deref PTHREAD_PID_OFFSET(descr), pid_t
+	dup
+	bge 0, test_pid
+
+  /* If pid == -match_pid it's about to do a fork, but it's really
+     still the parent PID.  */
+	neg
+	beq match_pid, pid_matches /* It's about to fork. */
+
+  /* It must be a fork child, whose new PID is in the tid field.  */
+	deref PTHREAD_TID_OFFSET(descr), pid_t
+
+test_pid:
+	bne match_pid, continue_loop
+
+pid_matches:
+  /* Now test whether this thread matches the specified conditions.  */
+	deref PTHREAD_SCHEDPOLICY_OFFSET(descr), i32
+	bne SCHED_OTHER, load_priority
+	load 0
+	goto test_priority
+
+load_priority:
+	deref PTHREAD_SCHEDPARAM_SCHED_PRIORITY_OFFSET(descr), i32
+
+test_priority:
+	blt ti_pri, continue_loop
+
+  /* It matches, call the callback function.  */
+        load descr
+	load cbdata_p
+	call callback
+	bne 0, main_loop_callback_failed
+
+continue_loop:
+  /* ToS is descr.  */
+	add PTHREAD_LIST_OFFSET
+	add LIST_T_NEXT_OFFSET
+	deref ptr
+	goto loop
+
+end_of_list:
+  /* ToS is head.  */
+	drop
+	load TD_OK
+	return
+
+libpthread_maybe_uninitialized:
+	load TRUE  /* The supplied list was uninitialized or empty).  */
+	load fake_if_empty
+	beq TRUE, fake_main_thread
+  /* We do not need to fake the main thread.  */
+	load TD_OK
+	return
+
+fake_main_thread:
+  /* __pthread_initialize_minimal has not gotten far enough.  We
+     need to call the callback for the main thread, but we can't
+     rely on its thread register as they sometimes contain garbage
+     that would confuse us (left by the kernel at exec).   We fake
+     a special descriptor of NULL for the initial thread; other
+     routines in this library recognise this special descriptor
+     and act accordingly.  */
+	load NULL
+	load cbdata_p
+	call callback
+	bne 0, fake_main_callback_failed
+	load TD_OK
+	return
+
+fake_main_callback_failed:
+	load TD_DBERR
+	return
+
+main_loop_callback_failed:
+  /* ToS is descr.  */
+	drop
+	load TD_DBERR
+	return
+
+/* Call CALLBACK (TD, CBDATA_P) for each of a process's threads, with
+   TD being a thread descriptor for the thread.  Thread descriptors
+   are opaque pointers and should not be dereferenced outside of this
+   library.  Return TD_OK on success, or a non-TD_OK td_err_e code
+   indicating the reason for failure.  The callback should return 0
+   to indicate success; if the callback returns otherwise then this
+   iteration will stop and this function will return TD_DBERR.  */
+
+define MODULE_NAME::thr_iter returns td_err_e
+	argument thr_iter_f callback
+	argument opaque cbdata_p
+	argument int ti_pri
+
+  /* The thread library keeps two lists for the running threads.
+     One list (__stack_user) contains the thread which are using
+     user-provided stacks and the other (__stack_used) includes the
+     threads for which the thread library allocated the stacks.  We
+     have to iterate over both lists separately.  We're going to
+     start with __stack_user, but we're going to set up the stack
+     for the second call (to iterate __stack_used) first.  */
+	load __stack_used
+
+  /* Get the PID of the main thread.  */
+	call procservice::getpid
+	name 0, main_pid
+
+  /* Process the list of threads with user-provided stacks.  */
+	load callback
+	load cbdata_p
+	load ti_pri
+	load __stack_user
+	load main_pid
+	load FALSE
+	call __iterate_thread_list
+
+	dup  /* Save code in case it's not TD_OK.  */
+	bne TD_OK, first_call_failed
+	drop /* It was TD_OK, we can drop it now.  */
+
+  /* Process the list of threads with library-allocated stacks.  */
+	call __iterate_thread_list
+	return
+
+first_call_failed:
+  /* ToS is td_err_e error code from __iterate_thread_list.  */
+	return
diff --git a/nptl/tst-infinity-thr_iter.py b/nptl/tst-infinity-thr_iter.py
new file mode 100644
index 0000000..f9f3354
--- /dev/null
+++ b/nptl/tst-infinity-thr_iter.py
@@ -0,0 +1,221 @@ 
+from i8c.runtime import TestCase
+
+TestCase.import_builtin_constants()
+TestCase.import_constants_from("infinity-nptl-constants.h")
+TestCase.import_constants_from("infinity-nptl_db-constants.h")
+
+class TestThread(object):
+    def __init__(self, pid, policy=SCHED_OTHER, priority=0, tid=None):
+        self.pid = pid
+        self.policy = policy
+        self.priority = priority
+        self.tid = tid
+
+    def write_into(self, buf):
+        self.__buf = buf
+        buf.store_i32(PTHREAD_PID_OFFSET, self.pid)
+        buf.store_i32(PTHREAD_SCHEDPOLICY_OFFSET, self.policy)
+        buf.store_i32(PTHREAD_SCHEDPARAM_SCHED_PRIORITY_OFFSET,
+                      self.priority)
+        if self.tid is not None:
+            buf.store_i32(PTHREAD_TID_OFFSET, self.tid)
+
+    @property
+    def handle(self):
+        return self.__buf.location
+
+    def matches(self, test):
+        if self.pid < 0:
+            if not (self.pid == -test.MAIN_PID
+                    or self.tid == test.MAIN_PID):
+                return False
+        elif self.pid != test.MAIN_PID:
+            return False
+        if self.priority < test.TI_PRIORITY:
+            return False
+        return True
+
+class TestThrIter(TestCase):
+    TESTFUNC = "libpthread::thr_iter(Fi(po)oi)i"
+    MAIN_PID = 30000
+
+    # Arguments call_thr_iter uses.
+    TI_CALLBACK_ARG = lambda x: x + 3
+    TI_PRIORITY = TD_THR_LOWEST_PRIORITY
+
+    def setUp(self):
+        # Set up the address space.
+        with self.memory.builder() as mem:
+            self.__setup_threads(mem, "__stack_user", self.STACK_USER)
+            self.__setup_threads(mem, "__stack_used", self.STACK_USED)
+
+    def __setup_threads(self, mem, symname, threads):
+        head = mem.alloc(symname)
+        if threads is None:
+            # This thread list is uninitialized.
+            head.store_ptr(LIST_T_NEXT_OFFSET, NULL)
+            return
+
+        prev = head
+        for src in threads:
+            dst = mem.alloc()
+            src.write_into(dst)
+
+            list = dst + PTHREAD_LIST_OFFSET
+            prev.store_ptr(LIST_T_NEXT_OFFSET, list)
+            prev = list
+        prev.store_ptr(LIST_T_NEXT_OFFSET, head)
+
+    def call_procservice_getpid(self):
+        """Implementation of procservice::getpid."""
+        return self.MAIN_PID
+
+    def recording_callback(self, handle, arg):
+        self.assertEqual(arg, self.TI_CALLBACK_ARG)
+        self.calls.append(handle)
+        return 0
+
+    def failing_callback(self, handle, arg):
+        self.assertEqual(arg, self.TI_CALLBACK_ARG)
+        return 1
+
+    def call_thr_iter(self, callback):
+        return self.i8ctx.call(self.TESTFUNC,
+                               callback,
+                               self.TI_CALLBACK_ARG,
+                               self.TI_PRIORITY)
+
+    def run_standard_test(self, expect_ncalls, null_ok=False):
+        # Check callback is called for the expected threads.
+        self.calls = []
+        result = self.call_thr_iter(self.recording_callback)
+        self.assertEqual(len(result), 1)
+        self.assertEqual(result[0], TD_OK)
+        self.check_calls(expect_ncalls, null_ok)
+        # Check that callback errors are handled.
+        if expect_ncalls != 0:
+            result = self.call_thr_iter(self.failing_callback)
+            self.assertEqual(len(result), 1)
+            self.assertEqual(result[0], TD_DBERR)
+
+    def check_calls(self, expect_ncalls, null_ok):
+        expect, empty_count = [], 0
+        for list in self.STACK_USER, self.STACK_USED:
+            if not list:
+                empty_count += 1
+                continue
+            for thread in list:
+                if thread.matches(self):
+                    expect.append(thread.handle)
+        if empty_count == 2:
+            expect.append(NULL) # faked main process
+        # Check the list we've built seems right.
+        self.assertEqual(len(expect), expect_ncalls)
+        if not null_ok:
+            self.assertNotIn(NULL, expect)
+        # Now check our list matches what happened.
+        self.assertEqual(self.calls, expect)
+
+# Tests with uninitialized and partly initialized thread lists.
+
+class TestThrIter_both_uninit(TestThrIter):
+    STACK_USER = None
+    STACK_USED = None
+
+    def test_both_uninit(self):
+       """Test thr_iter with both lists uninitialized"""
+       self.run_standard_test(1, True)
+
+class TestThrIter_stack_user_uninit(TestThrIter):
+    STACK_USER = None
+    STACK_USED = []
+
+    def test_stack_user_uninit(self):
+        """Test thr_iter with __stack_user uninitialized"""
+        # There is a tiny window in glibc where this setup can happen.
+        self.run_standard_test(1, True)
+
+class TestThrIter_both_empty(TestThrIter):
+    STACK_USER = []
+    STACK_USED = []
+
+    def test_stack_user_uninit(self):
+        """Test thr_iter with both lists initialized but empty"""
+        # There is a tiny window in glibc where this setup can happen.
+        self.run_standard_test(1, True)
+
+class TestThrIter_stack_used_uninit_1(TestThrIter):
+    STACK_USER = []
+    STACK_USED = None
+
+    def test_stack_used_uninit_1(self):
+        """Test thr_iter with __stack_used uninitialized (1)"""
+        # This should never happen in glibc (__stack_used is
+        # initialized first) but we test it anyway.
+        self.run_standard_test(1, True)
+
+class TestThrIter_stack_used_uninit_2(TestThrIter):
+    STACK_USER = [TestThread(TestThrIter.MAIN_PID)]
+    STACK_USED = None
+
+    def test_stack_used_uninit_2(self):
+        """Test thr_iter with __stack_used uninitialized (2)"""
+        # This should never happen in glibc (__stack_used is
+        # initialized first) but we test it anyway.
+        self.run_standard_test(1)
+
+# Test with threads on both lists.
+
+class TestThrIter_regular(TestThrIter):
+    STACK_USER = [TestThread(TestThrIter.MAIN_PID),
+                  TestThread(TestThrIter.MAIN_PID, SCHED_FIFO, 5),
+                  TestThread(TestThrIter.MAIN_PID, SCHED_RR, -14),
+                  TestThread(TestThrIter.MAIN_PID),
+                  TestThread(TestThrIter.MAIN_PID + 1),
+                  TestThread(TestThrIter.MAIN_PID + 2),
+                  TestThread(TestThrIter.MAIN_PID)]
+    STACK_USED = [TestThread(TestThrIter.MAIN_PID + 4, SCHED_FIFO, -3),
+                  TestThread(TestThrIter.MAIN_PID),
+                  TestThread(TestThrIter.MAIN_PID, SCHED_RR, -5),
+                  TestThread(TestThrIter.MAIN_PID + 2, SCHED_RR, -3),
+                  TestThread(TestThrIter.MAIN_PID + 1),
+                  TestThread(TestThrIter.MAIN_PID),
+                  TestThread(TestThrIter.MAIN_PID + 2),
+                  TestThread(TestThrIter.MAIN_PID + 1),
+                  TestThread(TestThrIter.MAIN_PID - 1),
+                  # Threads which are about to fork.
+                  TestThread(-TestThrIter.MAIN_PID),
+                  TestThread(-TestThrIter.MAIN_PID, SCHED_FIFO, -3),
+                  # Threads which are fork children.
+                  TestThread(-(TestThrIter.MAIN_PID + 1),
+                             tid=TestThrIter.MAIN_PID),
+                  TestThread(-(TestThrIter.MAIN_PID + 2),
+                             SCHED_RR, -5,
+                             tid=TestThrIter.MAIN_PID),
+                  TestThread(-(TestThrIter.MAIN_PID + 2),
+                             tid=TestThrIter.MAIN_PID + 4),
+    ]
+
+    def test_thr_iter(self):
+        """Test thr_iter with both lists initialized"""
+        self.run_standard_test(12)
+
+    def test_by_priority(self):
+        """Test thr_iter priority filtering works."""
+        counts = {}
+        for list in self.STACK_USER, self.STACK_USED:
+            for thread in list:
+                if thread.matches(self):
+                    priority = thread.priority
+                    counts[priority] = counts.get(priority, 0) + 1
+
+        pstart = TD_THR_LOWEST_PRIORITY
+        plimit = pstart + 32 # POSIX
+        self.assertLessEqual(pstart, min(counts.keys()))
+        self.assertGreater(plimit, max(counts.keys()))
+
+        nthreads = 0
+        for priority in reversed(range(pstart, plimit)):
+            nthreads += counts.get(priority, 0)
+            self.TI_PRIORITY = priority
+            self.run_standard_test(nthreads)