-
-
Notifications
You must be signed in to change notification settings - Fork 250
Replace in-memory locks with file locks #1140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace in-memory locks with file locks #1140
Conversation
✅ Deploy Preview for testcontainers-node ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
| log.debug(`Container reuse has been enabled with hash "${containerHash}"`); | ||
|
|
||
| return reusableContainerCreationLock.acquire(containerHash, async () => { | ||
| return withFileLock(`testcontainers-node-reuse-${containerHash.substring(0, 12)}.lock`, async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's also worth mentioning that async-lock preserved the order of call queue within a single process, but withFileLock doesn't have that guarantee (especially with randomized delay). Should I look into it or is it a non-issue?
|
Hi @abendi, thanks for raising. The reason there's a mix of file locks and in-memory locks is for performance. This project used to use Jest and now uses Vitest and so these mechanisms were designed with the concept of separate workers already in mind. For example we use file locks when we need to synchronise across workers (we want a single resource reaper container, and to reuse it if exists), and in-memory when we don't (we don't mind if the work is duplicated, e.g. checking if an image exists). It seems your issue here is that you're starting and configuring a PG container, which you want to re-use across workers, but you want each worker to wait until the PG container is started. Adding a file lock here is too much, firstly 99.9% of the time the container is not shared across workers, so adding file locks will negatively affect performance most of the time, and secondly this can be worked around by better using the test framework. It seems for your use case you should be using Jest's |
|
Thanks for the feedback @cristianrgreco. I agree with your arguments, they make sense. I'll close the PR now. |
In-memory
async-lockworks nicely when testcontainers code is used in a single Node process, but when multiple processes are used then, for instance, reusable containers can run into race conditions.We're using jest for testing and it runs test files in separate workers that don't share the same module cache and thus bypass in-memory locks, because they don't "see" each other. The race condition we're seeing is that when one of the workers is in the middle of starting a Postgres container (
containerStartedlifecycle hook hasn't completed yet, where we initialize the database), then other worker(s) think that the pending container is already ready for use and once queries are made against the uninitialized DB, errors are propagated back.The workaround we came up with was to implement file locking in the container itself, something along the lines of this:
But then I figured why not try to fix it in the library itself, for everyone. And so this PR proposes to replace all usages of
async-lockwithproper-lockfile, which apparently was already used in a few places viawithFileLock. Something that scares me a little bit there is the 3 secondmaxTimeout, which can add extra delay to various callsites, depending on how unlucky one gets with the timings and race conditions. Maybe I should also make this timeout a little bit more aggressive?If you'd like I can also add a test to showcase the fix, but it can get quite messy as I'd have to use
child_process.fork+ IPC to synchronize with the main process to correctly simulate a race condition in parallel process setup (unless there's some cleaner way that I'm not aware of?).