带有stdin.readline的Rust和Python子流程模块



最小可重复示例

我创建了一个最小的可复制示例,可以在Github上克隆并轻松运行以进行测试:https://github.com/VirxEC/python-no-run

如果你不想去GitHub,那么代码片段将在这篇文章的底部。

问题,尽快描述

当在Windows上从Rust启动Python时,(Linux运行良好,(从stdin读取会阻止Python子进程启动更多的Python子进程。添加一个快速的sleep(1)可以让子流程启动,并且运行良好,但如果子流程启动时间超过1秒,那么我就倒霉了。如果它只需要不到1秒的时间,那么我等待的时间就比必须的要长。所以,使用sleep是我目前实现的快速补丁,但我需要一个合适的解决方案。

问题,详细描述

何时以及为什么

只有当Python仅在Windows上从Rust启动时才会出现此问题,如果您正常运行Python,那么一切都会如预期那样工作。然而,我的应用程序不是Python应用程序,它是一个使用Tauri和Python子组件的Rust GUI,因为有一个大型库,它只是Python,还没有移植到Rust。。。并且由于其大尺寸,可能永远不会被移植。

这也意味着,在生成子流程时,我不能从主Rust流程中获取其他不相关的命令,因为它只是没有读取stdin并查找它们。在我真正的程序中,我开始了一个新的";线程";从线程中读取stdin,因此理论上可以在侧面启动子流程时读取stdin。现在,在生成这些进程时,我决不能让stdin被读取,这只会增加不需要的额外复杂性。

应该发生什么与实际发生什么

问题在于;Hello World"不会因为子流程生成但没有开始执行而被垃圾邮件。我一直在做这件事,我不知道发生了什么。我知道它与stdin有关,但我的程序使用stdin和stdout在主Rust进程和Python进程之间进行通信。

不知怎的,从主线程上的stdin读取会阻碍子进程的启动。我知道这一点,因为如果你用CCD_ 3生成子进程;Hello World"收到垃圾邮件。

此外,如果我在生成子流程后立即添加sleep(1),那么一切都正常!耶!但这与理想相去甚远。它就像一个创可贴,对伤口来说不够大,但至少它覆盖了其中的一部分。我已经在我的程序中临时实现了这个解决方案(特别是sleep(3),用于生成的每个子流程(,但可能需要生成几十个流程,最终耗时太长。此外,一个过程仍然有可能只需要3秒以上的时间就可以开始,如果真的发生了,我会倒霉的。

borked代码(仅限Windows!(

我删除了导入语句等,因为它们不太相关。如果你感兴趣的话,它们仍然可以在GitHub repo中找到,该repo包含了整个最小的可复制示例。

main.rs

// Setup command
let mut command = Command::new("python");
command.args(["-u", "-c", "from main import start; start()"]);
command.current_dir(std::env::current_dir()?.join("python"));
// Pipe stdin so we can issue commands to tell Python what we want it to do
command.stdin(Stdio::piped());
// Spawn the process & take ownership of stdin
let mut child = command.spawn()?;
let mut stdin = child.stdin.take().unwrap();
// Give a second to make 100% sure Python has started
sleep(Duration::from_secs(1));
// Issue start command to Python, wait 5 seconds then tell it to stop
println!("Writing start");
stdin.write_all(b"start | n")?;
sleep(Duration::from_secs(5));
println!("Writing stop");
stdin.write_all(b"stop | n")?;
// Make sure the process has exited before we exit
child.wait()?;

main.py

def start():
procs = []
while True:
print("Listening for new command...")
command = sys.stdin.readline()
params = command.split(" | ")
print(f"Got command: {command}")
if params[0] == "stop":
print("Stopping...")
# Break from loop
break
elif params[0] == "start":
print("Starting...")
# Spawn the subprocess
proc = subprocess.Popen([sys.executable, "say-hi.py"])
# Add the subprocess to the list so we can kill it later
procs.append(proc)
print("Shutting down")
# Terminate processes
for proc in procs:
proc.terminate()

say-hi.py

while True:
print(f"Hello world!", flush=True)
sleep(1)

我的运行过程

这与GitHub repo中README.md中的指令类似。

在根文件夹中

  • 运行cargo r并确保不会发生任何事情
  • python/main.py中,将Popen([sys.executable, "say-hi.py"])更改为Popen([sys.executable, "say-hi.py"], shell=True)
  • 运行CCD_ 14;你好世界"发出停止命令时收到垃圾邮件,并且从未停止

python文件夹中

  • shell可以是TrueFalse,这无关紧要
  • 运行python -c "from main import start; start()"启动进程
  • 准确地输入CCD_ 20;你好世界"收到垃圾邮件
  • 准确地输入CCD_ 21;你好世界"停止垃圾邮件,成功处理所有退出

subprocess.Popen使用默认参数从windows上的操作系统请求stdout和stdin的句柄。。。它似乎由于某种原因而卡住或失败。

无论如何,传递父级的stdout或PIPE似乎可以在windows上修复它,只需替换

proc = subprocess.Popen([sys.executable, "say-hi.py"])

带有

proc = subprocess.Popen([sys.executable, "say-hi.py"], stdin=subprocess.PIPE, stdout=sys.stdout, stderr=sys.stderr)

它只是将父级的stdout文件id传递给子级使用。

最新更新