Nogil subprocess for Python with stdout/stderr capturing and stdin writing - without deadlocking!
Project description
Nogil subprocess for Python with stdout/stderr capturing and stdin writing - without deadlocking!
What is it?
cynonblockingsubprocess is a Cython-based wrapper around a C++ class called ShellProcessManager, defined in nonblockingsubprocess.hpp. Its purpose is to start and manage a shell process (e.g., /bin/bash, "C:\\Windows\\System32\\cmd.exe", or any other command-line application), allowing you to:
- Write commands to the shell’s standard input.
- Read from the shell’s standard output and standard error without risking deadlocks.
- Run two background threads to non-blockingly capture
stdoutandstderrin nogil mode (reducing Python GIL contention). - Optionally print
stdoutand/orstderrin real time while still retaining the captured output.
Features
- Non-blocking I/O: Spawns separate threads to read
stdoutandstderrin the background, preventing the parent process from freezing or deadlocking. - Configurable Buffers: Control the maximum length of captured
stdoutandstderrbuffers before data is truncated, similar to a ring buffer (orcollections.deque). - OS Compatibility:
- On Windows, you can customize process creation flags and environment parameters.
- On Linux/Unix, the Windows-specific parameters are ignored, but the shell process is still managed the same way.
- Graceful Shutdown: Call
stop_shell()to terminate the subprocess and background threads safely. When using subprocess.Popen, shells might keep open in the background, even after calling .kill() or .terminate(). This should not happen here! The C++ destructor takes care of closing the shell.
Installation and Requirements
pip install cynonblockingsubprocess
- Cython (to compile the
.pyx/.pxdfile). - A C++ compiler (minimum version 11) compatible with your platform (e.g.,
MSVCon Windows org++/clang++on Linux). - The code will compile the first time you import it
Usage example Python
from cynonblockingsubprocess import CySubProc
from time import sleep
from platform import platform
iswindows = "win" in platform().lower()
# shell_command (bytes or str): Path or command to start (e.g., "C:\\Windows\\System32\\cmd.exe" or "/bin/bash").
# buffer_size (int): Size of the internal buffer for reading output (default 4096).
# stdout_max_len (int): Maximum amount of data retained in the stdout buffer (default 4096).
# stderr_max_len (int): Maximum amount of data retained in the stderr buffer (default 4096).
# exit_command (bytes or str): Command used internally to gracefully stop the shell (default b"exit").
# print_stdout (bool): If True, prints all stdout in real time to the console (default False).
# print_stderr (bool): If True, prints all stderr in real time to the console (default False).
tete = CySubProc(
shell_command="C:\\Windows\\System32\\cmd.exe" if iswindows else "/bin/bash", # cross plattform
buffer_size=4096,
stdout_max_len=4096,
stderr_max_len=4096,
exit_command=b"exit",
print_stdout=False,
print_stderr=False,
)
# Start the shell process with specific creation flags and environment parameters.
# On Posix, all arguments are ignored!
# Detailed information can be found on Microsoft's website https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
# Parameters
# ----------
# creationFlag : int, optional
# A custom flag for process creation, by default 0.
# creationFlags : int, optional
# Additional flags controlling process creation, by default 0x08000000.
# wShowWindow : int, optional
# Flags controlling how the window is shown (e.g., hidden, normal, minimized),
# by default 1 (SW_SHOWNORMAL).
# lpReserved : bytes or str, optional
# Reserved parameter for process creation, by default None.
# lpDesktop : bytes or str, optional
# The name of the desktop for the process, by default None.
# lpTitle : bytes or str, optional
# The title for the new console window, by default None.
# dwX : int, optional
# X-coordinate for the upper-left corner of the window, by default 0.
# dwY : int, optional
# Y-coordinate for the upper-left corner of the window, by default 0.
# dwXSize : int, optional
# Width of the window, by default 0.
# dwYSize : int, optional
# Height of the window, by default 0.
# dwXCountChars : int, optional
# Screen buffer width in character columns, by default 0.
# dwYCountChars : int, optional
# Screen buffer height in character rows, by default 0.
# dwFillAttribute : int, optional
# Initial text and background colors if used in a console, by default 0.
# dwFlags : int, optional
# Flags that control how the creationFlags are used, by default 0.
# cbReserved2 : int, optional
# Reserved for C runtime initialization, by default 0.
# lpReserved2 : bytes or str, optional
# Reserved for C runtime initialization, by default None.
tete.start_shell() # This function must be always called first, if not, you probably will get segmentation faults!
# Write a command or input data to the shell process's stdin.
# Parameters
# ----------
# cmd : bytes or str
# The command or data to send to the process via stdin.
tete.stdin_write("ls -l") # Writing an existing command
sleep(1) # Wait a little for the output
# Retrieve the current contents of the shell's standard output as bytes, and clears the C++ vector
# Returns
# -------
# bytes
# The raw bytes from the shell's stdout.
print(tete.get_stdout().decode()) # there will be something here
# Retrieve the current contents of the shell's standard error as bytes, and clears the C++ vector.
# Returns
# -------
# bytes
# The raw bytes from the shell's stderr.
print(tete.get_stderr().decode()) # will be empty
tete.stdin_write("lxs xx-lxxxx") # command does not exist
sleep(1)
print(tete.get_stdout()) # will be empty
print(tete.get_stderr()) # there will be something here
del tete # closes the shell automatically! If you want, you can also call proc.stop_shell()
Usage example C++ (stack)
#include "nonblockingsubprocess.hpp"
int main(int argc, char *argv[])
{
while (true)
{
#ifdef _WIN32
std::string shellcmd = "C:\\Windows\\System32\\cmd.exe";
#else
std::string shellcmd = "/bin/bash";
#endif
// arguments: std::string shell_command, size_t buffer_size = 4096, size_t stdout_max_len = 4096, size_t stderr_max_len = 4096, std::string exit_command = "exit", int print_stdout = 1, int print_stderr = 1
ShellProcessManager proc{shellcmd, 4096, 4096, 4096, "exit", 1, 1};
bool resultproc = proc.start_shell(); //optional arguments for Windows: DWORD creationFlag = 0, DWORD creationFlags = CREATE_NO_WINDOW, WORD wShowWindow = SW_NORMAL, LPSTR lpReserved = nullptr, LPSTR lpDesktop = nullptr, LPSTR lpTitle = nullptr, DWORD dwX = 0, DWORD dwY = 0, DWORD dwXSize = 0, DWORD dwYSize = 0, DWORD dwXCountChars = 0, DWORD dwYCountChars = 0, DWORD dwFillAttribute = 0, DWORD dwFlags = 0, WORD cbReserved2 = 0, LPBYTE lpReserved2 = nullptr
std::cout << "resultproc: " << resultproc << std::endl;
proc.stdin_write("ls -l");
sleepcp(100);
auto val = proc.get_stdout();
std::cout << "stdout: " << val << std::endl;
sleepcp(100);
proc.stdin_write("ls -l");
sleepcp(100);
auto val2 = proc.get_stdout();
std::cout << "stderr: " << val << std::endl;
proc.stop_shell(); // optional: automatically called by the destructor
sleepcp(1000);
}
}
Usage example C++ (heap)
#include "nonblockingsubprocess.hpp"
int main(int argc, char *argv[])
{
while (true)
{
#ifdef _WIN32
std::string shellcmd = "C:\\Windows\\System32\\cmd.exe";
#else
std::string shellcmd = "/bin/bash";
#endif
ShellProcessManager *proc = new ShellProcessManager{shellcmd, 4096, 4096, 4096, "exit", 1, 1};
bool resultproc = proc->start_shell();
std::cout << "resultproc: " << resultproc << std::endl;
proc->stdin_write("ls -l");
sleepcp(100);
auto val = proc->get_stdout();
std::cout << "v1111111111: " << val << std::endl;
sleepcp(100);
proc->stdin_write("ls -l");
sleepcp(100);
auto val2 = proc->get_stdout();
std::cout << "v2222222222: " << val << std::endl;
proc->stop_shell();
sleepcp(1000);
}
delete proc
std::cin.get();
}
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cynonblockingsubprocess-0.10.tar.gz.
File metadata
- Download URL: cynonblockingsubprocess-0.10.tar.gz
- Upload date:
- Size: 17.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b0d3f6dc7729f540cbc896c365d9c17d888bb775de7f26dd76870c7ec2e61a3
|
|
| MD5 |
17317a0108f516eaa5fac80c9636a6bc
|
|
| BLAKE2b-256 |
6886b003e8255906aebdcc8ce2026e2bad36fb32585eb0df3c9527253f84bfa6
|
File details
Details for the file cynonblockingsubprocess-0.10-py3-none-any.whl.
File metadata
- Download URL: cynonblockingsubprocess-0.10-py3-none-any.whl
- Upload date:
- Size: 19.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ed267a7743b2e6018b3ade013bc147ac3e13b184eca0e1c621d2596f7e835cbe
|
|
| MD5 |
1862acf2ef1c8cb418bf768ee7ddadea
|
|
| BLAKE2b-256 |
588233a817d465fa1eaccf2af6af2eaaf8db39e810858cff0e6ab1d63221fdc9
|