Skip to content

fix: deduplicate concurrent downloads in resolveModelFile#570

Open
graciegould wants to merge 2 commits intowithcatai:masterfrom
graciegould:fix/deduplicate-concurrent-model-downloads
Open

fix: deduplicate concurrent downloads in resolveModelFile#570
graciegould wants to merge 2 commits intowithcatai:masterfrom
graciegould:fix/deduplicate-concurrent-model-downloads

Conversation

@graciegould
Copy link

Description of change

  • Current behavior: Concurrent resolveModelFile calls each start separate ipull downloads to the same .ipull temp file. When the first completes and renames the file, the other instances' file handles become orphaned. On Node 22+, garbage-collected FileHandle objects are a fatal error that crashes the process.

  • New behavior: A module-level Map tracks in-flight downloads by entrypoint path. Concurrent calls detect the existing download, cancel their own downloader, and await the shared promise.

  • Fixes #569

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • npm run format to apply eslint formatting
  • npm run test passes with this change
  • This pull request links relevant issues as Fixes #569
  • There are new or updated unit tests validating the change - N/A
  • Documentation has been updated to reflect this change - N/A
  • The new commits and pull request title follow conventions explained in pull request guidelines (PRs that do not follow this convention will not be merged)

When multiple concurrent calls to resolveModelFile target the same
model, each call independently starts a download via ipull. Multiple
ipull instances writing to the same .ipull temp file causes orphaned
FileHandle objects when the first download completes and renames the
file. On Node 22+, garbage-collected FileHandles are a fatal error.

Add a module-level Map to track in-flight downloads by entrypoint
file path. When a concurrent call detects an existing download for
the same file, it cancels its own downloader and awaits the existing
promise instead of starting a duplicate download.

Closes withcatai#569
? chalk.gray(` (combining ${downloader.splitBinaryParts} parts into a single file)`)
: ""
}`);
const downloadKey = downloader.entrypointFilePath;
Copy link
Member

@giladgd giladgd Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue with this approach is that the first call to resolveModelFile may pass a signal and cancel it and thus cause other downloads to be cancelled as well.
I think we can take a different approach by doing something like this:

import {acquireLock} from "lifecycle-utils";

...

const downloader = await createModelDownloader({
    ...
});

const lock = await acquireLock([resolveModelFile, "download", downloader.entrypointFilePath]);
try {
    if (await fs.pathExists(downloader.entrypointFilePath)) {
        await downloader.cancel({deleteTempFile: false});
        return downloader.entrypointFilePath;
    }


    ...

    return downloader.entrypointFilePath;
} finally {
    lock.dispose();
}

In this case if the first download gets cancelled, the next call to resolveModelFile will redownload (and use the headers, tokens and other parameters passed to that resolveModelFile call).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! I will update this.

@graciegould graciegould force-pushed the fix/deduplicate-concurrent-model-downloads branch from 5334f6b to b5c177b Compare March 6, 2026 22:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: concurrent calls to resolveModelFile cause multiple downloads and file handle leaks

2 participants