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

[WinError 5] Access is denied during cleanup/rmtree #7280

Closed
choyrim opened this issue Nov 1, 2019 · 46 comments · Fixed by #10560
Closed

[WinError 5] Access is denied during cleanup/rmtree #7280

choyrim opened this issue Nov 1, 2019 · 46 comments · Fixed by #10560
Labels
kind: crash For situations where pip crashes OS: windows Windows specific state: needs discussion This needs some more discussion

Comments

@choyrim
Copy link

choyrim commented Nov 1, 2019

Environment

  • pip version: 19.2.3+
  • Python version: python 3.7
  • OS: Windows 10
  • AntiVirus: CylancePROTECT

Description
pip install or download fails on Windows during cleanup because (real-time) virus protection is still scanning files that pip is trying to remove.

using Process Monitor (procmon), I was able to confirm that CylanceSvc.exe (virus scanner) was reading the files while python was trying to the same files.

I tried both increasing the retry wait (in misc.py) and ignoring the rmtree error (in temp_dir.py). both approaches worked.

This issue indicates that other anti-virus scanners cause similar problems:
aws/aws-cli#2654

Expected behavior
The package should install.

How to Reproduce
pip install or download a large package. I was usually able to reproduce when installing or downloading awscli. If I have difficulty reproducing, I can lower the priority of the Cylance.exe to Background (4), and the symptoms are easier to reproduce consistently.

using install:
pip install awscli

using download:
pip download awscli

pip download is better because it provides a stacktrace.

Output

using pip install, the error message is quite terse.

>pip install awscli
Collecting awscli
  Using cached https://files.pythonhosted.org/packages/f2/f5/d682aa32100edc6908a784630aa2903ef7d2735130e6df98a05af6c33096/awscli-1.16.271-py2.py3-none-any.whl
ERROR: Could not install packages due to an EnvironmentError: [WinError 5] Access is denied: 'c:\\tmp\\pip\\pip-unpack-5e4a1ye3\\awscli-1.16.271-py2.py3-none-any.whl'
Consider using the `--user` option or check the permissions.
. . .

using pip download, the stacktrace is more helpful.

>pip download awscli
Collecting awscli
  Using cached https://files.pythonhosted.org/packages/f2/f5/d682aa32100edc6908a784630aa2903ef7d2735130e6df98a05af6c33096/awscli-1.16.271-py2.py3-none-any.whl
  Saved c:\users\rich9002\tmp\awscli-1.16.271-py2.py3-none-any.whl
ERROR: Exception:
Traceback (most recent call last):
  File "c:\sw\python37\lib\shutil.py", line 398, in _rmtree_unsafe
    os.unlink(fullname)
PermissionError: [WinError 5] Access is denied: 'c:\\tmp\\pip\\pip-unpack-4gdrskd3\\awscli-1.16.271-py2.py3-none-any.whl'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:\sw\python37\lib\site-packages\pip\_internal\cli\base_command.py", line 188, in main
    status = self.run(options, args)
  File "c:\sw\python37\lib\site-packages\pip\_internal\commands\download.py", line 156, in run
    resolver.resolve(requirement_set)
  File "c:\sw\python37\lib\site-packages\pip\_internal\legacy_resolve.py", line 196, in resolve
    self._resolve_one(requirement_set, req)
  File "c:\sw\python37\lib\site-packages\pip\_internal\legacy_resolve.py", line 359, in _resolve_one
    abstract_dist = self._get_abstract_dist_for(req_to_install)
  File "c:\sw\python37\lib\site-packages\pip\_internal\legacy_resolve.py", line 307, in _get_abstract_dist_for
    self.require_hashes
  File "c:\sw\python37\lib\site-packages\pip\_internal\operations\prepare.py", line 199, in prepare_linked_requirement
    progress_bar=self.progress_bar
  File "c:\sw\python37\lib\site-packages\pip\_internal\download.py", line 1064, in unpack_url
    progress_bar=progress_bar
  File "c:\sw\python37\lib\site-packages\pip\_internal\download.py", line 935, in unpack_http_url
    os.unlink(from_path)
  File "c:\sw\python37\lib\site-packages\pip\_internal\utils\temp_dir.py", line 60, in __exit__
    self.cleanup()
  File "c:\sw\python37\lib\site-packages\pip\_internal\utils\temp_dir.py", line 83, in cleanup
    rmtree(self.path)
  File "c:\sw\python37\lib\site-packages\pip\_vendor\retrying.py", line 49, in wrapped_f
    return Retrying(*dargs, **dkw).call(f, *args, **kw)
  File "c:\sw\python37\lib\site-packages\pip\_vendor\retrying.py", line 212, in call
    raise attempt.get()
  File "c:\sw\python37\lib\site-packages\pip\_vendor\retrying.py", line 247, in get
    six.reraise(self.value[0], self.value[1], self.value[2])
  File "c:\sw\python37\lib\site-packages\pip\_vendor\six.py", line 693, in reraise
    raise value
  File "c:\sw\python37\lib\site-packages\pip\_vendor\retrying.py", line 200, in call
    attempt = Attempt(fn(*args, **kwargs), attempt_number, False)
  File "c:\sw\python37\lib\site-packages\pip\_internal\utils\misc.py", line 166, in rmtree
    onerror=rmtree_errorhandler)
  File "c:\sw\python37\lib\shutil.py", line 516, in rmtree
    return _rmtree_unsafe(path, onerror)
  File "c:\sw\python37\lib\shutil.py", line 400, in _rmtree_unsafe
    onerror(os.unlink, fullname, sys.exc_info())
  File "c:\sw\python37\lib\site-packages\pip\_internal\utils\misc.py", line 176, in rmtree_errorhandler
    os.chmod(path, stat.S_IWRITE)
PermissionError: [WinError 5] Access is denied: 'c:\\tmp\\pip\\pip-unpack-4gdrskd3\\awscli-1.16.271-py2.py3-none-any.whl'
@triage-new-issues triage-new-issues bot added the S: needs triage Issues/PRs that need to be triaged label Nov 1, 2019
@pfmoore
Copy link
Member

pfmoore commented Nov 1, 2019

I view this as an issue with the virus scanner. If it's not able to scan the file quickly enough to avoid issues with programs using temporary files, it should open those files in a mode that does not prevent deletion. If it's not possible to get the virus scanner fixed, you could exclude pip's temporary directory from virus scanning.

Increasing the timeouts on deletions would cause problems for cases where files genuinely couldn't be deleted, resulting in longer delays for the user before a useful error was produced.

@choyrim
Copy link
Author

choyrim commented Nov 1, 2019

@pfmoore
Well then you might agree that the user experience is confusing and cryptic. The directory in the error is usually deleted by the time the user checks. The error message is not particularly helpful. based on the messages, it looks like the download succeeded.

What is actually happening is this: the download succeeded. but pip died because it could not delete the temporary directory it was using. IMHO the install should not fail because the cleanup handler for the download failed.

That said, I can see your point about longer delays for useful errors on pip._internal.utils.misc.rmtree. so let's keep the retry timeout on that function as is. Instead we can modify the pip._internal.utils.TempDirectory.__exit__ handler to warn instead of fatally exiting on cleanup failure. I'm not sure it ever makes sense for temp directory cleanup failure to cause the tempdir client to fail.

to address your suggestion that users reconfigure their virus scanner - in many workplaces, the user does not have control over virus scanner configuration. so it's not possible to exclude any directory. besides pip uses/creates a randomly named directory in the windows user's %TEMP% directory. so you would have to configure the virus scanner to exclude the user's entire %TEMP% directory - which would nullify much of the effectiveness of real-time virus scanning in the first place.

@pfmoore
Copy link
Member

pfmoore commented Nov 1, 2019

IMHO the install should not fail because the cleanup handler for the download failed.

In principle, that sounds reasonable.

in many workplaces, the user does not have control over virus scanner configuration. so it's not possible to exclude any directory.

I work in precisely such an environment, and Python is highly non-core to our business, so requesting changes for the benefit of Python is unlikely to get any sympathy. So I understand your point here, but I still strongly believe that pip shouldn't be catering for the quirks and failures of (IMO badly written or badly configured) virus scanners. General principles like "we shouldn't fail just because cleanup errored" are OK, but that's different.

@chrahunt
Copy link
Member

chrahunt commented Nov 1, 2019

Typically when I execute a command, I expect the behavior to be the same across multiple invocations. In the event that the behavior is not the same I would want that indicated. A warning may be good enough when executed from the command line by a user, but we have to consider that pip's CLI is it's only public API. Add on to this that we make hardly any guarantees about output format, then the exit code is essentially the only way we have to communicate that something unexpected happened and someone should take a look. A command being unable to clean up after itself, especially if the files may be large, is something unexpected that I'd want to know about if running in an automated way.

@chrahunt chrahunt added the state: needs discussion This needs some more discussion label Nov 1, 2019
@triage-new-issues triage-new-issues bot removed the S: needs triage Issues/PRs that need to be triaged label Nov 1, 2019
@choyrim
Copy link
Author

choyrim commented Nov 2, 2019

@chrahunt so then to ensure backward compatibility perhaps we need to add CLI options so that users can opt-in to dealing with system software that scans downloaded files. So I'll propose the following CLI options:

  • --allow-tempdir-cleanup-failure (bool) - this would allow installation to proceed despite the inability to remove the temp directory.
  • --rmtree-stop-max-delay (int, default 3000 msec) - parameter overriding the retry on directory removal (rmtree) - this would allow the rmtree function to wait longer (retry more often) before failing and allowing the virus scanner to let go of the file handle.

either of them will work. second one is probably nicer. but more code i think.

@chrahunt
Copy link
Member

chrahunt commented Nov 2, 2019

I'm generally weakly against adding more options given the number and disorganization of the ones we have (see #6221). That does bring up a good question though, which is why don't we see these kinds of options in other apps? How do they handle it? Maybe if we find good examples of common applications that have made these kinds of decisions, they'll have different approaches or justification than what we're considering here. Conda could be one example.

@choyrim
Copy link
Author

choyrim commented Nov 2, 2019

In the most recent code base, the section of code that fails can be found here:

with TempDirectory(kind="unpack") as temp_dir:
# If a download dir is specified, is the file already downloaded there?
already_downloaded_path = None
if download_dir:
already_downloaded_path = _check_download_dir(link,
download_dir,
hashes)
if already_downloaded_path:
from_path = already_downloaded_path
content_type = mimetypes.guess_type(from_path)[0]
else:
# let's download to a tmp dir
from_path, content_type = _download_http_url(link,
session,
temp_dir.path,
hashes,
progress_bar)
# unpack the archive to the build dir location. even when only
# downloading archives, they have to be unpacked to parse dependencies
unpack_file(from_path, location, content_type)
# a download dir is specified; let's copy the archive there
if download_dir and not already_downloaded_path:
_copy_file(from_path, download_dir, link)
if not already_downloaded_path:
os.unlink(from_path)

It fails on the __exit__ handler for TempDirectory. but in theory virus scanning could cause problems any time pip tries to delete a directory with files downloaded from the internet. If all downloads go thru this unpack_http_url then it might be a good place to attack this.

@pfmoore
Copy link
Member

pfmoore commented Nov 2, 2019

why don't we see these kinds of options in other apps

In my experience, it's because this type of error simply isn't that common, and people typically accept that when anti-virus programs do this type of thing, it's down to the antivirus.

I'm generally weakly against adding more options

I'm definitely against proliferating options for edge cases like this. We should be making the decision, not avoiding the issue and making the user choose. In this case, I personally think the current behaviour is fine, but if the consensus is to change, then OK, as long as we don't dump the choice on the user.

@choyrim
Copy link
Author

choyrim commented Nov 3, 2019

@chrahunt what conda is doing is rather elaborate and sophisticated - essentially doing whatever it takes to remove the directory structure as fast as possible, with special optimizations for windows. it even does an exponential backoff to optimize the waiting between retries. in the worst case (for windows only), they will rename the directory with a special batch script.

I would prefer to keep this change minimal and just set stop_max_delay=15000 (5x longer) on misc.rmtree:

@retry(stop_max_delay=3000, wait_fixed=500)
def rmtree(dir, ignore_errors=False):

Hacking this file is one of the approaches I've been recommending for folks who have this problem.

The retry decorator looks like it supports all sorts of functionality including exponential backoff:

stop=None, wait=None,
stop_max_attempt_number=None,
stop_max_delay=None,
wait_fixed=None,
wait_random_min=None, wait_random_max=None,
wait_incrementing_start=None, wait_incrementing_increment=None,
wait_exponential_multiplier=None, wait_exponential_max=None,
retry_on_exception=None,
retry_on_result=None,
wrap_exception=False,
stop_func=None,
wait_func=None,
wait_jitter_max=None):

but I haven't looked into it yet.

@choyrim
Copy link
Author

choyrim commented Nov 4, 2019

Well I was able to try out the exponential backoff by modifying the decoration like so

@retry(stop_max_delay=XXXX, wait_exponential_multiplier=100, wait_exponential_max=2000)
def rmtree(dir, ignore_errors=False):

Not sure this buys us anything except slightly faster retry on transient delete problems.

How do you want to proceed? Shall I submit a PR with basic changes to the retry decorator?

@chrahunt
Copy link
Member

chrahunt commented Nov 5, 2019

IMO the exponential backoff isn't critical, I was hoping there was some fool-proof solution with no downsides that would just need to be implemented. :)

At this point we just need to haggle over the max delay. You suggested 15s above, did that number come from somewhere specific? Given that not many people are complaining about this, the increase should probably be pretty modest.

@choyrim
Copy link
Author

choyrim commented Nov 5, 2019

15 seconds was an attempt at balancing what I'd prefer (60 sec) and what @pfmoore wants (leave as-is at 3 sec). Based on some testing, 12 seconds is probably fine too.

I just did an unscientific experiment to measure the delay between python attempting to delete the file and the virus scanner cylance releasing the file. I did this measurement 10 times on my machine. In seconds, these were the timings: 2, 16, 8, 18, 48, 11, 12, 13, 15, 17. If I did my math correctly, the 25th, 50th, 75th, and 95th percentile are 11.25, 14, 16.75, and 34.5 respectively.

The testing methodology is as follows:

  • edit the misc.py retry decorator stop_max_delay = 1000
  • set priority of virus scanner Cylance to "background process (4)"
  • in my command prompt, set TEMP=c:\tmp\pip
  • with process monitor (procmon)
    • add filter to include "ends with awscli-1.16.273-py2.py3-none-any.whl" (latest awscli whl)
    • add filter to exclude "excludes c:\tmp\pip"
    • start capture
  • in my command prompt,
    • del *.whl
    • pip download awscli
  • when the error occurs, go to process monitor
    • Hit the [End] key to scroll to the bottom of the filtered capture
    • Keep doing that until the last line shows Cylance doing a CloseFile on the whl file.
  • Record the time that Cylance does the CloseFile
  • Hit the [Home] key to scroll to the top of the filtered capture
  • Ctrl-F to search for "delete pending". This will find the time when python starts waiting for the delete to complete.
  • Record the time that python gets a DELETE PENDING status.
  • Subtract the times and record.

By both reducing the max delay and the lowering virus scanner priority, I can reproduce the problem consistently.

Ideally, the test would download the largest file hosted at files.pythonhosted.org on a slower windows machine and/or network. Slowing down the virus scanner is not really a fair test because it inflates the difference between pip and the virus scanner. But it could be considered a simulation of a worst case scenario.

As a side note, for a few of the test runs I measured the time between when python started the download and when the virus scanner started reading the file. This delay ranged from 1 to 16 sec but mostly under 10 sec.

I would argue that there have been complaints about this bug for a long time and occasional mumblings about this over the internet. For example, not sure what happened to this issue #2892
But the root cause is a race condition. So occasionally the install works and then the user forgets about it. The size of the download, the speed of the virus scanner, the alignment of the moons of jupiter, all come into play and occasionally it works. For the longest time I worked around this issue by downloading a very old version of awscli.

@choyrim
Copy link
Author

choyrim commented Nov 15, 2019

@chrahunt @pfmoore drafted a PR for this issue ( #7361 ) since the discussion went kinda dead. let me know what you think.

@danizen
Copy link

danizen commented Dec 30, 2019

I've written a utility that can create similar problems even without Cylance Protect, and can make the failure more consistent. This is https://github.com/danizen/watchandlock.

I can run it with my own package, confsecrets, which is quite small.
In one command-prompt in a new virtual environment:

pip install requirements.txt
python watchandlock.py --delay 10.0 %TEMP%

In another command-prompt in the same virtual environment

pip install confsecrets

TL;DR - I'm not getting exactly the same error, possibly because I'm opening the file and not the directory. I've also confirmed using procmon that Cylance Protect is simply doing the following:

  1. Open the file (https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea)

     Operation:        CreateFile
     Desired Access:   Generic Read
     Disposition:      Open
     Options:          Synchronous IO Non-Alert, Non-Directory File, Open No Recall
     Attributes:       n/a
     ShareMode:        Read, Write, Delete
     AllocationSize:   n/a
     OpenResult:       Opened
    
  2. Read the file (4 Kb at a time) (https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile)

     Operation:        ReadFile
     Duration:         0.0000083
     Offset:           0
     Length:           4,096
     Priority:         Normal
    

@danizen
Copy link

danizen commented Dec 30, 2019

I wonder whether the answer is simply to ignore the error on Windows only, and attempt to delete a matching directory before doing and installation of de-installation:

   shutil.rmtree(..., ignore_errors=platform.platform().startswith('Windows'))

Or something like the above.

@danizen
Copy link

danizen commented Dec 30, 2019

I have several questions:

  • Does pip's CI/CD process include Windows OS hosts? I do not see that in tox.ini, but that proves very little. I see that @choyrim's test cases will be skipped outside of windows. I can run tests/unit/test_utils.py, but if I try to run all of the tests on Windows I fail fairly badly.
  • CPython 2.7 is end of life - do we need to include a fix for CPython 2.7 on Windows?
  • If winerror 32 is included, an integration test could be added. Is this worth doing?

@danizen
Copy link

danizen commented Dec 30, 2019

Also, after extending @choyrim's pull request to add a test and code to work-around WinError 32 as well as 5, I see that the problem moves (in the real world) to another location:

ERROR: Could not install packages due to an EnvironmentError.
Consider using the `--user` option or check the permissions.
Traceback (most recent call last):
  File "C:\Users\davisda4\workspace\pip7280\src\pip\_internal\commands\install.py", line 381, in run
    resolver.resolve(requirement_set)
  File "C:\Users\davisda4\workspace\pip7280\src\pip\_internal\legacy_resolve.py", line 202, in resolve
    self._resolve_one(requirement_set, req, require_hashes)
  File "C:\Users\davisda4\workspace\pip7280\src\pip\_internal\legacy_resolve.py", line 364, in _resolve_one
    req_to_install, require_hashes
  File "C:\Users\davisda4\workspace\pip7280\src\pip\_internal\legacy_resolve.py", line 310, in _get_abstract_dist_for
    req, self.session, self.finder, require_hashes
  File "C:\Users\davisda4\workspace\pip7280\src\pip\_internal\operations\prepare.py", line 688, in prepare_linked_requirement
    progress_bar=self.progress_bar
  File "C:\Users\davisda4\workspace\pip7280\src\pip\_internal\operations\prepare.py", line 431, in unpack_url
    progress_bar=progress_bar
  File "C:\Users\davisda4\workspace\pip7280\src\pip\_internal\operations\prepare.py", line 293, in unpack_http_url
    os.unlink(from_path)
PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'C:\\Users\\davisda4\\AppData\\Local\\Temp\\pip-unpack-48o536hw\\confsecrets-0.0.2-py3-none-any.whl'
Cleaning up...

@danizen
Copy link

danizen commented Dec 30, 2019

Does pip's CI/CD process include Windows OS hosts? I do not see that in tox.ini, but that proves very little. I see that @choyrim's test cases will be skipped outside of windows. I can run tests/unit/test_utils.py, but if I try to run all of the tests on Windows I fail fairly badly.

Please ignore these questions... I see now the .appveyor.yml file.

@danizen
Copy link

danizen commented Dec 30, 2019

The more I think about this, the more I like simply ignoring the error when it happens if we are on Windows. There are a couple of reasons to like this:

  • We know the directory is a temporary directory, and the whl is a temporary file
  • If a user is in an environment where a Cylance Protect is active, they will also have
    desktop support staff to help them clean-up their TEMP directory.
  • It is potentially more resilient than extending the timeout on Windows; which is important given that the underlying problem is not a timeout, but that Windows has a mandatory file locking on both FAT32 and NTFS (and CIFS)

I prototyped a simple change to pip/_internal/utils/misc.py, but this needs to be more surgical so that we always do know we are ignoring this error only in a temporary directory. If removing a package, changes to site-packages just simply need to happen successfully.

I will pursue this alternate fix and be ready to test pip with @choyrim's fix once it has resolved. I will submit a pull request if I think it is good behavior, and we can see; I like that @choyrim's fix helps the error/warning message.

danizen pushed a commit to danizen/pip that referenced this issue Dec 30, 2019
- Try to delete the temporary directory, as most of the time this works
- If it fails on windows due to an Access error, it clearly is not an
  Access error because we just created the directory.
  Warn the user and continue.
danizen pushed a commit to danizen/pip that referenced this issue Dec 30, 2019
- Add issue description file
@choyrim
Copy link
Author

choyrim commented Dec 31, 2019

@danizen,

wow, i missed a lot. thank you for joining the fun.

I've written a utility that can create similar problems even without Cylance Protect, and can make the failure more consistent. This is https://github.com/danizen/watchandlock.
. . . >8 . . .
TL;DR - I'm not getting exactly the same error, possibly because I'm opening the file and not the directory. I've also confirmed using procmon that Cylance Protect is simply doing the following:

I also found that spawning another process to hold open the file did not reproduce the "access denied" error and instead got a "... being used by another process ..." error which is what is reported by python 2.7 i think.

I've gone over the procmon logs in detail to see if there is some other special thing that cylance is doing and couldn't find anything. the only difference is that cylance is running under a different user (SYSTEM). but i haven't figured out how to test that.

@choyrim
Copy link
Author

choyrim commented Dec 31, 2019

@danizen,

I wonder whether the answer is simply to ignore the error on Windows only, and attempt to delete a matching directory before doing and installation of de-installation:

   shutil.rmtree(..., ignore_errors=platform.platform().startswith('Windows'))

Or something like the above.

I suggested something similar earlier in the thread - to ignore the error on failure. @pfmoore and @chrahunt felt that it was not appropriate to accomodate virus scanners. however, I had suggested ignoring the failure in general.

@pfmoore @chrahunt would you be more open to ignoring the failure on windows only?

@danizen
Copy link

danizen commented Dec 31, 2019

@pfmoore @chrahunt , I'm talking about a very specific ignore in pip._internal.utils.temp_dir:

--- a/src/pip/_internal/utils/temp_dir.py
+++ b/src/pip/_internal/utils/temp_dir.py
@@ -4,6 +4,7 @@ import errno
 import itertools
 import logging
 import os.path
+import sys
 import tempfile
 from contextlib import contextmanager
@@ -124,7 +125,21 @@ class TempDirectory(object):
         """
         self._deleted = True
         if os.path.exists(self._path):
-            rmtree(self._path)
+            try:
+                rmtree(self._path)
+            except OSError as e:
+                skip_error = (
+                    sys.platform == 'win32' and
+                    e.errno == errno.EACCES and
+                    getattr(e, 'winerror', 0) in {5, 32}
+                )
+                if skip_error:
+                    logger.warning(
+                        "%s (virus scanner may be holding it)."
+                        "cannot remove '%s'",
+                        e.strerror, e.filename)
+                else:
+                    raise

@danizen
Copy link

danizen commented Dec 31, 2019

Also note if this problem occurs and is not ignored, pip fails, and the pip temporary directory in %TEMP% remains anyway, so we are making it better not worse:

C:\Users\davisda4\workspace\pip7280 (7280-alt-3 -> origin)
(pip7280) λ ls -d %TEMP%/pip-unpack*
'C:\Users\davisda4\AppData\Local\Temp/pip-unpack-1hugvn1n'/  'C:\Users\davisda4\AppData\Local\Temp/pip-unpack-oukq4ewt'/
'C:\Users\davisda4\AppData\Local\Temp/pip-unpack-5mx_frb4'/  'C:\Users\davisda4\AppData\Local\Temp/pip-unpack-ruq3u2q4'/
'C:\Users\davisda4\AppData\Local\Temp/pip-unpack-7516vdj7'/  'C:\Users\davisda4\AppData\Local\Temp/pip-unpack-uoptvt69'/
'C:\Users\davisda4\AppData\Local\Temp/pip-unpack-dkxsc6c1'/  'C:\Users\davisda4\AppData\Local\Temp/pip-unpack-y0n7gg2t'/
'C:\Users\davisda4\AppData\Local\Temp/pip-unpack-lbw7iy9a'/  'C:\Users\davisda4\AppData\Local\Temp/pip-unpack-y8w8297d'/
'C:\Users\davisda4\AppData\Local\Temp/pip-unpack-my798z3y'/

@pfmoore
Copy link
Member

pfmoore commented Jan 2, 2020

AFAIK, windows does not support the ability to open a file that will "allow others to delete".

From MSDN:

FILE_SHARE_DELETE0 (x00000004) | Enables subsequent open operations on a file or device to request delete access.Otherwise, other processes cannot open the file or device if they request delete access.

I've not tested it, but this is clearly intended to allow deletion by others.

@danizen
Copy link

danizen commented Jan 2, 2020

FILE_SHARE_DELETE0 (x00000004) | Enables subsequent open operations on a file or device to request delete access.Otherwise, other processes cannot open the file or device if they request delete access.

I've not tested it, but this is clearly intended to allow deletion by others.

Both python (pip) and CylanceSvc.exe are opening the wheel file with this flag. Like @pfmoore said, I'd rather avoid going way too deep in a solution.

danizen pushed a commit to danizen/pip that referenced this issue Jan 2, 2020
- TempDirectory() tries to delete the directory as normal
- if cleanup fails on Windows due to EACCES, warn about virus scanner
- This is a more specific error than previous error, but does not change
  the behavior when a user is attempting to pip uninstall in a system directory
- This changes the messaging, but risks leaving the directory behind.
- Leaving the directory behind, however, is what is already happening
- The fix for pypa#7479 gives the Virus scanner more time to finish its work,
  but there is still a possibility for a race condition that leaves the
  impression that there is an error in pip itself.
danizen pushed a commit to danizen/pip that referenced this issue Jan 2, 2020
@danizen
Copy link

danizen commented Jan 2, 2020

@pfmoore @chrahunt, pull request #7544 is my attempt to fix by issuing a warning. It complements the change for issue #7479; thanks. It is very important that the original behavior of warning about permissions remains, even on Windows, because pip install is globally used by my engineers, despite my rants about virtual environments. So, I like fixing this at the TempDirectory level when we really can know it shouldn't be a true permissions error; as we just created the temporary directory seconds ago.

For my engineers who do use virtual environments, this problem has just become too frequent for me to ignore, but from the level of pip collaborators, I can see why it is minor.

danizen pushed a commit to danizen/pip that referenced this issue Jan 2, 2020
danizen pushed a commit to danizen/pip that referenced this issue Jan 2, 2020
danizen pushed a commit to danizen/pip that referenced this issue Jan 2, 2020
- IOError persists as an issue in Python 2.7.17 on win32
danizen pushed a commit to danizen/pip that referenced this issue Jan 2, 2020
danizen pushed a commit to danizen/pip that referenced this issue Jan 2, 2020
danizen pushed a commit to danizen/pip that referenced this issue Jan 2, 2020
- TempDirectory() tries to delete the directory as normal
- if cleanup fails on Windows due to EACCES, warn about virus scanner
- This is a more specific error than previous error, but does not change
  the behavior when a user is attempting to pip uninstall in a system directory
- This changes the messaging, but risks leaving the directory behind.
- Leaving the directory behind, however, is what is already happening
- The fix for pypa#7479 gives the Virus scanner more time to finish its work,
  but there is still a possibility for a race condition that leaves the
  impression that there is an error in pip itself.
@danizen
Copy link

danizen commented Jan 10, 2020

One of my programmers posts this morning to slack (8:33 am):

Can someone please help me with this error:
pip install -r Q:\gitRepoQ\CMDBD_My_IT_Services\requirements.txt
...
  Downloading https://tools.nlm.nih.gov/artifactory/api/pypi/nlm-pypi-developers/packages/packages/cb/c9/ef1e25bdd092749dae74c95c2707dff892fde36e4053c4a2354b2303be10/Django-2.2.9-py3-none-any.whl (7.5MB)
    100% |████████████████████████████████| 7.5MB 3.3MB/s
Could not install packages due to an EnvironmentError: [WinError 5] Access is denied: 'C:\\Users\\MARKOW~1\\AppData\\Local\\Temp\\pip-unpack-od87lcs4\\Django-2.2.9-py3-none-any.whl'
Consider using the `--user` option or check the permissions.
Then tried:

@danizen
Copy link

danizen commented Apr 14, 2020

I have only seen this issue twice since pip 20 was released, and in each case, the virtual environment involved had not been upgraded. Checking in - should this issue be resolved then?

@pradyunsg
Copy link
Member

If this still happens occasionally for users, we should keep this open IMO - to indicate that this needs investigation eventually.

@pradyunsg
Copy link
Member

Is there some way for to diagnose / infer that the issue is caused by a virus scanner or something?

@pfmoore Would it make sense to include "maybe it's your virus scanner" in the error message we're printing?

@pradyunsg pradyunsg added OS: windows Windows specific kind: crash For situations where pip crashes labels May 12, 2020
@danizen
Copy link

danizen commented May 12, 2020

If this still happens occasionally for users, we should keep this open IMO - to indicate
that this needs investigation eventually.

Due to the changes in pip 20+, when/if this happens, it will not prevent the package from installing, but instead prevent the directory from being removed.

@choyrim
Copy link
Author

choyrim commented May 12, 2020

Looking at #7479 it seems like this problem isnt an issue anymore. I've moved on to using WSL and then finally getting a linux laptop. so closing this seems appropriate.

@pfmoore
Copy link
Member

pfmoore commented May 12, 2020

@pfmoore Would it make sense to include "maybe it's your virus scanner" in the error message we're printing?

I'm mildly against doing so. Virus scanners are the most likely culprit, but it would be awfully easy for a message like that to get seen as "pip is advising people to turn off their virus scanners" 🙁

The OP's message shows that the user was aware that it was the virus scanner - so adding a clarification to the message wouldn't have helped here in any case.

@choyrim
Copy link
Author

choyrim commented May 12, 2020

The OP's message shows that the user was aware that it was the virus scanner - so adding a clarification to the message wouldn't have helped here in any case.

hinting that the antivirus software may be preventing the deletion would've helped a lot.

When I first saw the message, I would just keep retrying. Sometimes older versions would install more easily (depending on the size of the download I suppose). I eventually hacked up a workaround by changing my local copy. But when I heard about other ppl at work with the same issue, I dug in and tried to find a root cause - which was far from obvious.

however, it looks like the change in #7479 no longer deletes at the end of the download so it should let the install finish, right? so then this becomes a non-issue if i understand correctly.

@danizen
Copy link

danizen commented May 12, 2020

hinting that the antivirus software may be preventing the deletion would've helped a lot.

Agreed.

it should let the install finished right? so then this becomes a non-issue if i understand correctly.

pip still tries to cleanup the directory, but after the installation completes.

So, what happens if this happens again then? The developer/user gets the error and retries...

I'm for closing this because if the developers retries the install, they will see that the install already happened. And ... they still have the message to indicate that the directory was unable to be deleted.

@pradyunsg
Copy link
Member

it would be awfully easy for a message like that to get seen as "pip is advising people to turn off their virus scanners" 🙁

That's True. OTOH, I think we can phrase this nicely.

if WINDOWS:
    print("We've received reports that aggressive antivirus software can also result in such errors, in which case, re-running the pip command usually succeeds.")

;)

@prasanthcakewalk
Copy link

prasanthcakewalk commented Nov 25, 2020

pip still tries to cleanup the directory, but after the installation completes.

So, what happens if this happens again then? The developer/user gets the error and retries...

I'm for closing this because if the developers retries the install, they will see that the install already happened. And ... they still have the message to indicate that the directory was unable to be deleted.

Apparently the order of operations goes:

  1. Move previous version to a backup directory within site-packages (as a failsafe to revert to, in case the installation fails).
  2. Download the update.
  3. Install the update.
  4. Cleanup the download directory.
  5. Cleanup the backup directory.

If 4 gets interrupted because of an antivirus, 5 doesn't happen.
Unfortunately the backup directories aren't ignored by pip completely. For example, they show up as outputs in pip list and cause issues with pip-tools. https://discuss.python.org/t/pip-list-has-packages-whose-name-starts-with-a-hypen/2047

If the order of 4 and 5 also get flipped, that'll be great! Then the only issue will be be that a Temp directory wasn't deleted and there'd be an error message to indicate it.


When upgrading multiple packages in one command, the cleanup error prevents the subsequent upgrades from installing. If the download directory cleanup is done together after installing all the upgrades, that'll be great. But not sure how much work that would be. Besides, re-running the upgrade command is not really a big deal.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
kind: crash For situations where pip crashes OS: windows Windows specific state: needs discussion This needs some more discussion
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants