2

I have a python script which uses subprocess.check_call to launch Wine (Windows Emulator on Linux), then the wine launches Z:\\Program Files (x86)\\PeaZip\\peazip.exe.

Firstly, when I tested this python script in debugging mode python3 -u -m ipdb unpack_archive.py, and set breakpoint around wine launch and run statements step by step, the Wine runs peazip.exe successfully. That is, peazip successfully extract the PEA archive on Linux.

However, when I tested this python script not in debugging mode python3 unpack_archive.py, then I find peazip.exe doesn't extract PEA archive successfully. So I suspect there is a synchronization problem in wine or python subprocess.check_call().

Now my workaround is, inserting time.sleep(1.0) after launching wine :

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)

I checked the wine manual, it doesn't mention anything about synchronization. I also checked subprocess.check_call(). The document explicitly says the check_call() will wait for the command completed.

I don't want this workaround, because if the PEA archive file is very large, then the timeout value for sleep() must be larger, and we can't predict the sufficient timeout value before running it.


I referred to @jasonharper's suggestion. Use subprocess.check_output() instead of 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)

I tested it with python3 unpack_archive.py Kevin.pea, which is a 2.0GB PEA archive. The extraction process costs 4 minutes 16 seconds. Three subfiles are unpacked successfully.

MikimotoH
  • 393
  • 1
  • 4
  • 16

2 Answers2

1

My understanding is that the wine executable is not the actual emulator - it just launches a background process called wineserver if it's not already running, tells it to run the Windows program, and then immediately exits itself - quite possibly before the Windows program has even started running.

One of the answers to this question suggests that piping the output of wine to another program will delay things until the Windows program actually exits. In Python terms, this would be equivalent to using check_output() instead of check_call(), although I haven't tried this myself.

jasonharper
  • 9,450
  • 2
  • 18
  • 42
  • Good. I tried to use `wineserver --foreground`. I found that after `peazip.exe` completed extraction, the `wineserver` will also terminate. I launch `wineserver --foreground` before launch `wine`, and wait until `wineserver` subprocess termination. – MikimotoH Aug 11 '17 at 16:33
1

Consider using advisory locking to block until the process has exited:

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)

Here, our preexec_fn (run after we've fork()ed off the subprocess but before wine has been started) grabs a lock, and after check_call() has returned, we then try to grab that lock ourselves -- which will block if it's not yet released.

(Note that you'll need to be sure that wine doesn't close that file descriptor itself prior to program exit; if it does, one way to avoid that is to create the lock on a descriptor passed as stdin, stdout or stderr).

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • I tried your sample code, but I encounter this exception: ` Traceback (most recent call last): File "unpack_archive.py", line 258, in main() File "unpack_archive.py", line 251, in main for f,sha1 in unpack_archive(arcname, outdir): File "unpack_archive.py", line 169, in unpack_archive close_fds=False) File "/usr/lib/python3.4/subprocess.py", line 556, in check_call retcode = call(*popenargs, **kwargs) subprocess.SubprocessError: Exception occurred in preexec_fn. ` – MikimotoH Aug 13 '17 at 15:20
  • That's not enough detail -- I need the actual exception. You might consider replacing the lambda with a real function that prints a stack trace before raising. – Charles Duffy Aug 13 '17 at 17:35
  • I modified lambda with a real function. The result is the second `fcntl.flock(lockfile, fcntl.LOCK_EX)` doesn't wait until peazip execution completion. It still can't resolve race condition. – MikimotoH Aug 14 '17 at 02:31
  • I don't suppose there was already a `wineserver` running during the test? (Otherwise, it would have had to close all preopened FDs to avoid holding the lock... which, to be fair, is quite possibly happening). – Charles Duffy Aug 22 '17 at 22:29