【问题标题】:Python subprocess.check_call(["wine"]..) has a synchronization issuePython subprocess.check_call(["wine"]..) 存在同步问题
【发布时间】:2017-08-13 23:05:57
【问题描述】:

我有一个 python 脚本,它使用 subprocess.check_call 启动 Wine(Linux 上的 Windows 模拟器),然后 wine 启动 Z:\\Program Files (x86)\\PeaZip\\peazip.exe

首先,当我在调试模式python3 -u -m ipdb unpack_archive.py 下测试这个python 脚本,并逐步围绕wine 启动和运行语句设置断点时,Wine 成功运行peazip.exe。即 peazip 在 Linux 上成功解压 PEA 压缩包。

但是,当我在调试模式python3 unpack_archive.py 下测试这个 python 脚本时,我发现 peazip.exe 没有成功提取 PEA 存档。所以我怀疑wine或python subprocess.check_call()中存在同步问题。

现在我的解决方法是,在启动 wine 后插入 time.sleep(1.0)

elif 'PEA archive' in ftype:
    if splitext(arcname)[1] != '.pea':
        tmpfile = os.path.join(tmpdir, basename(arcname))+'.pea'
    else:
        tmpfile = os.path.join(tmpdir, basename(arcname))
    shutil.copy(arcname, tmpfile)
    subprocess.check_call(["wine", "/home/acteam/.wine/drive_c/Program Files (x86)/PeaZip/peazip.exe",
        "-ext2here", to_wine_path(tmpfile)])
    import time
    time.sleep(1.0) # if we don't sleep, then peazip.exe won't extract file successfully 
    os.remove(tmpfile)
    copy_without_symlink(tmpdir, outdir)

我检查了wine manual,它没有提到任何关于同步的内容。我还检查了subprocess.check_call()。该文档明确表示 check_call() 将等待命令完成。

我不想要这个变通方法,因为如果 PEA 存档文件非常大,那么 sleep() 的超时值必须更大,并且在运行它之前我们无法预测足够的超时值。


我参考了@jasonharper的建议。使用 subprocess.check_output() 而不是 check_call()

    elif 'PEA archive' in ftype:
        if splitext(arcname)[1] != '.pea':
            tmpfile = os.path.join(tmpdir, basename(arcname))+'.pea'
        else:
            tmpfile = os.path.join(tmpdir, basename(arcname))
        shutil.copy(arcname, tmpfile)
        subprocess.check_output(["wine", "/home/acteam/.wine/drive_c/Program Files (x86)/PeaZip/peazip.exe",
            "-ext2here", to_wine_path(tmpfile)])
        os.remove(tmpfile)
        copy_without_symlink(splitext(tmpfile)[0], outdir)

我使用 python3 unpack_archive.py Kevin.pea 对其进行了测试,这是一个 2.0GB 的 PEA 存档。提取过程耗时 4 分 16 秒。三个子文件解压成功。

【问题讨论】:

    标签: python python-3.x subprocess race-condition wine


    【解决方案1】:

    我的理解是wine 可执行文件不是真正的模拟器——如果它还没有运行,它只是启动一个名为wineserver 的后台进程,告诉它运行 Windows 程序,然后立即退出——很可能在 Windows 程序开始运行之前。

    this question 的一个答案表明,将wine 的输出通过管道传输到另一个程序会延迟事情,直到 Windows 程序实际退出。在 Python 术语中,这相当于使用 check_output() 而不是 check_call(),尽管我自己没有尝试过。

    【讨论】:

    • 好。我尝试使用wineserver --foreground。我发现peazip.exe完成提取后,wineserver也将终止。我在启动wine 之前启动wineserver --foreground,并等到wineserver 子进程终止。
    【解决方案2】:

    考虑使用咨询锁定来阻塞直到进程退出:

    lockfile=open(tmpfile, 'a')
    subprocess.check_call([
             "wine", "/home/acteam/.wine/drive_c/Program Files (x86)/PeaZip/peazip.exe",
            "-ext2here", to_wine_path(tmpfile)],
        preexec_fn=lambda: fcntl.flock(lockfile, fcntl.LOCK_EX),
        close_fds=False)
    fcntl.flock(lockfile, fcntl.LOCK_EX)
    

    在这里,我们的preexec_fn(在我们将fork()ed 关闭子进程之后但在wine 启动之前运行)获取锁,在check_call() 返回之后,我们然后尝试获取该锁我们自己——如果它还没有发布,它会阻塞。

    (请注意,您需要确保 wine 在程序退出之前不会关闭该文件描述符本身;如果确实如此,避免这种情况的一种方法是在作为 stdin、stdout 传递的描述符上创建锁或标准错误)。

    【讨论】:

    • 我试过你的示例代码,但我遇到了这个异常:` Traceback(最近一次调用最后一次):文件“unpack_archive.py”,第 258 行,在 main() 文件“unpack_archive. py",第 251 行,主要用于 unpack_archive(arcname, outdir) 中的 f,sha1:文件 "unpack_archive.py",第 169 行,在 unpack_archive close_fds=False) 文件 "/usr/lib/python3.4/subprocess.py ",第 556 行,在 check_call retcode = call(*popenargs, **kwargs) subprocess.SubprocessError:preexec_fn 中发生异常。 `
    • 这还不够详细——我需要真正的例外。您可以考虑将 lambda 替换为在引发之前打印堆栈跟踪的真实函数。
    • 我用一个真正的函数修改了 lambda。结果是第二个fcntl.flock(lockfile, fcntl.LOCK_EX) 不等到peazip 执行完成。它仍然无法解决竞争条件。
    • 我不认为在测试期间已经有一个wineserver 在运行? (否则,它必须关闭所有预先打开的 FD 以避免持有锁定......公平地说,这很可能发生)。
    猜你喜欢
    • 2022-09-27
    • 1970-01-01
    • 2019-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-08
    • 2011-07-15
    相关资源
    最近更新 更多