diff --git a/supervisor/options.py b/supervisor/options.py index 2f0c98f1d..15348f57b 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -891,6 +891,7 @@ def get(section, opt, *args, **kwargs): serverurl = get(section, 'serverurl', None) if serverurl and serverurl.strip().upper() == 'AUTO': serverurl = None + oom_score_adj = get(section, 'oom_score_adj', None) # find uid from "user" option user = get(section, 'user', None) @@ -902,7 +903,6 @@ def get(section, opt, *args, **kwargs): umask = get(section, 'umask', None) if umask is not None: umask = octal_type(umask) - process_name = process_or_group_name( get(section, 'process_name', '%(program_name)s', do_expand=False)) @@ -1001,6 +1001,7 @@ def get(section, opt, *args, **kwargs): killasgroup=killasgroup, exitcodes=exitcodes, redirect_stderr=redirect_stderr, + oom_score_adj=oom_score_adj, environment=environment, serverurl=serverurl) @@ -1571,6 +1572,14 @@ def close_child_pipes(self, pipes): if fd is not None: self.close_fd(fd) + def set_oom_score_adj(self, oom_score_adj): + try: + procfile = open('/proc/%s/oom_score_adj' % os.getpid(), 'w') + procfile.write(str(oom_score_adj) + '\n') + procfile.close() + except IOError: + return "Can't set oom_score_adj to %s" % oom_score_adj + class ClientOptions(Options): positional_args_allowed = 1 @@ -1809,7 +1818,7 @@ class ProcessConfig(Config): 'stderr_logfile_backups', 'stderr_logfile_maxbytes', 'stderr_events_enabled', 'stderr_syslog', 'stopsignal', 'stopwaitsecs', 'stopasgroup', 'killasgroup', - 'exitcodes', 'redirect_stderr' ] + 'exitcodes', 'redirect_stderr', 'oom_score_adj' ] optional_param_names = [ 'environment', 'serverurl' ] def __init__(self, options, **params): diff --git a/supervisor/process.py b/supervisor/process.py index e2fef5b70..fd1636400 100644 --- a/supervisor/process.py +++ b/supervisor/process.py @@ -296,6 +296,13 @@ def _spawn_as_child(self, filename, argv): self._prepare_child_fds() # sending to fd 2 will put this output in the stderr log + # set oom_score_adj, better to do it before dropping privileges + # so it can also be decreased + oom_score_adj_msg = self.set_oom_score_adj() + if oom_score_adj_msg: + options.write(2, "supervisor: %s\n" % oom_score_adj_msg) + return + # set user setuid_msg = self.set_uid() if setuid_msg: @@ -573,6 +580,11 @@ def set_uid(self): msg = self.config.options.dropPrivileges(self.config.uid) return msg + def set_oom_score_adj(self): + if self.config.oom_score_adj is None: + return + return self.config.options.set_oom_score_adj(self.config.oom_score_adj) + def __lt__(self, other): return self.config.priority < other.config.priority diff --git a/supervisor/tests/base.py b/supervisor/tests/base.py index eb2e3fe51..17457f8e1 100644 --- a/supervisor/tests/base.py +++ b/supervisor/tests/base.py @@ -87,6 +87,7 @@ def __init__(self): self.changed_directory = False self.chdir_error = None self.umaskset = None + self.oom_score_adj_set = None self.poller = DummyPoller(self) def getLogger(self, *args, **kw): @@ -261,6 +262,9 @@ def chdir(self, dir): def setumask(self, mask): self.umaskset = mask + def set_oom_score_adj(self, oom_score_adj): + self.oom_score_adj_set = oom_score_adj + class DummyLogger: level = None @@ -518,7 +522,8 @@ def __init__(self, options, name, command, directory=None, umask=None, stderr_syslog=False, redirect_stderr=False, stopsignal=None, stopwaitsecs=10, stopasgroup=False, killasgroup=False, - exitcodes=(0,2), environment=None, serverurl=None): + exitcodes=(0,2), environment=None, serverurl=None, + oom_score_adj=None): self.options = options self.name = name self.command = command @@ -554,6 +559,7 @@ def __init__(self, options, name, command, directory=None, umask=None, self.umask = umask self.autochildlogs_created = False self.serverurl = serverurl + self.oom_score_adj = oom_score_adj def create_autochildlogs(self): self.autochildlogs_created = True diff --git a/supervisor/tests/test_options.py b/supervisor/tests/test_options.py index 815ffa963..f4c25e623 100644 --- a/supervisor/tests/test_options.py +++ b/supervisor/tests/test_options.py @@ -3008,7 +3008,7 @@ def _makeOne(self, *arg, **kw): 'stderr_events_enabled', 'stderr_syslog', 'stopsignal', 'stopwaitsecs', 'stopasgroup', 'killasgroup', 'exitcodes', 'redirect_stderr', - 'environment'): + 'environment', 'oom_score_adj'): defaults[name] = name for name in ('stdout_logfile_backups', 'stdout_logfile_maxbytes', 'stderr_logfile_backups', 'stderr_logfile_maxbytes'): @@ -3090,7 +3090,7 @@ def _makeOne(self, *arg, **kw): 'stderr_events_enabled', 'stderr_syslog', 'stopsignal', 'stopwaitsecs', 'stopasgroup', 'killasgroup', 'exitcodes', 'redirect_stderr', - 'environment'): + 'environment', 'oom_score_adj'): defaults[name] = name for name in ('stdout_logfile_backups', 'stdout_logfile_maxbytes', 'stderr_logfile_backups', 'stderr_logfile_maxbytes'): @@ -3138,7 +3138,7 @@ def _makeOne(self, *arg, **kw): 'stderr_events_enabled', 'stderr_syslog', 'stopsignal', 'stopwaitsecs', 'stopasgroup', 'killasgroup', 'exitcodes', 'redirect_stderr', - 'environment'): + 'environment', 'oom_score_adj'): defaults[name] = name for name in ('stdout_logfile_backups', 'stdout_logfile_maxbytes', 'stderr_logfile_backups', 'stderr_logfile_maxbytes'): diff --git a/supervisor/tests/test_process.py b/supervisor/tests/test_process.py index 7144b4c7d..0ffc9131b 100644 --- a/supervisor/tests/test_process.py +++ b/supervisor/tests/test_process.py @@ -420,6 +420,24 @@ def test_spawn_as_child_sets_umask(self): self.assertEqual(options.written, {2: "supervisor: child process was not spawned\n"}) + def test_spawn_as_child_sets_oom_score_adj(self): + options = DummyOptions() + options.forkpid = 0 + config = DummyPConfig(options, 'good', '/good/filename', + oom_score_adj=100) + instance = self._makeOne(config) + result = instance.spawn() + self.assertEqual(result, None) + self.assertEqual(options.execv_args, + ('/good/filename', ['/good/filename']) ) + self.assertEqual(options.oom_score_adj_set, 100) + self.assertEqual(options.execve_called, True) + # if the real execve() succeeds, the code that writes the + # "was not spawned" message won't be reached. this assertion + # is to test that no other errors were written. + self.assertEqual(options.written, + {2: "supervisor: child process was not spawned\n"}) + def test_spawn_as_child_cwd_fail(self): options = DummyOptions() options.forkpid = 0 diff --git a/supervisor/tests/test_supervisord.py b/supervisor/tests/test_supervisord.py index df1523296..fb5d9b6af 100644 --- a/supervisor/tests/test_supervisord.py +++ b/supervisor/tests/test_supervisord.py @@ -332,6 +332,7 @@ def make_pconfig(name, command, **params): 'stopasgroup': False, 'killasgroup': False, 'exitcodes': (0,2), 'environment': None, 'serverurl': None, + 'oom_score_adj': None } result.update(params) return ProcessConfig(options, **result)