Skip to content

trustPackedRefsStat=after_open does not work reliably #271

@klingarkar

Description

@klingarkar

Version

6.10

Operating System

Linux/Unix

Bug description

On an NFS-backed repository shared by multiple hosts, a reading host can fail to observe updates to packed-refs made by a writing host, even when core.trustPackedRefsStat=after_open is configured.

RefDirectory's AFTER_OPEN refresh opens the packed-refs file itself to force the NFS client to refresh cached attributes before FileSnapshot.isModified() decides whether to re-read it. This is insufficient in the following scenario:

On NFS, updating packed-refs replaces the file by atomically swapping in a new inode under the same name. If the previous packed-refs is still held open on a host when the swap occurs, NFS preserves the old inode as a hidden .nfsXXXX entry instead of deleting it (silly-rename). In this state, a reading host can keep resolving/observing the stale inode, and opening+closing the packed-refs file does not reliably trigger an attribute refresh. As a result, isModified() compares against stale attributes, concludes nothing changed, and the reader skips re-reading, so refs written on the writing host go unnoticed.

Actual behavior

With AFTER_OPEN configured, after a writing host updates packed-refs, a reading host may not detect that packed-refs was updated and read stale refs.

Expected behavior

After a writing host updates packed-refs, a reading host with AFTER_OPEN configured should always detect that packed-refs has changed and re-read it.

Relevant log output

Other information

This can possibly be reproduced by:

Reader

  1. Open a repo (with trustPackedRefsStat=after_open configured) via JGit RepositoryCache
  2. Start a reader thread which calls getRefDatabase().getRefs() in a loop
  3. Print any added/changed refs whenever a change in refs is detected

Writer

  1. Open the same repo's packed-refs file once in a thread and keep it open for the whole test run
  2. Repeat below for a few iterations:
  • Open the repo via RepositoryCache
  • Create a new commit
  • Generate couple of randomly named refs
  • Write both refs atomically using PackedBatchRefUpdate
  • Sleep 1-2s before the next iteration
  1. After all iterations, stop the packed-refs holder thread

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions