Skip to content

fix handling of concurrent package installs#756

Merged
karawoo merged 1 commit into
mainfrom
kara/packrat-concurrent-install-race
Jul 14, 2026
Merged

fix handling of concurrent package installs#756
karawoo merged 1 commit into
mainfrom
kara/packrat-concurrent-install-race

Conversation

@karawoo

@karawoo karawoo commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Concurrent R processes installing the same package into a shared package cache can collide in moveInstalledPackageToCache(), where the losing process fails with "failed to restore package ... may be lost from cache".

The function already attempted to handle this with a file.exists() check before renaming into the cache, but a competing process can populate the cache entry between the check and the rename. This PR moves the check to after the rename fails, where there is no window.

Also, only roll back a backed-up cache entry when a backup was actually made. The rollback previously ran unconditionally, so a fresh package that failed to insert reported "may be lost from cache" instead of "failed to copy package to cache".

The race window is extremely small, so the tests mock file.rename to simulate the losing interleaving.

@karawoo karawoo merged commit e53a9df into main Jul 14, 2026
10 checks passed
@karawoo karawoo deleted the kara/packrat-concurrent-install-race branch July 14, 2026 20:27

@aronatkins aronatkins left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks so much for improving this cache population logic; it has been a source of fragility.

Comment thread R/cache.R
tempPath <- tempfile(tmpdir = dirname(cachedPackagePath))
on.exit(unlink(tempPath, recursive = TRUE), add = TRUE)
if (all(dir_copy(packagePath, tempPath))) {
# check to see if the cached package path exists now; if it does,

@aronatkins aronatkins Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The dir_copy is a comparatively slow operation and likely to see two processes racing. Unfortunately, because there is time between file.exists and file.rename, our previous code resolved the most common type of races, but did not eliminate them.

Because file.rename is atomic, using it alone is a better guard than our previous file.exists before file.rename.

👍

Comment thread R/cache.R
}

# attempt to rename to cache
if (suppressWarnings(file.rename(packagePath, cachedPackagePath))) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note: This earlier rename always fails in Connect deployments because the R library and cache are beneath separate filesystems (mounts).

Comment thread R/cache.R

# back up a pre-existing cached package (restore on failure)
if (file.exists(cachedPackagePath)) {
if (!file.rename(cachedPackagePath, backupPackagePath)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note: This earlier rename is both good (removing a potentially old cache entry so it can be replaced with just-rebuilt package contents) and bad (many processes may see the need to build this package, separately call moveInstalledPackageToCache and move aside just-created cache entries).

Everything after the cache checks in installPkg leads to these races that we're resolving, because package-building and cache-population are expensive, which makes it more likely that multiple restore operations are doing that work at the same time.

Have you explored writing "burn in" tests of installPkg to see what other problems still linger? This early rename concerns me; maybe overwrite = TRUE is a mistake?

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.

3 participants