fish-shell / fish-shell

The user-friendly command line shell.
https://fishshell.com
Other
26.16k stars 1.91k forks source link

Slow to startup on fresh boot of macOS #10535

Open nixpulvis opened 5 months ago

nixpulvis commented 5 months ago

This seems like a regression of the solution to #6625 but I'm not 100% sure.

fish, version 3.7.1

Darwin kandil.cable.rcn.com 23.5.0 Darwin Kernel Version 23.5.0: Wed May  1 20:16:51 PDT 2024; root:xnu-10063.121.3~5/RELEASE_ARM64_T8103 arm64

alacritty 0.14.0-dev (48c088a5)
xterm-256color

I've restarted with -p enabled and the largest time is spent in these calls:

22280    22280    -----> command git rev-parse --git-dir --is-inside-git-dir --is-bare-repository --is-inside-work-tree HEAD 2>/dev/null
22955    22955    -------> xcrun --show-cache-path 2>/dev/null
223171   223171   -----> command git rev-parse --git-dir --is-inside-git-dir --is-bare-repository --is-inside-work-tree HEAD 2>/dev/null
12620297 12620297 -----> command git rev-parse --git-dir --is-inside-git-dir --is-bare-repository --is-inside-work-tree HEAD 2>/dev/null

I'm happy to try some other debugging steps if you let me know how I can help.

faho commented 5 months ago

For context: xcode ships some terrible shims that take ages to set up so the first git call takes ages.

We kick off git in the background and then check if the xcrun cache-path exists to determine whether it's set up or not.

Apparently that got changed so that check is no longer working - wouldn't be the first time apple broke something around us (see the apropos saga).

I have no idea what the correct check now is and I don't have a mac to check, so I'm not going to be of help here.

My recommendation for now is to install a git outside of xcode and let that take precedence in $PATH.

mqudsi commented 5 months ago

For posterity and reference, can you do a which git, ldd git, and find / -samefile (which git) and share the outputs?

faho commented 5 months ago

Make that command -s git and find / -samefile (command -s git)

nixpulvis commented 5 months ago

@mqudsi

$ command -s git
/usr/bin/git
$ otool -L (command -s git)
/usr/bin/git:
    /usr/lib/libxcselect.dylib (compatibility version 1.0.0, current version 1.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1345.120.2)
$ find / -samefile (command -s git)
/usr/bin/indent
/usr/bin/xml2man
/usr/bin/gcov
/usr/bin/g++
/usr/bin/libtool
/usr/bin/Rez
/usr/bin/ctags
/usr/bin/c++
/usr/bin/strings
/usr/bin/gm4
/usr/bin/clang
/usr/bin/git-receive-pack
/usr/bin/objdump
/usr/bin/c99
/usr/bin/c++filt
/usr/bin/strip
/usr/bin/python3
/usr/bin/clangd
/usr/bin/lex
/usr/bin/ctf_insert
/usr/bin/sourcekit-lsp
/usr/bin/pip3
/usr/bin/ld
/usr/bin/lldb
/usr/bin/pagestuff
/usr/bin/headerdoc2html
/usr/bin/hdxml2manxml
/usr/bin/resolveLinks
/usr/bin/git-upload-archive
/usr/bin/gcc
/usr/bin/segedit
/usr/bin/dwarfdump
/usr/bin/gperf
/usr/bin/vtool
/usr/bin/dsymutil
/usr/bin/SplitForks
/usr/bin/rpcgen
/usr/bin/otool
/usr/bin/DeRez
/usr/bin/codesign_allocate
/usr/bin/cpp
/usr/bin/swift
/usr/bin/make
/usr/bin/nm
/usr/bin/flex++
/usr/bin/llvm-gcc
/usr/bin/ar
/usr/bin/unifdef
/usr/bin/lipo
/usr/bin/clang++
/usr/bin/m4
/usr/bin/asa
/usr/bin/swiftc
/usr/bin/as
/usr/bin/gnumake
/usr/bin/ResMerger
/usr/bin/bm4
/usr/bin/git-shell
/usr/bin/cc
/usr/bin/unifdefall
/usr/bin/size
/usr/bin/llvm-g++
/usr/bin/yacc
/usr/bin/c89
/usr/bin/git-upload-pack
/usr/bin/lorder
/usr/bin/ranlib
/usr/bin/flex
/usr/bin/SetFile
/usr/bin/nmedit
/usr/bin/bison
/usr/bin/mig
/usr/bin/GetFileInfo
/usr/bin/git
/usr/bin/install_name_tool
/usr/bin/cmpdylib
/usr/bin/gatherheaderdoc
...
mqudsi commented 5 months ago

Ok, we're in luck and as I suspected it's a hard link to a single binary that does this for a lot of other stuff. Can you post the un-truncated list of find output as an attachment?

mqudsi commented 5 months ago

Also, please attach the full profile so we can see in which order the commands are executed and how long specific git invocations take.

nixpulvis commented 5 months ago

I don't think my truncation missed anything, I simply stopped the find once it started returning permission errors searching folders this user doesn't have access to.

And here's the full profile: https://paste.rs/vBWhf.txt

mqudsi commented 5 months ago

Ok, thanks.

Going through the profile, it seems that the logic behind a77bc70defd47db9c401127465d7e71d05184200 no longer holds up. Specifically, we kick off a backgrounded git --version task, but then the first time we run xcrun --show-cache-path it returns something that passes the path is ... check. We take that to mean that the cache has been populated and subsequent git invocations will be quick, but immediately thereafter when we try to use git rev-parse ... for real, it hangs for 12 seconds. Subsequent invocations are much faster, so it's not probably it's because the cache isn't being used; meaning most likely our assumption that xcrun --show-cache-path returning a valid path implies a git invocation will be quick is incorrect.

I don't see why we need to do this dance with xcrun at all; maybe it's only because we can't background fish functions? It's an ugly hack, but instead of backgrounding git --version then checking/waiting for xcrun --show-cache-path to return a valid path, why don't we just do the following:

function __fish_is_git_ready
    # Specifically use /tmp/ because that's where the xcrun cache is stored and
    # it is cleared on startup. We have no guarantee that our normal fish temp
    # directory would be cleared on startup.
    if ! path is /tmp/__fish_git_xcrun_primed
        # Ignore this race condition; it's unlikely to happen and OK if it does
        if ! path is /tmp/__fish_git_xcrun_started
            touch /tmp/__fish_git_xcrun_started
            sh -c 'git --version && touch /tmp/__fish_git_xcrun_primed' &
            disown
        end
        return 1
    end
    functions -e __fish_is_git_ready
    return 0
end

# In fish_git_prompt:

# if Darwin
    if functions -q __fish_is_git_ready && not __fish_is_git_ready
        return 1
    end
# endif

# Carry on
mati865 commented 2 months ago

Would using gitoxide to access the repository directly, instead of calling the git binary, help here?

faho commented 2 months ago

A new dependency is out of the question, as is the work necessary to make it accessible to scripts in a way that the git prompt can get all the info it needs.

People on macOS can already install git outside of xcode.

ridiculousfish commented 3 weeks ago

From what I can tell, on macOS 15 (Sequoia), installing the developer tools also eagerly populates the xcrun path, so that xcrun --show-cache-path has a path and git does not take multiple seconds to run.

I will try on 14.5 as well though I'm not sure how many heroics we want to go through this round.

nixpulvis commented 3 weeks ago

@ridiculousfish I'm running 14.6.1 at the moment. Let me know if I can help test/debug anything.

ridiculousfish commented 4 days ago

Ok I'm able to reproduce this. Simple way is to use xcrun --kill-cache and then it reproduces - approximately a six second hang.

What's changed is that the cache file is now created eagerly, but populated lazily:

        function __fish_git_prompt_ready
            path is (xcrun --show-cache-path 2>/dev/null) || return 1
            # git is ready, erase the function.
            functions -e __fish_git_prompt_ready
            return 0
        end

xcrun --show-cache-path now returns a real path with a real file even though it hasn't cached git yet.

The ideal fix is to ask xcrun if git is cached, but I'm not sure there's an option for that. One very hacky possibility is to look for git in the cache, but that seems quite fragile. A third possibility is to do nothing: this will only reproduce on Macs which have the CLI tools installed but they have not yet run.

ridiculousfish commented 4 days ago

@nixpulvis can you confirm this only happens the first time you run git? That is, fish may hang for a few seconds on its first run, if you have never run git before; and after that new launches of fish are fast? We may be able to live with that.

ridiculousfish commented 4 days ago

Oh it will affect a fresh boot. That makes sense. So it will happen more often than once.

I haven't thought of something better than @mqudsi 's suggestion