Skip to content
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

sync-from-archive does not consider ${archive_version}/${store_hash} subfolder if syncing from (local) archive directory #236

Open
snetramo opened this issue Oct 11, 2024 · 2 comments · May be fixed by #237

Comments

@snetramo
Copy link

Issue

When syncing from a local archive directory it seems like cabal-cache sync-from-archive does not sync libraries that are contained in the ${archive_version}/${store_hash} sub-folder even if the store hash for the store to sync to coincides with store_hash.

What and why?

Using cabal-cache in a CI/CD setup to avoid having to rebuild all dependencies, we encountered a number of cache-misses for dependencies that were contained in the (store-hash subfolder of the) archive. This did not make the builds fail. However, the corresponding dependencies had to be rebuilt which increased build times.

Notably, we encountered lines such as the following even though the package was contained in the archive and the store hashes coincide:

Not found: Local "cabal-cache/v2/ghc-9.4.8/colour-2.3.6-e74462c0ef939bc168bf448e20d49ad726c72b5868b56e583db62c85090062d2.tar.gz" :| [Local "cabal-cache/v2/fcfe93a36b/ghc-9.4.8/colour-2.3.6-e74462c0ef939bc168bf448e20d49ad726c72b5868b56e583db62c85090062d2.tar.gz"]
...
Failed to download: colour-2.3.6-e74462c0ef939bc168bf448e20d49ad726c72b5868b56e583db62c85090062d2
...

It was possible to reproduce the problem locally.

Steps to reproduce (locally)

  1. Create a cabal project (ideally small to avoid long build times) with at least one dependency whose build artifact contains absolute paths to the cabal store. For us (ghc-9.4.8) colour-2.3.6 was one such dependency.
  2. Set store path in cabal.project.local to some absolute path, e.g., store-dir: /tmp/.cabal-store/ (this step is optional but it helps to avoid messing with the default store).
  3. Build the project, respectively its dependencies: cabal build --dependencies-only
  4. Sync store to archive: cabal-cache sync-to-archive --archive-uri my-archive --store-path /tmp/.cabal-store.
  5. At this point a folder my-archive with the following sub-folder structure should have been created:
my-archive/${archive_version}/${compiler_id}/
my-archive/${archive_version}/${store_hash}/${compiler_id}/
  1. Delete content of /tmp/.cabal-store.
  2. Sync store from created archive: cabal-cache sync-from-archive --archive-uri my-archive --store-path /tmp/.cabal-store

Expected behavior

After syncing from the archive, the store should contain exactly the same library artifacts as before. Invoking cabal build --dependencies-only again should not rebuild anything.

Actual behavior

cabal-cache sync-from-archive encounters cache misses for all dependencies that are contained in the store_hash sub-folder of the cache:

Not found: Local "cabal-cache/v2/ghc-9.4.8/colour-2.3.6-e74462c0ef939bc168bf448e20d49ad726c72b5868b56e583db62c85090062d2.tar.gz" :| [Local "cabal-cache/v2/fcfe93a36b/ghc-9.4.8/colour-2.3.6-e74462c0ef939bc168bf448e20d49ad726c72b5868b56e583db62c85090062d2.tar.gz"]
...
Failed to download: colour-2.3.6-e74462c0ef939bc168bf448e20d49ad726c72b5868b56e583db62c85090062d2
...

Checking the store indicates that colour was not synced down and invoking cabal build --dependencies-only again yields:

Build profile: -w ghc-9.4.8 -O1
In order, the following will be built (use -v for more details):
 - alex-3.5.1.0 (exe:alex) (requires build)
 - byteable-0.1.1 (lib) (requires build)
 - colour-2.3.6 (lib) (requires build)
...

Suspects

It seems like the issue comes from HaskellWorks.CabalCache.IO.Lazy.readFirstAvailableResource. This function takes a non-empty list of the two possible locations to search for a library artifact. To do so it invokes HaskellWorks.CabalCache.IO.Lazy.readResource for the first location. This function throws NotFound if the artifact is located in the store_hash folder. In this case readFirstAvailableResource is not prepared to handle NotFound which means that the second location (which contains the store-hash) is never looked at.

Handling NotFound the same way as AwsError and HttpError are handled seems to fix the issue:

diff --git a/src/HaskellWorks/CabalCache/IO/Lazy.hs b/src/HaskellWorks/CabalCache/IO/Lazy.hs
index de4360c..13c7adf 100644
--- a/src/HaskellWorks/CabalCache/IO/Lazy.hs
+++ b/src/HaskellWorks/CabalCache/IO/Lazy.hs
@@ -105,6 +105,10 @@ readFirstAvailableResource :: ()
   -> ExceptT (OO.Variant e) m (LBS.ByteString, Location)
 readFirstAvailableResource envAws (a:|as) maxRetries = do
   (, a) <$> readResource envAws maxRetries a
+    & do OO.catch @NotFound \e -> do
+          case NEL.nonEmpty as of
+            Nothing -> OO.throwF (Identity e)
+            Just nas -> readFirstAvailableResource envAws nas maxRetries
     & do OO.catch @AwsError \e -> do
           case NEL.nonEmpty as of
             Nothing -> OO.throwF (Identity e)

After installing a local version that contains the change above, cabal-cache synced everything down from the archive as expected and no dependencies had to be rebuilt.

@snetramo
Copy link
Author

I was not sure about the PR-policies for this repo (and also about possible side-effects of the change suggested above) which is why I have not opened a PR. I am happy to do so if you agree that the described issue should be fixed and this is the way to do it 😃?

@newhoggy
Copy link
Member

Thank you for raising the issue.

Yes, we do accept PRs.

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 a pull request may close this issue.

2 participants