From 055abe523c2c3f6c8f1dccfb53565209222f90c1 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 21 Mar 2021 12:07:38 +0100 Subject: [PATCH] PORTAGE_NICENESS: Consider autogroup scheduling With Linux's autogroup scheduling feature (CONFIG_SCHED_AUTOGROUP) setting a nice value on a per-process base has only an effect for scheduling decisions relative to the other threads in the same session (typically: the same terminal window). See the section "The nice value and group scheduling" in the sched(7) man page. Basically this means that portage "just" setting the nice value, has no effect in presence of autogroup scheduling being active (which is probably true for most (desktop) user systems). This commit changes emerge to set the autogroup's nice value, instead of the processes' nice value, in case autogroups are present (detected by the existence of /proc/self/autogroup). The tricky part about autogroup nice values is that we want restore the orignal nice value once we are finished. As otherwise, the session, e.g. your terminal, would continue using this value, and so would subsequently executed processes. For that we use Python's atexit functinaly, to register a function that will restore the orignal nice value of the autogroup. Users may have set PORTAGE_NICENESS to a value outside of the range of valid nice values [-20, 19]. Calling os.nice() with such a value will simply cap the process's nice value, but writing this invalid value to the autogoup pseudo-file will fail with "Invalid argument". Since os.nice() returns the current nice value, we simply use the returned value to set the autogroup nice value. Portage would previously always change the nice value to zero, even if the user did not explicitly request so. Now we do not change the nice value unless requested. Closes: https://github.com/gentoo/portage/pull/727 Bug: https://bugs.gentoo.org/777492 Signed-off-by: Florian Schmaus Signed-off-by: Zac Medico --- lib/_emerge/actions.py | 48 +++++++++++++++++++++++++++++++++++++++--- man/make.conf.5 | 10 ++++++++- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/lib/_emerge/actions.py b/lib/_emerge/actions.py index 1946f49dfc..df5c86c6cc 100644 --- a/lib/_emerge/actions.py +++ b/lib/_emerge/actions.py @@ -14,6 +14,7 @@ import time import warnings from itertools import chain +from pathlib import Path import portage portage.proxy.lazyimport.lazyimport(globals(), @@ -2634,14 +2635,55 @@ def apply_priorities(settings): nice(settings) def nice(settings): + nice_value: str = settings.get("PORTAGE_NICENESS", "").strip() + if not nice_value: + return + try: - os.nice(int(settings.get("PORTAGE_NICENESS", "0"))) + current_nice_value = os.nice(int(nice_value)) + # Calling os.nice() with a value outside of the valid range of + # nice values, e.g. 20, caps the process's nice value. This is + # because the argument of os.nice() is not an absolute value, + # but the increment to the process's current nice + # value. Hence users may use PORTAGE_NICENESS=20 without any + # issues here. However, below we write nice_value potentially + # to /proc/self/autogroup, which will only accept valid nice + # values. Therefore we simply set nice_value to what os.nice() + # returned (i.e. the process's current nice value). + nice_value = str(current_nice_value) except (OSError, ValueError) as e: out = portage.output.EOutput() - out.eerror("Failed to change nice value to '%s'" % \ - settings.get("PORTAGE_NICENESS", "0")) + out.eerror(f"Failed to change nice value to {nice_value}") out.eerror("%s\n" % str(e)) + autogroup_file = Path("/proc/self/autogroup") + try: + f = autogroup_file.open("r+") + except EnvironmentError: + # Autogroup scheduling is not enabled on this system. + return + + with f: + line = f.readline() + original_autogroup_nice_value = line.split(" ")[2] + + # We need to restore the original nice value of the + # autogroup, as otherwise the session, e.g. the + # terminal where portage was executed in, would + # continue running with that value. + portage.atexit_register( + lambda value: autogroup_file.write_text(value), + original_autogroup_nice_value, + ) + + try: + f.write(nice_value) + except EnvironmentError as e: + out = portage.output.EOutput() + out.eerror(f"Failed to change autogroup's nice value to {nice_value}") + out.eerror("%s\n" % str(e)) + + def ionice(settings): ionice_cmd = settings.get("PORTAGE_IONICE_COMMAND") diff --git a/man/make.conf.5 b/man/make.conf.5 index 1c72109ad3..18573b5e22 100644 --- a/man/make.conf.5 +++ b/man/make.conf.5 @@ -1,4 +1,4 @@ -.TH "MAKE.CONF" "5" "May 2021" "Portage VERSION" "Portage" +.TH "MAKE.CONF" "5" "Jun 2021" "Portage VERSION" "Portage" .SH "NAME" make.conf \- custom settings for Portage .SH "SYNOPSIS" @@ -1031,6 +1031,14 @@ The value of this variable will be added to the current nice level that emerge is running at. In other words, this will not set the nice level, it will increment it. For more information about nice levels and what are acceptable ranges, see \fBnice\fR(1). +.br +If set and portage is run under Linux with autogroup scheduling (see +\fBsched\fR(7)) enabled, then portage will set the nice value of its +autogroup to PORTAGE_NICENESS. Upon exiting, portage will restore the +original value. Note that if the function responsible for restoring the +original value is not run, e.g., because portage's process was killed, +then the autogroup will stay niced. In such a case, the value can be +reset via corresponding autogroup pseudo\-file in /proc. .TP \fBPORTAGE_RO_DISTDIRS\fR = \fI[space delimited list of directories]\fR When a given file does not exist in \fBDISTDIR\fR, search for the file