Repository.open doesn't resolve in some cases
See original GitHub issueSystem information
- node version: 10.15.3
- npm or yarn version: yarn 1.9.4
- OS/version/architecture: macOS (Darwin bogon 18.0.0 Darwin Kernel Version 18.0.0: Wed Aug 22 20:13:40 PDT 2018; root:xnu-4903.201.2~1/RELEASE_X86_64 x86_64)
- Applicable nodegit version: 0.24.3
Here is the example: https://github.com/Means88/nodegit-test
It contains 5 same tests
it('should pass', async () => {
await nodegit.Repository.open(
path.resolve(__dirname, '..', 'repo', '.git'),
);
}, 10000);
git init repo
yarn test
yarn run v1.9.4
$ jest
PASS test/second.test.ts
PASS test/fifth.test.ts
PASS test/first.test.ts
FAIL test/third.test.ts (10.176s)
● third test › should pass
Timeout - Async callback was not invoked within the 10000ms timeout specified by jest.setTimeout.
3 |
4 | describe('third test', () => {
> 5 | it('should pass', async () => {
| ^
6 | await nodegit.Repository.open(
7 | path.resolve(__dirname, '..', 'repo', '.git'),
8 | );
at Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:85:20)
at Suite.<anonymous> (test/third.test.ts:5:3)
FAIL test/fourth.test.ts (10.167s)
● first test › should pass
Timeout - Async callback was not invoked within the 10000ms timeout specified by jest.setTimeout.
3 |
4 | describe('first test', () => {
> 5 | it('should pass', async () => {
| ^
6 | await nodegit.Repository.open(
7 | path.resolve(__dirname, '..', 'repo', '.git'),
8 | );
at Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:85:20)
at Suite.<anonymous> (test/first.test.ts:5:3)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | Unknown | Unknown | Unknown | Unknown | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 2 failed, 3 passed, 5 total
Tests: 2 failed, 3 passed, 5 total
Snapshots: 0 total
Time: 12.219s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
And if I run jest with yarn test --maxWorkers 4
, there will be 4 test cases passed and 1 test case failed. But it will pass if I open the repository several times in one test case like
it('should pass', async () => {
await nodegit.Repository.open(
path.resolve(__dirname, '..', 'repo', '.git'),
);
await nodegit.Repository.open(
path.resolve(__dirname, '..', 'repo', '.git'),
);
}, 10000);
Maybe there are something like mutex lock and didn’t clean up after test? I’ve tried enableThreadSafety
and setThreadSafetyStatus
but nothing changed.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:1
- Comments:10
Top Results From Across the Web
How to troubleshoot why you can't resolve from a remote ...
There are often cases where users can't resolve packages from a remote repo (or from a virtual that aggregates the remote). Here are...
Read more >The folder currently open doesn't have a git repository
You need to add the repo directory as a safe directory with the recommended command: git config --global --add safe.directory 'path/to/repo'.
Read more >Troubleshooting cloning errors - GitHub Docs
If you're having trouble cloning a repository, check these common errors.
Read more >Git error - Fatal: Not a git repository and how to fix it | Datree.io
In these cases, the fix is to make sure that you are both working in the correct folder and that you set up...
Read more >VS2022 cannot connect to local git repo
Even though the solution is in a folder that is a git repo, VS 2022 can not connect to the repo. If I...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
This is caused by https://github.com/facebook/jest/issues/3552. I recently experienced the same problem after adding a 9th testsuite on a quad-core hyperthreaded machine, therefore having 8 threads, and that let me down the rabbit hole.
In the end we managed to find the culprit and I now fully understand why this issue occurs. First some context on how Jest works, as it is relevant in understanding the problem.
Explanation
Jest has its own module resolution logic to support mocking modules, and it evicts the module caches between running testsuites. By default, it will spawn N processes, with N being equal to the number of threads you have available. Therefore, as long as there’s fewer testsuites than the number of threads, each process runs only a single testsuite. Once you start having more testsuites than threads, or run Jest with the
--runInBand
option to prevent forking child processes, some of the processes may need to run multiple testsuites. Since Jest’s module loader evicts its module caches between testsuites, thenodegit
JavaScript module is unloaded with it. However, the nativenodegit
binary is not unloaded, as Jest cannot influence native modules.Unfortunately, the initial load of the
nodegit
JavaScript module has affected the state of the native module. There’s some work being done to alleviate this problem, but it doesn’t appear to work (anymore):https://github.com/nodegit/nodegit/blob/37fcf5dcab1061c4ea9a2c137872141b653d9fdc/generate/templates/templates/nodegit.js#L19-L23
What I found is that this
nodegit.js
file modifies the native functions to become promise based instead of callback based, usingpromisify
. The result of the function call ends up being written into the native module. The_.cloneDeep
does not prevent this from happening, as the result ofpromisify
is written into properties on functions, which are not eligible for deep cloning.Now, when Jest loads the
nodegit
JavaScript module again it will execute thatnodegit.js
file once again. This time however, the functions in the native module that it promisifies have already been promisified before, resulting in repeated promisification (lovely word, if it even is one 😄). I don’t know exactly what happens from there, but the double promisified function will fail to resolve. It looks like https://github.com/nodegit/promisify-node gets into a state that it doesn’t expect, leading to a dead end.Workarounds
I found essentially two workarounds for the issue, as follows:
nodegit
JavaScript module is actually a builtin module. This prevents Jest from using its own module resolution logic and module cache, sonodegit
won’t be loaded multiple times.There doesn’t appear to be any configuration options to achieve this, it requires a patch inside of
node_modules/jest-resolve/build/isBuiltinModule.js
:nodegit
module. For this, you’ll need to register a Jest setup script in your Jest configuration file:Then, create the
jest.setup.js
file with the following script:Essentially what this does is patch the Jest runtime to rewire module resolution to the native NodeJS module loader, but only if the module to load is
nodegit
.Disclaimer
With both workarounds, you’ll end up in a situation where
nodegit
has a different module loader compared to the rest of your test code. For us this meant that e.g.Date
instances created withinnodegit
were no longer matching withDate
instances in the testcases, so assertions likejasmine.any(Date)
would now fail. We didn’t look into this but simply changed the assertion tojasmine.anything()
as that will avoid the issue, while being a little less strict.Wrapping up
I suspect that from the
nodegit
side of things, it should be possible to no longer write the promisified functions into the native module. That would avoid this problem, although I don’t really understand the current way of hownodegit.js
is structured so there could be reasons why that wouldn’t be possible.From the Jest side of the equation, I think it could be very valuable to be able to configure the module loader in some ways. Either its module loader should be able to disable altogether, or it should be possible for specific modules to sidestep its module loader.
Bottom line, this was one of the most annoying bugs I have ever had to debug. I haven’t had the time to report issues, if anyone feels like doing so please feel free to link to this post.
@bhubr It’s not Jest that is hanging, it’s somewhere in the nodegit-promise library. What I found during debugging was that the double promisification seems to introduce some internal state that causes the promise resolution to get stuck. I don’t know the exact details as I’m not really familiar with its internals.
Oh well, this took me 4 hours to figure out and fix so it was a pretty deep rabbit hole.