From patchwork Mon Jun 13 10:38:29 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gary Benson X-Patchwork-Id: 13017 Received: (qmail 58952 invoked by alias); 13 Jun 2016 10:41:17 -0000 Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: libc-alpha-owner@sourceware.org Delivered-To: mailing list libc-alpha@sourceware.org Received: (qmail 58330 invoked by uid 89); 13 Jun 2016 10:41:15 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-2.3 required=5.0 tests=BAYES_00, KAM_LAZY_DOMAIN_SECURITY, RP_MATCHES_RCVD, SPF_HELO_PASS autolearn=ham version=3.3.2 spammy=30000, supplied, tos, indicating X-HELO: mx1.redhat.com From: Gary Benson To: libc-alpha@sourceware.org Subject: [RFC v2][PATCH 25/27] Add Infinity notes implementing libpthread::thr_iter Date: Mon, 13 Jun 2016 11:38:29 +0100 Message-Id: <1465814311-31470-26-git-send-email-gbenson@redhat.com> In-Reply-To: <1465814311-31470-1-git-send-email-gbenson@redhat.com> References: <1465814311-31470-1-git-send-email-gbenson@redhat.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 + . */ + +#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)