为什么这个Popen调用在Django 2.0中返回“io.UnsupportedOperation:fileno”错误?

时间:2018-01-23 00:13:42

标签: python django gulp subprocess

我最近将我的项目升级到Django 2.0,并且错误开始出现。首先,每当我启动django runserver时,我都会使用django_gulp来启动gulp进程。我正在使用runserver_plus branch of the django_gulp project。这是来自django_gulp项目的the relevant code snippet,它会在subprocess.Popen调用。这个调用在Django 1.11.x中正常运行。

from __future__ import print_function

import atexit
import os
import psutil
import subprocess
import sys
import traceback

from signal import SIGTERM

from concurrent.futures import ThreadPoolExecutor

from django.core.management.base import CommandError
from django.conf import settings

from django_extensions.management.commands.runserver_plus import Command \
    as DjangoExtensionsRunserverCommand

from env_tools import load_env


class Command(DjangoExtensionsRunserverCommand):
    """
    Subclass the RunserverCommand from Staticfiles to set up our gulp
    environment.
    """

    def __init__(self, *args, **kwargs):
        self.cleanup_closing = False
        self.gulp_process = None

        super(Command, self).__init__(*args, **kwargs)

    @staticmethod
    def gulp_exited_cb(future):
        if future.exception():
            print(traceback.format_exc())

            children = psutil.Process().children(recursive=True)

            for child in children:
                print('>>> Killing pid {}'.format(child.pid))

                child.send_signal(SIGTERM)

            print('>>> Exiting')

            # It would be nice to be able to raise a CommandError or use
            # sys.kill here but neither of those stop the runserver instance
            # since we're in a thread. This method is used in django as well.
            os._exit(1)

    def handle(self, *args, **options):
        try:
            env = load_env()
        except IOError:
            env = {}

        # XXX: In Django 1.8 this changes to:
        # if 'PORT' in env and not options.get('addrport'):
        #     options['addrport'] = env['PORT']

        if 'PORT' in env and not args:
            args = (env['PORT'],)

        # We're subclassing runserver, which spawns threads for its
        # autoreloader with RUN_MAIN set to true, we have to check for
        # this to avoid running gulp twice.
        if not os.getenv('RUN_MAIN', False):
            pool = ThreadPoolExecutor(max_workers=1)

            gulp_thread = pool.submit(self.start_gulp)
            gulp_thread.add_done_callback(self.gulp_exited_cb)

        return super(Command, self).handle(*args, **options)

    def kill_gulp_process(self):
        if self.gulp_process.returncode is not None:
            return

        self.cleanup_closing = True
        self.stdout.write('>>> Closing gulp process')

        self.gulp_process.terminate()

    def start_gulp(self):
        self.stdout.write('>>> Starting gulp')

        gulp_command = getattr(settings, 'GULP_DEVELOP_COMMAND', 'gulp')

        self.gulp_process = subprocess.Popen(
            [gulp_command],
            shell=True,
            stdin=subprocess.PIPE,
            stdout=self.stdout,
            stderr=self.stderr)

        if self.gulp_process.poll() is not None:
            raise CommandError('gulp failed to start')

        self.stdout.write('>>> gulp process on pid {0}'
                          .format(self.gulp_process.pid))

        atexit.register(self.kill_gulp_process)

        self.gulp_process.wait()

        if self.gulp_process.returncode != 0 and not self.cleanup_closing:
            raise CommandError('gulp exited unexpectedly')

请注意self.stdoutself.stderr的参数是subprocess.Popen的参数,是对django.core.management.base.OutputWrapper的引用。到目前为止,我所能说的是Django 1.11的OutputWrapper类继承自object,而Django 2.0的OutputWrapper类继承自TextIOBase

这是我得到的错误:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/subprocess.py", line 667, in __init__
    errread, errwrite) = self._get_handles(stdin, stdout, stderr)
  File "/usr/local/lib/python3.6/subprocess.py", line 1184, in _get_handles
    c2pwrite = stdout.fileno()
io.UnsupportedOperation: fileno

如果这是django_gulp问题,我会在该回购中创建一个问题和/或PR来修复此问题。但是,就目前而言,我希望在我自己的项目中实现这一点。

我还应该提一下,我是在一个docker-compose环境中运行它,所以也许是因为它导致了错误。我还没有在非Docker环境中测试过这个。

修改

根据this answer,似乎子流程代码可能假设流具有文件描述符,在这种情况下没有文件描述符。

1 个答案:

答案 0 :(得分:4)

您看到此错误的原因是文件类对象是Python抽象。操作系统和其他进程不了解这种抽象,他们只知道文件描述符。因此,必须将有效的文件描述符传递给Popen。

您可以从OutputWrapper

访问由_out包裹的信息流
self.gulp_process = subprocess.Popen(
    #...
    stdout=self.stdout._out,
    stderr=self.stderr._out)

或者,您可以传递标准文件编号以获得标准输出和错误:

self.gulp_process = subprocess.Popen(
    #...
    stdout=1,
    stderr=2)