From 1875f65a5434aa89c559076031b88a3cb060acc4 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Fri, 29 Jan 2016 05:32:12 -0500 Subject: [PATCH] 6515172: Runtime.availableProcessors() ignores Linux taskset command Extract processor count from sched_getaffinity mask Reviewed-by: dcubed, stuefe, gthornbr --- hotspot/src/os/linux/vm/globals_linux.hpp | 7 +- hotspot/src/os/linux/vm/os_linux.cpp | 81 ++++++++++++-- hotspot/src/share/vm/logging/logTag.hpp | 3 +- .../test/runtime/os/AvailableProcessors.java | 102 ++++++++++++++++++ 4 files changed, 184 insertions(+), 9 deletions(-) create mode 100644 hotspot/test/runtime/os/AvailableProcessors.java diff --git a/hotspot/src/os/linux/vm/globals_linux.hpp b/hotspot/src/os/linux/vm/globals_linux.hpp index 29e450f8db..05f4c18133 100644 --- openjdk/hotspot/src/os/linux/vm/globals_linux.hpp +++ openjdk/hotspot/src/os/linux/vm/globals_linux.hpp @@ -49,6 +49,9 @@ product(bool, UseSHM, false, \ "Use SYSV shared memory for large pages") \ \ + diagnostic(bool, UseCpuAllocPath, false, \ + "Use CPU_ALLOC code path in os::active_processor_count ") \ + \ product(bool, UseContainerSupport, true, \ "Enable detection and runtime container configuration support") \ \ diff --git a/hotspot/src/os/linux/vm/os_linux.cpp b/hotspot/src/os/linux/vm/os_linux.cpp index 7e01a577d5..61406a11dd 100644 --- openjdk/hotspot/src/os/linux/vm/os_linux.cpp +++ openjdk/hotspot/src/os/linux/vm/os_linux.cpp @@ -106,6 +107,14 @@ PRAGMA_FORMAT_MUTE_WARNINGS_FOR_GCC +#ifndef _GNU_SOURCE + #define _GNU_SOURCE + #include + #undef _GNU_SOURCE +#else + #include +#endif + // if RUSAGE_THREAD for getrusage() has not been defined, do it here. The code calling // getrusage() is prepared to handle the associated failure. #ifndef RUSAGE_THREAD @@ -5335,12 +5344,57 @@ } }; +// Get the current number of available processors for this process. +// This value can change at any time during a process's lifetime. +// sched_getaffinity gives an accurate answer as it accounts for cpusets. +// If it appears there may be more than 1024 processors then we do a +// dynamic check - see 6515172 for details. +// If anything goes wrong we fallback to returning the number of online +// processors - which can be greater than the number available to the process. int os::Linux::active_processor_count() { - // Linux doesn't yet have a (official) notion of processor sets, - // so just return the number of online processors. - int online_cpus = ::sysconf(_SC_NPROCESSORS_ONLN); - assert(online_cpus > 0 && online_cpus <= processor_count(), "sanity check"); - return online_cpus; + cpu_set_t cpus; // can represent at most 1024 (CPU_SETSIZE) processors + cpu_set_t* cpus_p = &cpus; + int cpus_size = sizeof(cpu_set_t); + + int configured_cpus = processor_count(); // upper bound on available cpus + int cpu_count = 0; + + // To enable easy testing of the dynamic path on different platforms we + // introduce a diagnostic flag: UseCpuAllocPath + if (configured_cpus >= CPU_SETSIZE || UseCpuAllocPath) { + // kernel may use a mask bigger than cpu_set_t + cpus_p = CPU_ALLOC(configured_cpus); + if (cpus_p != NULL) { + cpus_size = CPU_ALLOC_SIZE(configured_cpus); + // zero it just to be safe + CPU_ZERO_S(cpus_size, cpus_p); + } + else { + // failed to allocate so fallback to online cpus + int online_cpus = ::sysconf(_SC_NPROCESSORS_ONLN); + return online_cpus; + } + } + + // pid 0 means the current thread - which we have to assume represents the process + if (sched_getaffinity(0, cpus_size, cpus_p) == 0) { + if (cpus_p != &cpus) { + cpu_count = CPU_COUNT_S(cpus_size, cpus_p); + } + else { + cpu_count = CPU_COUNT(cpus_p); + } + } + else { + cpu_count = ::sysconf(_SC_NPROCESSORS_ONLN); + } + + if (cpus_p != &cpus) { + CPU_FREE(cpus_p); + } + + assert(cpu_count > 0 && cpu_count <= processor_count(), "sanity check"); + return cpu_count; } // Determine the active processor count from one of diff --git a/hotspot/test/runtime/os/AvailableProcessors.java b/hotspot/test/runtime/os/AvailableProcessors.java new file mode 100644 index 0000000000..4188a11d91 --- /dev/null +++ openjdk/hotspot/test/runtime/os/AvailableProcessors.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.io.File; +import jdk.test.lib.ProcessTools; +import jdk.test.lib.OutputAnalyzer; +import java.util.ArrayList; + +/* + * @test + * @bug 6515172 + * @summary Check that availableProcessors reports the correct value when running in a cpuset on linux + * @requires os.family == "linux" + * @library /testlibrary + * @build jdk.test.lib.* + * @run driver AvailableProcessors + */ +public class AvailableProcessors { + + static final String SUCCESS_STRING = "Found expected processors: "; + + public static void main(String[] args) throws Exception { + if (args.length > 0) + checkProcessors(Integer.parseInt(args[0])); + else { + // run ourselves under different cpu configurations + // using the taskset command + String taskset; + final String taskset1 = "/bin/taskset"; + final String taskset2 = "/usr/bin/taskset"; + if (new File(taskset1).exists()) + taskset = taskset1; + else if (new File(taskset2).exists()) + taskset = taskset2; + else { + System.out.println("Skipping test: could not find taskset command"); + return; + } + + int available = Runtime.getRuntime().availableProcessors(); + + if (available == 1) { + System.out.println("Skipping test: only one processor available"); + return; + } + + // Get the java command we want to execute + // Enable logging for easier failure diagnosis + ProcessBuilder master = + ProcessTools.createJavaProcessBuilder(false, + "-Xlog:os=trace", + "AvailableProcessors"); + + int[] expected = new int[] { 1, available/2, available-1, available }; + + for (int i : expected) { + System.out.println("Testing for " + i + " processors ..."); + int max = i - 1; + ArrayList cmdline = new ArrayList<>(master.command()); + // prepend taskset command + cmdline.add(0, "0-" + max); + cmdline.add(0, "-c"); + cmdline.add(0, taskset); + // append expected processor count + cmdline.add(String.valueOf(i)); + ProcessBuilder pb = new ProcessBuilder(cmdline); + System.out.println("Final command line: " + + ProcessTools.getCommandLine(pb)); + OutputAnalyzer output = ProcessTools.executeProcess(pb); + output.shouldContain(SUCCESS_STRING); + } + } + } + + static void checkProcessors(int expected) { + int available = Runtime.getRuntime().availableProcessors(); + if (available != expected) + throw new Error("Expected " + expected + " processors, but found " + + available); + else + System.out.println(SUCCESS_STRING + available); + } +}