我要做的是一个命令行程序进程管理工具,集中展示多个进程的控制台输出内容,并且提供关闭终止进程的操作。
由于是小工具,就不考虑tokio,async-std等大型异步运行时了,用原生的std::process::Child api来处理。由于原生的api都是同步的api,要同时读取常规输出和错误输出,必须在不同的线程去循环,然后通过发送消息到一个统一的线程去处理消息。要把输入输出分发到不同线程,就要把子进程结构体拆散,因为引用不支持发送到别的线程去,除非它有静态生命周期,我想到的方法是用模式匹配来解构Child结构体,这样,子进程被消费掉,变成未初始化状态。一切进行得很顺利,常规输出和错误输出都成功获取到了,但是还有个需求。子进程执行过程中,我还要执行停止操作,要杀进程。但是结构体已经被消费,没办法执行它的kill方法了。通过查看源代码,发现它内部存了个handle句柄,Child结构体的kill方法其实是调用这个内部句柄的kill方法,这个类型并不是标准库公开的类型,是内部使用的类型。我就尝试获取它,走了些弯路,编译器提示我使用了不稳定的特性process_internals,要nightly版本编译器才可用,我之前是都不用nightly版本编译器的,当初找web服务器框架的时候,找到一个Rocket库,发现它需要nightly版本编译器,所以没有用,不过这次例外了,我的需求是做一个很小的命令行工具,不是什么大型系统,就破例用一次,于是切换nightly版本,它又提示我handle是私有变量,得不到,其实我进源码的时候已经看到了,只是编译器还没有提示我,我以为私有变量在结构体被消费的时候能拿到了,结果还是拿不到;此路不通,另寻它途,我又想到了调用windows命令来杀进程,百度一下,windows下有个taskkill命令可以直接根据进程id来杀进程,这样,就脱离了Child结构体了,我只要在它消费之前把子进程id保存起来就行了,执行停止的操作就调用taskkill命令,按理说这条路应该是通的,但是还是报错了,提示的是“句柄无效 OSError[6]”,具体原因不明白,百度一下也没搜到什么可用的信息。我拿taskkill命令在控制台单独执行,一点儿问题都没有,但是在程序里调用就不知道为啥不行,这条路也被堵死了。继续找别的方法,如果不能把Child结构体消费掉,那就只得保留,以保证能够调用到它的kill方法。最后才注意到Child结构体的stdin,stdout,stderr这些成员其实都是Option类型的,而且还是公有变量,记得之前学习Option的时候它有个take方法,可以把里面的内容拿出来,留个None在里面。如果调用这个方法,那么Child结构体并没有被消费,而又拿到了它的输出流。真是上天庇佑,给我留了个后门,事实证明,此路是通的。这就像一个动物,把它杀死了之后再砍掉四肢和直接把它的四肢砍下来还是有区别的,直接砍掉四肢,它还是活着的,它还活着我就可以在以后需要的时候再杀它。这个比喻有点儿血腥,但是合适。或许Child结构体的设计,就是为了我这种场景考虑的,设计之初就考虑到了。Rust的所有权机制对大对象的使用有点儿束缚,总感觉绊手绊脚的,估计这就是安全的代价吧。
补充:
做这个命令行程序进程管理工具已经有一段时间,重新来尝试在程序里调用taskkill命令是可以杀掉的,为什么当时出现“句柄无效 OSError[6]”的错误,现在已经记不清当时的场景了。