Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix PermissionError when removing TemporaryDirectory on Windows #13

Closed
mikepqr opened this issue Mar 16, 2021 · 13 comments · Fixed by #20
Closed

Fix PermissionError when removing TemporaryDirectory on Windows #13

mikepqr opened this issue Mar 16, 2021 · 13 comments · Fixed by #20

Comments

@mikepqr
Copy link
Owner

mikepqr commented Mar 16, 2021

I don't know if this only happens when it runs as a Github Action, or if it happens on regular Windows, but this is a pretty scary message for Windows users to see.

The access denied error is due to https://bugs.python.org/issue26660 (fixed in 3.8, but the Github windows environment uses 3.7, and I would rather not require 3.8 for users).

I am not sure why the exception we catch is bubbling up as "Exception ignored".

Could not delete C:\Users\RUNNER~1\AppData\Local\Temp\resume.md_3lhv1lk9
[WinError 5] Access is denied: 'C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\resume.md_3lhv1lk9\\CrashpadMetrics-active.pma'
Exception ignored in: <finalize object at 0x263c82064d0; dead>
Traceback (most recent call last):
  File "C:\hostedtoolcache\windows\Python\3.7.9\x64\lib\weakref.py", line 572, in __call__
    return info.func(*info.args, **(info.kwargs or {}))
  File "C:\hostedtoolcache\windows\Python\3.7.9\x64\lib\tempfile.py", line 797, in _cleanup
    _shutil.rmtree(name)
  File "C:\hostedtoolcache\windows\Python\3.7.9\x64\lib\shutil.py", line 516, in rmtree
    return _rmtree_unsafe(path, onerror)
  File "C:\hostedtoolcache\windows\Python\3.7.9\x64\lib\shutil.py", line 400, in _rmtree_unsafe
    onerror(os.unlink, fullname, sys.exc_info())
  File "C:\hostedtoolcache\windows\Python\3.7.9\x64\lib\shutil.py", line 398, in _rmtree_unsafe
    os.unlink(fullname)
PermissionError: [WinError 5] Access is denied: 'C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\resume.md_3lhv1lk9\\CrashpadMetrics-active.pma'
@amandal1810
Copy link

Can confirm that i am seeing a similar error

@amandal1810
Copy link

I tried using weasyprint instead of headless chrome and pdf generation works fine. Is there any reason why we cannot use weasyprint?
I also tried pdfkit but it has been updated in a while.

@mikepqr
Copy link
Owner Author

mikepqr commented Jul 4, 2021

See #11 and especially #8 for why I switched away from WeasyPrint.

I don't have access to Windows, but this "Error" is actually a harmless warning (i.e. the resume.pdf file is successfully built) on Windows in Github Actions: https://github.com/mikepqr/resume.md/runs/2901233186?check_suite_focus=true.

I believe it can be eliminated by upgrading to a more recent version of Python (e.g. 3.8, 3.9 or 3.10).

Given it is harmless then I'd be glad to take a PR that hides it on 3.7, but I don't want to revert to WeasyPrint just to get rid of it.

But like I said, I don't have access to Windows to test anything, so please let me know if any of the above is wrong (e.g. it's not harmless, it's not fixed by upgrading Python to 3.8+).

@mikepqr
Copy link
Owner Author

mikepqr commented Jul 4, 2021

According to https://bugs.python.org/issue26660, the snippet at https://docs.python.org/3/library/shutil.html?highlight=shutil#rmtree-example might help. I'd probably need to add that here

shutil.rmtree(tmpdir.name)
. I'll get around to this eventually, but I'Il gladly take a PR (especially from someone who can reproduce the error locally).

@amandal1810
Copy link

amandal1810 commented Jul 7, 2021

Thanks for all the details @mikepqr . I'll try to make this change.
Please note that i am on Python 3.9.6 and i am still seeing the error :)

@amandal1810
Copy link

Also, there are problems with WeasyPrint. :)
As already mentioned in an issue here, the most annoying thing about WeasyPrint is probably the installation process. Its quite convoluted. And then the output is also not satisfactory. I was trying to achieve a LaTeX look - the html looks fine but the pdf looks all weird.
So i agree, its probably best to stick with headless chrome.

@amandal1810
Copy link

Do note that, for me, it does not write the resume.pdf file.
This is the output i see:

PS C:\work\code\resume.md> python resume.py
Wrote resume.html
Found Chrome or Chromium at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
Traceback (most recent call last):
  File "C:\work\code\resume.md\resume.py", line 210, in <module>
    write_pdf(html, prefix=prefix, chrome=args.chrome_path)
  File "C:\work\code\resume.md\resume.py", line 139, in write_pdf
    subprocess.run(
  File "C:\Python39\lib\subprocess.py", line 507, in run
    stdout, stderr = process.communicate(input, timeout=timeout)
  File "C:\Python39\lib\subprocess.py", line 1126, in communicate
    self.wait()
  File "C:\Python39\lib\subprocess.py", line 1189, in wait
    return self._wait(timeout=timeout)
  File "C:\Python39\lib\subprocess.py", line 1470, in _wait
    result = _winapi.WaitForSingleObject(self._handle,
KeyboardInterrupt

@amandal1810
Copy link

amandal1810 commented Jul 7, 2021

OK. So i added the --repl flag while calling chrome.exe in subprocess.run() and now we can see the real reason for the failure:
Note that although it says Wrote resume.pdf, the file was not created.

C:\work\code\resume.md [main ≡ +4 ~4 -1 !]> python resume.py
Wrote resume.html
Found Chrome or Chromium at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
>>>
Wrote resume.pdf
Could not delete C:\Users\abmandal\AppData\Local\Temp\resume.md_iv3sprky
[WinError 5] Access is denied: 'C:\\Users\\abmandal\\AppData\\Local\\Temp\\resume.md_iv3sprky\\CrashpadMetrics-active.pma'
Exception ignored in: <finalize object at 0x1e6aa6d0700; dead>
Traceback (most recent call last):
  File "C:\Python39\lib\weakref.py", line 580, in __call__
    return info.func(*info.args, **(info.kwargs or {}))
  File "C:\Python39\lib\tempfile.py", line 816, in _cleanup
    cls._rmtree(name)
  File "C:\Python39\lib\tempfile.py", line 812, in _rmtree
    _shutil.rmtree(name, onerror=onerror)
  File "C:\Python39\lib\shutil.py", line 740, in rmtree
    return _rmtree_unsafe(path, onerror)
  File "C:\Python39\lib\shutil.py", line 618, in _rmtree_unsafe
    onerror(os.unlink, fullname, sys.exc_info())
  File "C:\Python39\lib\tempfile.py", line 804, in onerror
    cls._rmtree(path)
  File "C:\Python39\lib\tempfile.py", line 812, in _rmtree
    _shutil.rmtree(name, onerror=onerror)
  File "C:\Python39\lib\shutil.py", line 740, in rmtree
    return _rmtree_unsafe(path, onerror)
  File "C:\Python39\lib\shutil.py", line 599, in _rmtree_unsafe
    onerror(os.scandir, path, sys.exc_info())
  File "C:\Python39\lib\shutil.py", line 596, in _rmtree_unsafe
    with os.scandir(path) as scandir_it:
NotADirectoryError: [WinError 267] The directory name is invalid: 'C:\\Users\\abmandal\\AppData\\Local\\Temp\\resume.md_iv3sprky\\CrashpadMetrics-active.pma'

@mikepqr
Copy link
Owner Author

mikepqr commented Jul 7, 2021

No idea, I'm afraid. The error should be harmless to our goals (I think it's Chrome's own telemetry/logging tempfile/cleanup failing) but it raises which causes resume.py to receive the exception. You could try catching NotADirectoryError just before here

except subprocess.CalledProcessError as exc:
.

@amandal1810
Copy link

amandal1810 commented Jul 7, 2021

OK. So i re-approached the problem from a different angle, and it works 😀

  • I have gotten rid of the temporary directory thingy altogether.
  • I have created a separate function for writing pdf for win32
  • i cannot pass the entire html in base64 encoded format via the command line. I get a command too long error. So i must first create the html file and then create the pdf file from the html file.
  • As a result, the args are not respected. IMHO, this is okay for now because the tool actually works now for win32 users 🤣
  • So in philosophy, I am simply trying to run the below PowerShell command from Python:
    & 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe' --headless --disable-gpu --run-all-compositor-stages-before-draw --print-to-pdf-no-header --virtual-time-budget=5000 --no-margins --print-to-pdf=C:\work\code\resume.md\resume.pdf C:\work\code\resume.md\resume.html

I will raise a PR tomorrow as its quite late here 😄

@mikepqr
Copy link
Owner Author

mikepqr commented Jul 7, 2021

OK, sounds promising! Also sounds like it might break macOS and Linux, so I will be sure to run rests on the PR 😄

@amandal1810
Copy link

amandal1810 commented Jul 8, 2021

OK, sounds promising! Also sounds like it might break macOS and Linux, so I will be sure to run rests on the PR 😄

I think it will not break for macOS and Linux because there are two separate functions now - one for win32 and another for macOS/Linux. But yes, you must test it, especially because I have not.

mikepqr added a commit that referenced this issue Feb 13, 2022
We were getting errors like `PermissionError: [WinError 5] Access is
denied:
'C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\resume.md_k098zhaj\\CrashpadMetrics-active.pma'`
and `Exception ignored in: <finalize object at 0x16d9e039d60; dead> ...
NotADirectoryError: [WinError 267] The directory name is invalid:
'C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\resume.md_3aoun3f2\\CrashpadMetrics-active.pma'`
in CI on Windows. See e.g.
https://github.com/mikepqr/resume.md/runs/5172290981 and
https://github.com/mikepqr/resume.md/runs/5172234014.

The root cause was that Chrome creates a file the python process does
not have permission to delete. See
puppeteer/puppeteer#2778. Because
TemporaryDirectory is intended to be used as a context manager there is
no way to prevent it logging an error when cleanup fails.

The fix is to switch to the lower level tempfile.mkdtemp, and make a
good faith attempt to clean it up manually, logging failure at the debug
level (while adding a new --debug option).

A more sophisticated fix would be to backport the new
ignore_cleanup_errors option added in python 3.10
(python/cpython#24793), but this will do.

Fixes #13
mikepqr added a commit that referenced this issue Feb 13, 2022
We were getting errors like `PermissionError: [WinError 5] Access is
denied:
'C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\resume.md_k098zhaj\\CrashpadMetrics-active.pma'`
and `Exception ignored in: <finalize object at 0x16d9e039d60; dead> ...
NotADirectoryError: [WinError 267] The directory name is invalid:
'C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\resume.md_3aoun3f2\\CrashpadMetrics-active.pma'`
in CI on Windows. See e.g.
https://github.com/mikepqr/resume.md/runs/5172290981 and
https://github.com/mikepqr/resume.md/runs/5172234014.

The root cause was that Chrome creates a file the python process does
not have permission to delete. See
puppeteer/puppeteer#2778. Because
TemporaryDirectory is intended to be used as a context manager there is
no way to prevent it logging an error when cleanup fails.

The fix is to switch to the lower level tempfile.mkdtemp, and make a
good faith attempt to clean it up manually, logging failure at the debug
level (while adding a new --debug option).

A more sophisticated fix would be to backport the new
ignore_cleanup_errors option added in python 3.10
(python/cpython#24793), but this will do.

Fixes #13
@akaihola
Copy link

akaihola commented Jan 7, 2023

For anyone else having this issue, you can find a recipe for a work-around in:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants