Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python subprocess.Popen PIPE and SIGPIPE

While I browsed posts, I ran into this example below on here, It is saying proc1.stdout.close() is needed to be called for appropriate exit of proc1, generating SIGPIPE.

import subprocess

proc1 = subprocess.Popen(['ps', 'cax'], stdout=subprocess.PIPE)
proc2 = subprocess.Popen(['grep', 'python'], stdin=proc1.stdout,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)

proc1.stdout.close() # Allow proc1 to receive a SIGPIPE if proc2 exits.
out, err = proc2.communicate()
print('out: {0}'.format(out))
print('err: {0}'.format(err))

However, I am not clear on that. Please fix my understanding.

  1. SIGPIPE occurs when a PIPE tries to write to closed PIPE.
  2. Writer PIPE is proc1's stdout and reader PIPE is proc2's stdin.
  3. proc1 will exit when proc2 exit and proc1 tries to write data to proc2's stdin PIPE. because
    • proc2's stdin PIPE is closed when proc2 exit
    • SIGPIPE happen at proc1 because proc1 tries to write to closed proc2's stdin PIPE.

From my understanding, SIGPIPE would happen and proc1 would exit, regardless of closing proc1's stdout.

What do I miss?


Edit

After reading the post from @unutbu's comment......

I think the copied file descriptor(proc1.stdout) is writer PIPE, not reader PIPE. thus, there are two writer PIPE and one reader PIPE connected one another.

Therefore, SIGPIPE will be generated when proc2 exit because proc2 is only one process which has reader PIPE(will be closed when proc2 exit).

However, the above post seems to say that there are two reader PIPEs by copying proc1.stdout so SIGPIPE won't be generated even after proc2 exit because there still is another reader PIPE open. the below is the part of post.

So by closing p1.stdout immediately, you ensure that the only remaining filehandle reading from dmesg stdout is the grep process, and if that process were to exit, dmesg receives a SIGPIPE.

I am not saying that the post is wrong but I just want to fix my understanding. Thank you in advance.

like image 999
SangminKim Avatar asked Mar 08 '23 06:03

SangminKim


1 Answers

proc1 = subprocess.Popen(['ps', 'cax'], stdout=subprocess.PIPE)

creates this a pipe between the parent process and proc1:

|        |         |       |
| parent |-<-----<-| proc1 |                   
|        | ^       |       |
           |                     
       p1.stdout   

p1.stdout is what the parent would read to obtain (stdout) output from proc1.

proc2 = subprocess.Popen(['grep', 'python'], stdin=proc1.stdout,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)

connects a copy of the pipe from proc1 to proc2:

|        |         |       |         |       |
| parent |-<-----<-| proc1 |->----->-| proc2 | 
|        |         |       |         |       |

By calling p1.stdout.close(), we close the parent processes's side of the pipe:

|        |         |       |         |       |
| parent |       <-| proc1 |->----->-| proc2 | 
|        |         |       |         |       |

Now when proc2 terminates, its side of the pipe is also closed:

|        |         |       |         |       |
| parent |       <-| proc1 |->       | proc2 | 
|        |         |       |         |       |

The next time proc1 tries to write to the pipe, a SIGPIPE signal is generated, which allows proc1 to terminate since it knows no one is listening on the other end of its pipes.

like image 176
unutbu Avatar answered Mar 16 '23 23:03

unutbu