171 lines
7.4 KiB
Markdown
171 lines
7.4 KiB
Markdown
# blog dds: 2021-11-27 — Rather than alchemy, methodical troubleshooting
|
||
|
||
- **期号**: SRE Weekly Issue #299(2021-12-05)
|
||
- **作者**: Diomidis Spinellis
|
||
- **链接**: https://www.spinellis.gr/blog/20211127/
|
||
|
||
## 简介
|
||
|
||
They constructed a set of git commits, one for each environment variable, then used git bisect to figure out which variable was causing the failure. Neat trick!
|
||
|
||
## 正文
|
||
|
||
I recently encountered a pesky problem while trying to
|
||
build a React Native project under Apple’s *Xcode*.
|
||
The build would fail with an error reporting:
|
||
`EMFILE: too many open files, watch`.
|
||
Frustratingly, all available advice on the web pointed to
|
||
different (often inexplicable) directions, none of which worked.
|
||
After tormenting myself with these, I decided to troubleshoot
|
||
the problem methodically, which allowed me to pinpoint it and
|
||
solve it with an uncommon and noteworthy application of the
|
||
*git bisect* command.
|
||
Here’s the story.
|
||
|
||
A Google search for
|
||
[EMFILe xcode](https://www.google.com/search?q=emfile+xcode)
|
||
returns 49,000 results with many inexplicable and conflicting suggestions.
|
||
These [include the following](https://github.com/facebook/watchman/issues/923).
|
||
|
||
- Increasing the open files limit with `ulimit` , with`launchctl` ,
|
||
by modifying`/etc/sysctl.conf` , or by creating a[plist file](https://gist.github.com/tombigel/d503800a282fcadbee14b537735d202c)
|
||
- Installing *watchman*
|
||
- Reinstalling *watchman*
|
||
- Upgrading *watchman*
|
||
- Installing watchman using `x86_64 brew`
|
||
- Reinstalling *brew*
|
||
- Running *watchman* under Rosetta 2
|
||
- Not running *Xcode* under Rosetta
|
||
- Clearing the build files
|
||
- Removing and reinstalling the `node_modules` files (classic)
|
||
- Removing the cached Cocoa Pods and reinstalling them,
|
||
- Excluding the `arm64` architecture in the*Xcode* project’s build specifications
|
||
- Using an earlier version of Node
|
||
- Creating a symbolic link from `/usr/local/var/run/watchman`
|
||
|
||
Although advice from the web can often help us solve tough problems
|
||
in seconds, as the author of the book
|
||
[Effective Debugging](https://www.spinellis.gr/debugging),
|
||
I felt ashamed of wasting time by following increasingly nonsensical advice.
|
||
I therefore decided to troubleshoot the issue methodically, even though my
|
||
knowledge of *Xcode* and the macOS platform is very limited.
|
||
My reasoning was that the top-down troubleshooting advice I give in the book
|
||
can be applied to any problem.
|
||
|
||
My first step was to verify that the increased file limit I enforced
|
||
by creating a `/Library/LaunchDaemons/limit.maxfiles.plist` file
|
||
was visible to *Xcode*.
|
||
For this I created a one-line shell script file containing
|
||
`open -a Terminal`, and added this as a new *Xcode* “behavior”
|
||
(Xcode - Behaviors).
|
||
Running `ulimit -n` in the new terminal, showed me that *Xcode*’s
|
||
environment was indeed running with an increased limit on open files.
|
||
I further validated this by running a Perl command to create an
|
||
array of open files until it was no longer possible.
|
||
|
||
```
|
||
perl -e 'for (;;) { open($a[++$i], "<", "/dev/null") || die "$!: $i"; }'
|
||
Too many open files in system: 61791 at -e line 1.
|
||
```
|
||
Having excluded the possibility of a low open file limit, my second
|
||
step involved focusing on the failing command.
|
||
This was a React Native bundling step,
|
||
clearly visible on the *Xcode* build log.
|
||
I pasted the command on a terminal window, and, surprisingly, it worked
|
||
without a hitch.
|
||
I also pasted the command on the *Xcode* terminal window I had created,
|
||
and it worked there as well.
|
||
This demonstrated that the reason of the failure was the environment
|
||
in which the command was running.
|
||
|
||
My third step involved methodically determining how the command’s environment
|
||
was causing its failure.
|
||
The command was preceded by the setting of hundreds (488) of environment
|
||
variables, starting with `export ACTION=install` and ending with
|
||
`export variant=normal`.
|
||
Which of these was causing the command’s failure?
|
||
|
||
The failing command took 23 seconds to execute, so iteratively adding
|
||
each variable until the command would fail could take up to three hours.
|
||
Instead, I used the `git bisect` command to binary search for the offending
|
||
variable.
|
||
The *git bisect* command is normally used to find which of a series
|
||
of commits is responsible for a failure.
|
||
|
||
As I lacked such a series, I created a synthetic one for *git bisect*
|
||
to run on.
|
||
I started this process by exporting the *Xcode* log file, and editing
|
||
it into a shell script.
|
||
I then created a new Git branch in which I committed the script with all
|
||
variables commented out, e.g. `#export ACTION=install`.
|
||
This was the working commit.
|
||
I then run a shell loop to reintroduce each variable and commit each result,
|
||
thus creating a series of 488 commits (one for each introduced variable).
|
||
|
||
(If you want to find more about creating such commands,
|
||
consider following my free edX MOOC
|
||
[Unix Tools: Data, Software and Production Engineering](https://www.spinellis.gr/unix?bl202101127).)
|
||
At the end of the loop the script was in its failing form.
|
||
|
||
I then provided to *git bisect* the first correct and the first
|
||
failing commit, and left it running to find
|
||
the one that caused the failure.
|
||
|
||
```
|
||
$ git bisect start
|
||
$ git bisect bad
|
||
$ git bisect good 6fec32ad0627ca1890a234982f08eb128ba9039a
|
||
Bisecting: 243 revisions left to test after this (roughly 8 steps)
|
||
$ git bisect run xcode-bundle
|
||
running xcode-bundle
|
||
[...]
|
||
Bisecting: 243 revisions left to test after this (roughly 8 steps)
|
||
Bisecting: 121 revisions left to test after this (roughly 7 steps)
|
||
Bisecting: 60 revisions left to test after this (roughly 6 steps)
|
||
Bisecting: 30 revisions left to test after this (roughly 5 steps)
|
||
Bisecting: 15 revisions left to test after this (roughly 4 steps)
|
||
Bisecting: 7 revisions left to test after this (roughly 3 steps)
|
||
Bisecting: 3 revisions left to test after this (roughly 2 steps)
|
||
Bisecting: 1 revision left to test after this (roughly 1 step)
|
||
Bisecting: 0 revisions left to test after this (roughly 0 steps)
|
||
879f283a7ea7cf92d9720c107aa34b90ee8f6451 is the first bad commit
|
||
bisect run success
|
||
```
|
||
A look at the offending bad commit, showed me that the problematic
|
||
statement set the `PATH` environment variable.
|
||
Running the previously working bundling command with the set `PATH`
|
||
caused it to fail, validating the finding.
|
||
Thanks to the binary search strategy of *git bisect*
|
||
Git pinpointed the offending commit in just 9 rather than 488 steps,
|
||
which took less than four minutes rather than hours.
|
||
|
||
I examined the two `PATH` variables and saw that, among other differences,
|
||
the faulty `PATH` lacked the *brew* path `/opt/homebrew/bin`.
|
||
Adding the *brew* path to the faulty `PATH` variable fixed the
|
||
problem in the terminal window.
|
||
|
||
The last step involved fixing the issue in *Xcode*.
|
||
For that I modified the shell script associated with the
|
||
“Bundle React Native code and images” project’s build phase, adding
|
||
a line to fix the path: `export PATH="$PATH:/opt/homebrew/bin"`.
|
||
|
||
After wasting many hours with web-advice alchemy, I was able to fix the problem in less than an hour of methodical troubleshooting. Lesson learned!
|
||
|
||
Navigation
|
||
|
||
|
||
Tagged as
|
||
|
||
|
||
Become a Unix command line wizard
|
||
|
||
Debug like a master
|
||
|
||
Compute with style
|
||
|
||
Recent posts
|
||
|
||
Last modified: Saturday, November 27, 2021 8:21 pm
|
||
|
||
Unless otherwise expressly stated, all original material on this page created by Diomidis Spinellis is licensed under a [Creative Commons Attribution-NonCommercial 4.0 International License](https://creativecommons.org/licenses/by-nc/4.0/).
|