This is the mail archive of the
libc-alpha@sourceware.org
mailing list for the glibc project.
[RFC v2][PATCH 25/27] Add Infinity notes implementing libpthread::thr_iter
- From: Gary Benson <gbenson at redhat dot com>
- To: libc-alpha at sourceware dot org
- Date: Mon, 13 Jun 2016 11:38:29 +0100
- Subject: [RFC v2][PATCH 25/27] Add Infinity notes implementing libpthread::thr_iter
- Authentication-results: sourceware.org; auth=none
- References: <1465814311-31470-1-git-send-email-gbenson at redhat dot com>
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
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)
--
1.7.1