Closed sin-ack closed 3 years ago
Huh, Pyright contains logic that specifically protects against cyclic symlinks. @jakebailey, any ideas here?
@sin-ack, what version of pyright are you using? A bug was fixed in version 1.1.123 specifically to handle cyclical symlinks. The latest version is 1.1.128.
I've tried to repro by manually creating cyclical symlinks in a local project, and the latest version of pyright handles it fine. My best theory is that you're running an old version of pyright.
The only think I can think of here is that the realpath
syscall when applied to wine's filesystem does not return a consistent result when going across filesystems, and defeats the checks we do to prevent loops. Or, because Z:
is literally /
, it's so huge that we aren't looping at all, but are taking a load of time.
I guess I'm more wondering why you'd want to open up your entire home folder as a workspace. I can't imagine that going well for exactly these sorts of reasons...
@erictraut I am indeed running 1.1.128.
@jakebailey I ran strace on pyright
, and could see it running openat
on /home/myuser/.local/share/Steam/..more folders here.../dosdevices/z:
repeatedly. So I do believe it is looping.
Regarding the workspace comment... I did some digging. lsp-mode
seems to have this weird "session" thing, and somehow my home directory got into those "session folders" and got associated with Pyright, which caused the weird behavior seen here. The infinite loop thing is only a side effect, Emacs would freeze up trying to tally the file count so it could warn you that the folder is too big as well (my ~ contains about 1.7 million files from various large projects). I'm guessing it has something to do with multi-root workspaces (which I don't know anything about), but after clearing the "LSP session" thingy, Pyright started working fine. I will keep the issue open, and will try to see whether I can reproduce this bug on a smaller scale (so symlinking inside a small project instead of my whole ~).
To be clear, do you see dosdevices
repeating in those path names? Or is it just a really path that ends in dosdevices
and such?
Here's a partial log of strace from the pyright process:
...
lstat("/home", {st_mode=S_IFDIR|0755, st_size=34, ...}) = 0
lstat("/home/myuser", {st_mode=S_IFDIR|0755, st_size=8796, ...}) = 0
lstat("/home/myuser/.local", {st_mode=S_IFDIR|0755, st_size=82, ...}) = 0
lstat("/home/myuser/.local/share", {st_mode=S_IFDIR|0755, st_size=1386, ...}) = 0
lstat("/home/myuser/.local/share/Steam", {st_mode=S_IFDIR|0700, st_size=1154, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps", {st_mode=S_IFDIR|0755, st_size=452, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps/common", {st_mode=S_IFDIR|0755, st_size=218, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7", {st_mode=S_IFDIR|0755, st_size=272, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7/dist", {st_mode=S_IFDIR|0755, st_size=46, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7/dist/share", {st_mode=S_IFDIR|0755, st_size=54, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7/dist/share/default_pfx", {st_mode=S_IFDIR|0755, st_size=126, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7/dist/share/default_pfx/dosdevices", {st_mode=S_IFDIR|0755, st_size=40, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7/dist/share/default_pfx/dosdevices/z:", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
stat("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7/dist/share/default_pfx/dosdevices/z:", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
readlink("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7/dist/share/default_pfx/dosdevices/z:", "/", 4096) = 1
lstat("/home", {st_mode=S_IFDIR|0755, st_size=34, ...}) = 0
lstat("/home/myuser", {st_mode=S_IFDIR|0755, st_size=8796, ...}) = 0
lstat("/home/myuser/.local", {st_mode=S_IFDIR|0755, st_size=82, ...}) = 0
lstat("/home/myuser/.local/share", {st_mode=S_IFDIR|0755, st_size=1386, ...}) = 0
lstat("/home/myuser/.local/share/Steam", {st_mode=S_IFDIR|0700, st_size=1154, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps", {st_mode=S_IFDIR|0755, st_size=452, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps/common", {st_mode=S_IFDIR|0755, st_size=218, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7", {st_mode=S_IFDIR|0755, st_size=272, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7/dist", {st_mode=S_IFDIR|0755, st_size=46, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7/dist/share", {st_mode=S_IFDIR|0755, st_size=54, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7/dist/share/default_pfx", {st_mode=S_IFDIR|0755, st_size=126, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7/dist/share/default_pfx/dosdevices", {st_mode=S_IFDIR|0755, st_size=40, ...}) = 0
lstat("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7/dist/share/default_pfx/dosdevices/z:", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
stat("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7/dist/share/default_pfx/dosdevices/z:", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
readlink("/home/myuser/.local/share/Steam/steamapps/common/Proton 3.7/dist/share/default_pfx/dosdevices/z:", "/", 4096) = 1
readlink("/sys/class/block/sdb", "../../devices/pci0000:00/0000:00"..., 4096) = 98
lstat("/sys/devices/pci0000:00", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
lstat("/sys/devices/pci0000:00/0000:00:1a.0", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
lstat("/sys/devices/pci0000:00/0000:00:1a.0/usb1", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
lstat("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
lstat("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
lstat("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4:1.0", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
lstat("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4:1.0/host6", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
lstat("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4:1.0/host6/target6:0:0", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
lstat("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4:1.0/host6/target6:0:0/6:0:0:0", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
lstat("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4:1.0/host6/target6:0:0/6:0:0:0/block", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
lstat("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4:1.0/host6/target6:0:0/6:0:0:0/block/sdb", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
lstat("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4:1.0/host6/target6:0:0/6:0:0:0/block/sdb/device", {st_mode=S_IFLNK|0777, st_size=0, ...}) = 0
stat("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4:1.0/host6/target6:0:0/6:0:0:0/block/sdb/device", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
readlink("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4:1.0/host6/target6:0:0/6:0:0:0/block/sdb/device", "../../../6:0:0:0", 4096) = 16
lstat("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4:1.0/host6/target6:0:0/6:0:0:0/scsi_generic", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
lstat("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4:1.0/host6/target6:0:0/6:0:0:0/scsi_generic/sg2", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
lstat("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4:1.0/host6/target6:0:0/6:0:0:0/scsi_generic/sg2/subsystem", {st_mode=S_IFLNK|0777, st_size=0, ...}) = 0
stat("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4:1.0/host6/target6:0:0/6:0:0:0/scsi_generic/sg2/subsystem", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
readlink("/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4:1.0/host6/target6:0:0/6:0:0:0/scsi_generic/sg2/subsystem", "../../../../../../../../../../.."..., 4096) = 54
...
Seems like any symlink that links above triggers an infinite loop.
I guess I'm trying to decide whether or not this is actually looping, or if it's just huge because it hits the filesystem root. I'd think the former case wouldn't occur; our traversal should be remembering the realpath of each folder and checking before it recurses. But, it really should not have continued when it saw /home
the second time...
If these fs accesses are performed by the loop that scans for source files, we should see the string "Skipping recursive symlink" appear in the log output.
I've reviewed all of the other code paths in pyright where we resolve symlinks, and none of them should result in a recursive walk of the file system hierarchy.
My only other thought is that it's a problem with the file watching code.
Yeah, I went though too and didn't find anything.
I don't think it'd be the file watching, since we don't actually watch anything in the workspace, and that's where I'm presuming the link begins. I'll have to spin up wine and see for myself.
Is there anyway I can provide you with a debug log from my end?
I'm unsure how to do this in emacs, but if you can set "python.analysis.logLevel": "Trace"
, I'd like to see if it's hanging at the point where we scan for source files in the workspace.
Here's a log at Trace level. Nothing happens after the last line, and pyright is stuck at 100% CPU.
That's the LSP trace, but the logs I'm in interested are our own log messages (those sent with window/logMessage
), but I can mostly see them though the noise. If you can get those directly that'd be preferred for the future if possible.
Anyway, extracting out some bits:
Exception received when installing recursive file system watcher
Searching for source files
Auto-excluding /home/myuser/projects/myproject/env
Found 14 source files
[FG] parsing: /home/myuser/projects/myproject/app/myproject/settings.py (80ms)
...
It's not stalling during the source file search, so this would seem to be elsewhere. An exception occurs when installing the file watcher, so that's a bit concerning (we should probably print that), but in that case there wouldn't be a file watcher, so I'm at a loss as to which code comes next that can be getting stuck.
I will try to profile the pyright process on my own end. Since it's (presumably) infinite looping in Node code, that might help me see which code paths are the hottest, which should reveal what's causing the loop.
If you can manage that, that'd be most welcome.
@sin-ack, any update on this issue?
I am sorry, I'm currently very busy with work. I'll try to return to this after Monday.
@sin-ack, any more clues on this one?
I'm going to close this issue since we haven't heard anything in over two weeks. If you are still able to repro the problem and have additional clues, please post them here, and we'll re-open the issue.
I do experience the same problem with Emacs + lsp-mode and latest pyright (freshly built from git b882b9c3). Attaching node profile.
I do experience the same problem with Emacs + lsp-mode and latest pyright (freshly built from git b882b9c).
Same here (Spacemacs with lsp-mode), pyright installed with npm install -g pyright
(1.1.138).
Some logs would be very helpful to identify if this is still in the source file scanning part of the code.
Unfortunately the above profile didn't really help, other than to say that a good amount of CPU time is spent asking the FS for the realpath of things (which is sort of expected, but maybe not to that degree). I think the kind of profile that would work best is the one that the Chrome DevTools captures, versus node's built-in trace (which just shows the top entries).
In my case I nailed down the problem to importmagic
. Uninstalled it and everything is working fine now. Thank you very much!
@jakebailey, based on the profiles provided by @m-khvoinitsky, it's pretty clear that the time is being spent in the code that searches for source files (in the _matchFiles
function). I've reviewed that code, and I don't see any way that we would recurse indefinitely. My best guess is that someone has created a symbolic link to the root of the drive, and we're scanning the entire drive for python sources.
If my hypothesis is correct, this isn't really a bug in pyright, but there are things we could do to help protect against this situation — or at least make it more diagnosable.
include
file specs are intended to be relative to (and contained within) the workspace.Thoughts?
I think both are a good idea, and I'd probably want to expand 1) to maybe print all symlinks in trace mode for better info.
I'd want to wait to try and touch this until after today's release, though. Too little time for testing. 🙂
The latter case might be a bit incongruent with how VS Code treats symlinks, though; I'd want to test that out to see if VS Code presents symlinks as folders when they aren't within the workspace.
My best guess is that someone has created a symbolic link to the root of the drive, and we're scanning the entire drive for python sources.
It looks like
$ find -type l -exec bash -c '[[ "$(readlink -f "$0")" == / ]] && echo "$0"' '{}' \;
./.wine/dosdevices/z:
Makes sense because it is exactly what is written in the very first comment. :)
Right, yeah.
Has there been any insight into why your editor is trying to make your home directory a workspace? It'd be good to try and fix this, but I would also expect that opening a giant folder wouldn't be such a good idea either, and your previous comments implied it was an editor bug.
Based on the evidence from this thread, this is not a bug. Pyright is handling cyclic symlinks correct. The same behavior would occur if you attempted to open the root of your drive or your home directory as a workspace. I'm therefore going to close the bug report.
Describe the bug My home folder contains Proton (from Steam) and Wine. Wine symlinks the
dosdevices/z:
folder back to/
, to provide Windows applications with access to my home filesystem. When Pyright is run and completion is requested, it scans my home folder, eventually reaching the.wine
folder, and then infinitely loops trying to readz:
(which is/
).To Reproduce
winecfg
at least once)Expected behavior To not infinitely loop.
Editor & OS information Editor: GNU Emacs 27.1 LSP Plugin:
lsp-mode
20210402.1701 +lsp-pyright
OS: Debian GNU/Linux bullseye/testing