Repository: iterative/dvc

Issue:
dvc: pretty slow on Mac
I experience around 10 seconds before and another 5 to 10 after `dvc version` output (below) on my Macbook Air.

```
DVC version: 0.61.2
Python version: 3.7.3
Platform: Darwin-18.7.0-x86_64-i386-64bit
Binary: False
Cache: reflink - True, hardlink - True, symlink - True
Filesystem type (cache directory): ('apfs', '/dev/disk1s1')
Filesystem type (workspace): ('apfs', '/dev/disk1s1')
```

Attached are a couple logs of `python3 -mcProfile -s cumtime -m dvc version`, the second one after opting out from analytics:

[python3 -mcProfile -s cumtime -m dvc version (with analytics).log](https://github.com/iterative/dvc/files/3694724/python3.-mcProfile.-s.cumtime.-m.dvc.version.with.analytics.log)
[python3 -mcProfile -s cumtime -m dvc version (without analytics).log](https://github.com/iterative/dvc/files/3694725/python3.-mcProfile.-s.cumtime.-m.dvc.version.without.analytics.log)


Discussion:
p.s. My connection is pretty fast, no VPN, and I've experienced this on several different networks recently (residential, WeWork, cafes, hotel).
Tried adding sleep(100) to places where `requests` is called on both mac and linux and was not able to reproduce. @jorgeorpinel Are you still able to reproduce? If so, could you check if there are any dvc processes running in the background? E.g. `ps -xaf | grep dvc`?
Yeah still happening on my machine with the latest DVC version. I ran `ps -xaf | grep dvc` a bunch of times on a separate terminal and It's just `...Python /usr/local/bin/dvc version` for the first 10 seconds (before output) and then both that and `...Python /usr/local/bin/dvc daemon -q analytics ...` for the last 10 seconds after output.

But oh well, as long as this is a problem only on my machine then it's not really an issue, right? Feel fee to close unless you want to investigate further. Glad to give you SSH access or something.
@jorgeorpinel No reason to close this, we don't know if it is happening to anyone else, but they are just stopping using it without reporting the issue :) Let's have a meeting together, so I could take a look, maybe it is something obvious that I'm missing. I'll ping you in PM's. Thank you :slightly_smiling_face: 
We've had a debugging session with @jorgeorpinel and found that it is `flufl.lock -> socket.getfqdn -> socket.gethostbyaddr` that is slow. On further inspection, we've found that he has some pretty unusual dns settings that seem to be the cause of this. He has now adjusted his dns settings and we are waiting for new results.
Ok, so turns out on Mac "Computer name" that you set in `Settings -> Sharing` is not recognisable by anyone until you enable "Remote Login" service. So even `ping Ruslans-MacBook-Pro.local` will hang and then fail. The moment you enable that service, everything starts to work instantly. `/etc/hosts` is not updated by that operation, so not quite sure yet about the precise mechanism. But I am able to reproduce now and I am investigating. 
Ok, so it is a cpython bug https://bugs.python.org/issue5004 . There is a patch for it from like 9 years ago. We can either monkeypatch it or patch it in flufl.lock and or try to push this fix into cpython as well. Working on this right now.
Thanks for looking at this Ruslan! Glad there's a workaround we can use in `lock.py` so Mac users don't have a bad experience with DVC (I guess most people don't enable Remote login) even if that cpython bug doesn't get addressed soon.
For the record, submitted a PR https://gitlab.com/warsaw/flufl.lock/merge_requests/12 . @jorgeorpinel confirmed that it works for him now.
@jorgeorpinel 0.63.3 is out. Please upgrade :slightly_smiling_face: 
Nice! It's fast now before output but still around 13 seconds to get back to the console prompt AFTER the command output, whether with or without analytics... Here's the profiler output: https://pastebin.com/isBDYC4E
@jorgeorpinel Ok, this time this seems to be OS X fault for not caching dns requests https://github.com/rust-lang/rust/issues/31665 .  Will prepare a workaround ASAP.
Aaaand I can fully reproduce the original issue but now with getaddrinfo on my conda on mac :grimacing: Looking into that...

Resolving patch:
diff --git a/dvc/lock.py b/dvc/lock.py
--- a/dvc/lock.py
+++ b/dvc/lock.py
@@ -22,38 +22,7 @@ class LockError(DvcException):
 
 
 if is_py3:
-    import socket
-
     import flufl.lock
-    from funcy import monkey
-
-    # Workaround for slow and obsoleted gethostbyaddr used in
-    # `socket.getfqdn()`. See [1], [2] and [3] for more info.
-    #
-    # [1] https://bugs.python.org/issue5004
-    # [2] https://github.com/iterative/dvc/issues/2582
-    # [3] https://gitlab.com/warsaw/flufl.lock/merge_requests/12
-    @monkey(socket)
-    def getfqdn(name=""):
-        """Get fully qualified domain name from name.
-
-        An empty argument is interpreted as meaning the local host.
-        """
-        name = name.strip()
-        if not name or name == "0.0.0.0":
-            name = socket.gethostname()
-        try:
-            addrs = socket.getaddrinfo(
-                name, None, 0, socket.SOCK_DGRAM, 0, socket.AI_CANONNAME
-            )
-        except socket.error:
-            pass
-        else:
-            for addr in addrs:
-                if addr[3]:
-                    name = addr[3]
-                    break
-        return name
 
     class Lock(flufl.lock.Lock):
         """Class for dvc repo lock.
@@ -65,12 +34,31 @@ class Lock(flufl.lock.Lock):
         """
 
         def __init__(self, lockfile, tmp_dir=None):
-            lifetime = timedelta(days=365)  # Lock for good by default
+            import socket
+
             self._tmp_dir = tmp_dir
             if self._tmp_dir is not None:
                 makedirs(self._tmp_dir, exist_ok=True)
 
-            super(Lock, self).__init__(lockfile, lifetime=lifetime)
+            # NOTE: this is basically Lock.__init__ copy-paste, except that
+            # instead of using `socket.getfqdn()` we use `socket.gethostname()`
+            # to speed this up. We've seen [1] `getfqdn()` take ~5sec to return
+            # anything, which is way too slow. `gethostname()` is actually a
+            # fallback for `getfqdn()` when it is not able to resolve a
+            # canonical hostname through network. The claimfile that uses
+            # `self._hostname` is still usable, as it uses `pid` and random
+            # number to generate the resulting lock file name, which is unique
+            # enough for our application.
+            #
+            # [1] https://github.com/iterative/dvc/issues/2582
+            self._hostname = socket.gethostname()
+
+            self._lockfile = lockfile
+            self._lifetime = timedelta(days=365)  # Lock for good by default
+            self._separator = flufl.lock.SEP
+            self._set_claimfile()
+            self._owned = True
+            self._retry_errnos = []
 
         @property
         def lockfile(self):


Test patch: