Run subprocess at high prio - python

I need to run a jar file from my python script that has to meet some realtime requirements (it's a programmer).
What I currently do is
import subprocess
process = subprocess.Popen(shlex.split("java programmer.jar"), stdout=subprocess.PIPE)
but the programmer stalls on a regular basis what doesn't happen if I start it by hand using start /high java programmer.jar. So I'd like to somehow also set the priority of the subprocess directly from my python script.
But if use the command above, I get a FileNotFoundError, so Google told me that I have to use the shell=True flag. That solved my problem but created a new one as I now have my jar running in another shell so I cannot parse the stdout anymore what I have to do to take some actions.
So is there a way to either run subprocess directly at a higher priority or to redirect the console output of the shell it creates when I run
process = subprocess.Popen(shlex.split("java programmer.jar"), stdout=subprocess.PIPE, shell=True)
This has to run on Windows

The kernel schedules a thread at one of 32 priority levels. The base priority depends on the thread priority relative to the process priority class:
IDLE_PRIORITY_CLASS = 0x00000040
BELOW_NORMAL_PRIORITY_CLASS = 0x00004000
NORMAL_PRIORITY_CLASS = 0x00000020
ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000
HIGH_PRIORITY_CLASS = 0x00000080
REALTIME_PRIORITY_CLASS = 0x00000100
The realtime-priority class should only be used when absolutely necessary and only for short-lived operations. Even the high-priority class should be used sparingly and with care.
The initial priority class of a process can be set via the dwCreationFlags parameter of CreateProcessW. Python's subprocess.Popen supports this as its creationflags parameter. For convenience, the above priority-class constants were added in 3.7, but of course we can use these flag values in previous versions of Python.

Related

Using subprocess to run a Python script with shell=True

I have a Python script foo.py that I want to invoke with a second Python script using the subprocess module. The code in foo.py is provided by a user, and it could be malicious. I have set some security features in place (e.g., resource limits, change UID) prior to running the code to minimize the damage that the user can do.
I'd like to invoke the script with shell=True kwarg in the subprocess.run command because I am setting resource limits at the UID level. It seems like these limits do not apply if I do not add this kwarg. My code looks something like this:
import os
import subprocess
def enable_extra_security() -> None:
# set a different user ID for security reasons
os.setuid(1234)
# limit CPU time usage
resource.setrlimit(resource.RLIMIT_CPU, (5, 5))
# limit file size creation
resource.setrlimit(resource.RLIMIT_FSIZE, (100_000, 100_000))
# prevent forking
resource.setrlimit(resource.RLIMIT_NPROC, (5, 5))
if __name__ == '__main__':
# this script is run as root.
pid = os.fork()
if pid == 0:
# child process
enable_extra_security()
res = subprocess.run(['/usr/bin/python', 'foo.py'], shell=True, capture_output=True)
# ...
# child does some processing based on the returncode and output.
# ...
else:
# parent process
os.waitpid(pid, 0)
# ...
# ...
When I invoke enable_extra_security(), it only applies the additional security features to the child process. Specifically, the limits are only imposed on user ID 1234, which is only set for the child process. If I run subprocess.run without shell=True, the security features vanish. Unfortunately, the code above does not work: the subprocess.run command will actually just start a Python console and hang (i.e., it does not seem to pass in "foo.py" as an argument).
Interestingly, the code does work for C and C++ code (if I were to replace /usr/bin/python with /usr/bin/gcc and instead put a .c or .cpp file, it would work just fine). Why is this happening, and how can I solve my problem?

Python: Keep processes started by subprocess.Popen alive after exiting

I am making a virtual assistant that can start several programs using subprocess.Popen("path/to/app.exe"). But when I exit the python program, all of processes are killed. I want the processes (the applications started with Popen) to be independent and remain alive after main process is killed.
I have tried adding start_new_session=True as argument in subprocess.Popen() as some posts have suggested, but it's still not working.
I don't think showing the code is necessary, but still, here you go.
app_path = r'C:\Users\myusername\AppData\Local\Discord\app-1.0.9001\discord.exe'
subprocess.Popen(app_path) # also tried adding start_new_session=True as argument
Since you're on Windows, you can call the start command, which exists for this very purpose: to run another program independently of the one that starts it.
The start command is provided by the command-line interpreter cmd.exe. It is not an executable: there is no start.exe. It is a "shell command" (in Linux terminology), which is why shell=True must be passed when creating the subprocess.
You won't be able to communicate with the subprocess started in this way, that is, not via the pipe mechanism provided by the subprocess module. So instead of Popen, you may just use the convenience function run:
from subprocess import run
app = 'notepad'
run(['start', app], shell=True)
The example starts the Notepad editor (instead of Discord in the question) in order to make it easier to reproduce.
In cases where the full path to the app contains spaces, we can either call start like so
app = r'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
run(f'start "" "{app}"', shell=True)
using the Edge browser in this example, or pass the directory separately:
folder = r'C:\Program Files (x86)\Microsoft\Edge\Application'
app = 'msedge.exe'
run(['start', '/d', folder, app], shell=True)
This is needed because start treats a single argument as the window title if that argument is in quotes. And only if not does it treat it as the command. See "Can I use the start command with spaces in the path?" (on SuperUser) for more details.
Answered here: https://stackoverflow.com/a/34718600/4355695
subprocess.Popen(full_command, shell=True, close_fds=True)
(In my linux system I have to put shell=True if I'm passing a full command as string instead of a split-up array of arguments. In windows it may differ, idk)

Python subprocess.check_output() seems to ignore arguments

First of all, I read as many related questions to subprocess.check_output() as I could find, but still struggle to identify the problem.
If I execute kill -l 1 in the shell, I get the corresponding signal name for 1, which is HUP. I need the same behaviour in my python script, so I use:
>>> subprocess.check_output(['kill', '-l', '1'])
b'HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT\nCHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS\n'
The subprocess seems to ignore the '1' in the argument list and instead executes kill -l.
I tried different versions, the argument as a list or string, with shell optione True and False, but none seem to work.
Any ideas what could be the reason? Using python3.4 on a Ubuntu14.04.
Thanks!
Possible cause: The kill command in your shell is executing a shell built-in (most shells have one, because you need to be able to kill without a process launch when you have runaway fork bombs and the like), whereas check_output (not executing within a shell by default) is running the kill executable found in your PATH (often /bin/kill, but not always, running type -P kill in bash will tell you where that executable is).
Odds are, the built-in supports the switches you're looking for, the executable does not. In bash, try running type -P kill, then explicitly running the /full/path/to/kill -l 1 to see if the kill check_output is finding actually supports that invocation. Often there are subtle differences between different implementations of kill.
The best solution to this is probably to avoid expensive and pointless subprocess launches and check the Python definitions for the signals. For example, in Python 3.5, it's trivial to construct a mapping from the signals known by Python to their Python names:
import signal
sigdict = {sig.value: sig.name for sig in signal.Signals}
print(sigdict[1])
CTRL_BREAK_EVENT # <-- The output on my Windows box. On your machine, it would probably be SIGHUP
In older Python where the names aren't enums, you can use similar code using dir of the module, filtering for names whose __module__ is signal and whose values are integers to construct the mapping.

Python: How to determine subprocess children have all finished running

I am trying to detect when an installation program finishes executing from within a Python script. Specifically, the application is the Oracle 10gR2 Database. Currently I am using the subprocess module with Popen. Ideally, I would simply use the wait() method to wait for the installation to finish executing, however, the documented command actually spawns child processes to handle the actual installation. Here is some sample code of the failing code:
import subprocess
OUI_DATABASE_10GR2_SUBPROCESS = ['sudo',
'-u',
'oracle',
os.path.join(DATABASE_10GR2_TMP_PATH,
'database',
'runInstaller'),
'-ignoreSysPrereqs',
'-silent',
'-noconfig',
'-responseFile '+ORACLE_DATABASE_10GR2_SILENT_RESPONSE]
oracle_subprocess = subprocess.Popen(OUI_DATABASE_10GR2_SUBPROCESS)
oracle_subprocess.wait()
There is a similar question here: Killing a subprocess including its children from python, but the selected answer does not address the children issue, instead it instructs the user to call directly the application to wait for. I am looking for a specific solution that will wait for all children of the subprocess. What if there are an unknown number of subprocesses? I will select the answer that addresses the issue of waiting for all children subprocesses to finish.
More clarity on failure: The child processes continue executing after the wait() command since that command only waits for the top level process (in this case it is 'sudo'). Here is a simple diagram of the known child processes in this problem:
Python subprocess module -> Sudo -> runInstaller -> java -> (unknown)
Ok, here is a trick that will work only under Unix. It is similar to one of the answers to this question: Ensuring subprocesses are dead on exiting Python program. The idea is to create a new process group. You can then wait for all processes in the group to terminate.
pid = os.fork()
if pid == 0:
os.setpgrp()
oracle_subprocess = subprocess.Popen(OUI_DATABASE_10GR2_SUBPROCESS)
oracle_subprocess.wait()
os._exit(0)
else:
os.waitpid(-pid)
I have not tested this. It creates an extra subprocess to be the leader of the process group, but avoiding that is (I think) quite a bit more complicated.
I found this web page to be helpful as well. http://code.activestate.com/recipes/278731-creating-a-daemon-the-python-way/
You can just use os.waitpid with the the pid set to -1, this will wait for all the subprocess of the current process until they finish:
import os
import sys
import subprocess
proc = subprocess.Popen([sys.executable,
'-c',
'import subprocess;'
'subprocess.Popen("sleep 5", shell=True).wait()'])
pid, status = os.waitpid(-1, 0)
print pid, status
This is the result of pstree <pid> of different subprocess forked:
python───python───sh───sleep
Hope this can help :)
Check out the following link http://www.oracle-wiki.net/startdocsruninstaller which details a flag you can use for the runInstaller command.
This flag is definitely available for 11gR2, but I have not got a 10g database to try out this flag for the runInstaller packaged with that version.
Regards
Everywhere I look seems to say it's not possible to solve this in the general case. I've whipped up a library called 'pidmon' that combines some answers for Windows and Linux and might do what you need.
I'm planning to clean this up and put it on github, possibly called 'pidmon' or something like that. I'll post a link if/when I get it up.
EDIT: It's available at http://github.com/dbarnett/python-pidmon.
I made a special waitpid function that accepts a graft_func argument so that you can loosely define what sort of processes you want to wait for when they're not direct children:
import pidmon
pidmon.waitpid(oracle_subprocess.pid, recursive=True,
graft_func=(lambda p: p.name == '???' and p.parent.pid == ???))
or, as a shotgun approach, to just wait for any processes started since the call to waitpid to stop again, do:
import pidmon
pidmon.waitpid(oracle_subprocess.pid, graft_func=(lambda p: True))
Note that this is still barely tested on Windows and seems very slow on Windows (but did I mention it's on github where it's easy to fork?). This should at least get you started, and if it works at all for you, I have plenty of ideas on how to optimize it.

How to get environment from a subprocess?

I want to call a process via a python program, however, this process need some specific environment variables that are set by another process. How can I get the first process environment variables to pass them to the second?
This is what the program look like:
import subprocess
subprocess.call(['proc1']) # this set env. variables for proc2
subprocess.call(['proc2']) # this must have env. variables set by proc1 to work
but the to process don't share the same environment. Note that these programs aren't mine (the first is big and ugly .bat file and the second a proprietary soft) so I can't modify them (ok, I can extract all that I need from the .bat but it's very combersome).
N.B.: I am using Windows, but I prefer a cross-platform solution (but my problem wouldn't happen on a Unix-like ...)
Here's an example of how you can extract environment variables from a batch or cmd file without creating a wrapper script. Enjoy.
from __future__ import print_function
import sys
import subprocess
import itertools
def validate_pair(ob):
try:
if not (len(ob) == 2):
print("Unexpected result:", ob, file=sys.stderr)
raise ValueError
except:
return False
return True
def consume(iter):
try:
while True: next(iter)
except StopIteration:
pass
def get_environment_from_batch_command(env_cmd, initial=None):
"""
Take a command (either a single command or list of arguments)
and return the environment created after running that command.
Note that if the command must be a batch file or .cmd file, or the
changes to the environment will not be captured.
If initial is supplied, it is used as the initial environment passed
to the child process.
"""
if not isinstance(env_cmd, (list, tuple)):
env_cmd = [env_cmd]
# construct the command that will alter the environment
env_cmd = subprocess.list2cmdline(env_cmd)
# create a tag so we can tell in the output when the proc is done
tag = 'Done running command'
# construct a cmd.exe command to do accomplish this
cmd = 'cmd.exe /s /c "{env_cmd} && echo "{tag}" && set"'.format(**vars())
# launch the process
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=initial)
# parse the output sent to stdout
lines = proc.stdout
# consume whatever output occurs until the tag is reached
consume(itertools.takewhile(lambda l: tag not in l, lines))
# define a way to handle each KEY=VALUE line
handle_line = lambda l: l.rstrip().split('=',1)
# parse key/values into pairs
pairs = map(handle_line, lines)
# make sure the pairs are valid
valid_pairs = filter(validate_pair, pairs)
# construct a dictionary of the pairs
result = dict(valid_pairs)
# let the process finish
proc.communicate()
return result
So to answer your question, you would create a .py file that does the following:
env = get_environment_from_batch_command('proc1')
subprocess.Popen('proc2', env=env)
As you say, processes don't share the environment - so what you literally ask is not possible, not only in Python, but with any programming language.
What you can do is to put the environment variables in a file, or in a pipe, and either
have the parent process read them, and pass them to proc2 before proc2 is created, or
have proc2 read them, and set them locally
The latter would require cooperation from proc2; the former requires that the variables become known before proc2 is started.
Since you're apparently in Windows, you need a Windows answer.
Create a wrapper batch file, eg. "run_program.bat", and run both programs:
#echo off
call proc1.bat
proc2
The script will run and set its environment variables. Both scripts run in the same interpreter (cmd.exe instance), so the variables prog1.bat sets will be set when prog2 is executed.
Not terribly pretty, but it'll work.
(Unix people, you can do the same thing in a bash script: "source file.sh".)
You can use Process in psutil to get the environment variables for that Process.
If you want to implement it yourself, you can refer to the internal implementation of psutil. It adapts to some operating system.
Currently supported operating systems are:
AIX
FreeBSD, OpenBSD, NetBSD
Linux
macOS
Sun Solaris
Windows
Eg: In Linux platform, you can find one pid 7877 environment variables in file /proc/7877/environ, just open with rt mode to read it.
Of course the best way to do this is to:
import os
from typing import Dict
from psutil import Process
process = Process(pid=os.getpid())
process_env: Dict = process.environ()
print(process_env)
You can find other platform implementation in source code
Hope I can help you.
The Python standard module multiprocessing have a Queues system that allow you to pass pickle-able object to be passed through processes. Also processes can exchange messages (a pickled object) using os.pipe. Remember that resources (e.g : database connection) and handle (e.g : file handles) can't be pickled.
You may find this link interesting :
Communication between processes with multiprocessing
Also the PyMOTw about multiprocessing worth mentioning :
multiprocessing Basics
sorry for my spelling
Two things spring to mind: (1) make the processes share the same environment, by combining them somehow into the same process, or (2) have the first process produce output that contains the relevant environment variables, that way Python can read it and construct the environment for the second process. I think (though I'm not 100% sure) that there isn't any way to get the environment from a subprocess as you're hoping to do.
Environment is inherited from the parent process. Set the environment you need in the main script, not a subprocess (child).

Categories

Resources