将 python 的子进程与 rsync 一起使用时出现“filedescriptor out of range in select()”

问题描述 投票:0回答:3

下面的代码用于将上传的图片同步到另一个地方。它可以工作,但在一段时间(大约10天)后,服务无法使用,显示错误:'filedescriptor out of range in select()',但重新启动服务解决了问题。

# sync.py

def sync_file(source_pic, hashval, retry_num=3):

    pic_path = os.path.join(gen_dir(hashval), os.path.split(source_pic)[1])
    filename = tempfile.mkstemp()[1]
    with open(filename, 'w') as f:
        f.write(pic_path)

    for sync_path in options.sync_paths:
        try_num = 0
        rsync_cmd = ['rsync','-au', '--files-from='+filename, options.pic_path, sync_path]

        while try_num < retry_num:
            proc = subprocess.Popen(rsync_cmd,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            stdout_value, stderr_value = proc.communicate()

            if len(stderr_value) != 0:
                logger.error('sync failed: %s' % stderr_value)
                try_num = try_num + 1
                #raise UploadException('sync failed')
            else:
                break

    os.remove(filename)

日志信息:

File "/path/to/sync.py", line 25, in sync_file
    stdout_value, stderr_value = proc.communicate()
File "/usr/lib/python2.6/subprocess.py", line 691, in communicate
    return self._communicate(input)
File "/usr/lib/python2.6/subprocess.py", line 1211, in _communicate
    rlist, wlist, xlist = select.select(read_set, write_set, [])
    ValueError: filedescriptor out of range in select()

是否有未关闭的文件描述符导致错误?看来子进程没有关闭文件描述符,所以当它运行1024次时,文件描述符超出了范围。 (我们使用的是python 2.6,子进程被迫使用select.select(),即使epoll可用,它也有1024个文件描述符的限制)

python subprocess rsync
3个回答
3
投票

https://bugzilla.redhat.com/show_bug.cgi?id=609020

在 Python 2.7 之前,使用

ulimit -n
来启用的程序 与大量子进程的通信仍然可以监控 一次只有1024个文件描述符,导致异常:

ValueError: filedescriptor out of range in select()

这是由于子进程模块使用了

select
系统调用。 该模块现在使用
poll
系统调用,消除了此限制。

可能的修复:使用 python 2.7+,或使用

poll
向后移植代码。


2
投票

您可以手动关闭文件描述符。拨打

communicate
后,再拨打
proc.stderr.close()
proc.stdout.close()


0
投票

将/anaconda3/envs/genesis/lib/python3.9/site-packages/pyglet/app/xlib.py中的class XlibEventLoopIni(PlatformEventLoop):替换为以下内容,就可以解决pending_devices, _, _ = select。选择(self.select_devices,(),(),超时) ValueError: filedescriptor out of range in select() 的问题:

import selectors

class XlibEventLoop(PlatformEventLoop):
    def __init__(self):
        super(XlibEventLoop, self).__init__()
        self._notification_device = NotificationDevice()
        self.select_devices = set()
        self.select_devices.add(self._notification_device)

        # 初始化 selectors.DefaultSelector
        self.selector = selectors.DefaultSelector()

        # 注册初始设备(如通知设备)到 selector
        for device in self.select_devices:
            self.selector.register(device, selectors.EVENT_READ)

    def notify(self):
        """
        通知事件循环有新事件需要处理(用于唤醒事件循环)。
        """
        self._notification_device.set()

    def step(self, timeout=None):
        """
        执行事件循环的一个步骤,处理所有准备好的事件。

        :param timeout: 超时时间(秒)。None 表示阻塞直到有事件。
        :return: 如果有事件被处理则返回 True,否则返回 False。
        """
        # 预先检查已准备好的设备
        pending_devices = []
        for device in self.select_devices:
            if device.poll():  # 检查设备是否有事件需要处理
                pending_devices.append(device)

        # 如果没有设备准备好,使用 selector 阻塞等待事件
        if not pending_devices:
            # timeout 传递给 selector.select,用于设置等待时间(秒)
            events = self.selector.select(timeout)

            # 将所有触发事件的设备添加到 pending_devices
            for key, _ in events:
                pending_devices.append(key.fileobj)

        # 如果仍然没有准备好的设备,则返回 False(超时或无事件)
        if not pending_devices:
            return False

        # 对每个准备好的设备,调用其 select() 方法处理事件
        for device in pending_devices:
            device.select()

        # 返回 True 表示成功处理了至少一个事件
        return True

    def register_device(self, device):
        """
        向事件循环中注册新的设备。

        :param device: 要注册的设备对象。
        """
        if device not in self.select_devices:
            self.select_devices.add(device)
            self.selector.register(device, selectors.EVENT_READ)

    def unregister_device(self, device):
        """
        从事件循环中移除设备。

        :param device: 要移除的设备对象。
        """
        if device in self.select_devices:
            self.select_devices.remove(device)
            self.selector.unregister(device)
© www.soinside.com 2019 - 2024. All rights reserved.