Question: I am using asyncio to asynchronously launch a process, and then read data from the stdout, my code looks like the following: process = await asyncio.subprocess.create_subprocess_exe(subprocess_args, stderr=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE)  I am then asynchronously reading from the stdout of this process, and for every line read, I am calling a user-provided callback function, my code looks like: async for line in process.stdout: user_provided_callback(line)  I then wait for the process to terminate: _, stderr = await process.communicate() if process.returncode != 0: raise Exception(stderr.decode())  However, I noticed that is user_provided_callback raises an error, the process is not properly disposed of, so I see warnings such as "Resource warning: unclosed transport", because I am running this code through pytest unit tests, warnings are treated as errors so this is causing a failure. I thought I would have to manually shut down the process in the case of failure, so I changed my code to:  async for line in process.stdout: try: user_provided_callback(line) except Exception as err: process.terminate() raise err  This explicitly terminates the process in case the callback fails, but I am still seeing this warning. I then modified the code above to: async for line in process.stdout: try: user_provided_callback(line) except Exception as err: process.terminate() process._transport.close() ## <- NEWLY ADDED raise err  after explicitly closing the transport, now the warning goes away, however this accesses a private variable so it does not seem like the right fix. What is the proper way to handle an exception in this scenario? I am looking for a cross platform solution as I want to support windows and linux. I am also using python 3.11.

Answer: process.terminate() is a fast, synchronous call that sends a signal that the target process asynchronously handles; thus, the target doesn't shut down immediately when terminate is called, but somewhat later, after the OS next schedules it and invokes its signal handler. This means you still want to await process.wait() or await process.communicate() (which may be better for reasons I'll describe below) after calling process.terminate() to allow cleanup to take place.  In the simplest case, just adding await process.wait() may be sufficient, but there are potential complications:  If the process tries to flush stdout or stderr after getting a SIGTERM, but is unable to do so because nothing in the Python code is still reading from the other side of the pipeline, this may cause a deadlock. To avoid this you'll want to continue to read output even after the terminate() call, either in your own code or by using await process.communicate(). On non-Windows platforms, if the process defines a handler for SIGTERM, it can choose to ignore or delay handling of that signal. If you're willing to accept some risk of data loss (perhaps because files the process modifies aren't fully flushed to disk or stdout isn't fully written), you can wait for a reasonable amount of time and then call process.kill() if the process still hasn't exited; unlike SIGTERM, SIGKILL cannot be ignored. (This should never be necessary on Windows, where terminate() has behavior closer to the UNIX SIGKILL).