[issue20866] segfailt with os.popen and SIGPIPE

akira report at bugs.python.org
Sat May 3 08:50:25 CEST 2014


akira added the comment:

I can't reproduce it on Ubuntu 12.04 with Python 2.7.3, 2.7.6, 3.2,
tip -- no segfault.

It prints the expected output on both Python 2 and 3:

  (standard input)
  io-error

"(standard input)" is printed by grep due to --files-with-match option

io-error (Broken pipe) is because grep exits as soon as it sees the
first decimal zero due to `--files-with-match 0` args without waiting
for all input to arrive therefore the subsequent attempts by the
parent python process to write to grep fail with BrokenPipeError.

You could get the same behaviour using "python -c pass" instead of
grep.

If the input is less than an OS pipe buffer (~64K) then fd.write
succeeds because the system call os.write(pipe, input) succeeds
whether the child process reads its input or not. Introducing a delay
before writing to the child process generates the error reliably even
for small input because the delay allows the child process to
exit. Despite being stdio-based Python 2 io behaves the same in this
case.

SIGPIPE is suppressed in python by default therefore the error is
generated instead of dying on SIGPIPE. If the signal is restored:

  import signal
  signal.signal(signal.SIGPIPE, signal.SIG_DFL) # restore SIGPIPE

then the parent process dies on SIGPIPE (if input is larger than the
OS pipe buffer of if the child process is already exited -- the same
as for BrokenPipeError).

The behaviour is the same ("Broken pipe" for large input) if syscalls
are used directly instead of os.popen:

  #!/usr/bin/env python
  from sys import argv, exit
  from os import close, dup2, execlp, fork, pipe, wait, write
  
  n = int(argv[1]) if len(argv) > 1 else 100000000
  n = (n // 2) * 2 # make it even
  assert n > 1
  
  in_, out = pipe()
  if fork() == 0: # child
      close(out)    # close unused write end of the pipe
      dup2(in_, 0)  # redirect stdin to the pipe
      close(in_)
      execlp('/bin/grep', '/bin/grep', '--files-with-match', '0')
  else: # parent
      close(in_) # close unused read end of the pipe
      while n > 1:
          n -= write(out, b'0\n' * (n // 2)) # write input to the child
      close(out) # no more input
      exit(wait()[1]) # wait for the child to exit
  assert 0

If you meant something else; you could write more specific test.

For reference:

os.popen() in Python 2: http://hg.python.org/cpython/file/2.7/Modules/posixmodule.c#l4560
os.popen() in Python 3: http://hg.python.org/cpython/file/3.4/Lib/os.py#l928

----------
nosy: +akira

_______________________________________
Python tracker <report at bugs.python.org>
<http://bugs.python.org/issue20866>
_______________________________________


More information about the Python-bugs-list mailing list