From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:42
*This patch series must be built on top of mh/reflife.*
This patch series fixes some races reading loose and packed refs.
Most of the problems, and some of the solutions, were pointed out by
Jeff King [1] but some other work was necessary to prevent his fixes
from causing problems elsewhere.
The basic race being addressed is that at any time "pack-refs --prune"
might be run at any time. It rewrites the packed-refs file and then
deletes the just-packed loose refs.
Readers, then, to get a self-consistent snapshot of references [2],
must be sure to read all of the loose references it will need *before*
reading the packed references (or at least verifying that the packed
references that it read earlier are still valid). But given the
lazy-loading of the loose references cache, this was not always the
case.
So the core of this patch series is to force loose references for an
iteration to be read all at once into the cache, and *then* to verify
that the packed-refs cache is up-to-date and if not to reload it.
Similarly, when looking up single references, a loose reference is
sought first, and then the validity of the packed-refs cache is
verified, and then the loose reference is sought in the cache.
The problem is that there was a lot of code that assumed that the
lifetime of the reference cache was essentially infinite. The
mh/reflife patch series (which has made it to next) fixed callers who
retained pointers to refnames in the cache.
The other problem was that the for_each_ref() functions will die if
the ref cache that they are iterating over is freed out from under
them. This problem is solved by using reference counts to avoid
freeing the old packed ref cache (even if it is no longer valid) until
all users are done with it.
Once those are done, it is possible to invalidate the packed refs
cache when needed. So (1) we always read all loose references that
will be needed in an iteration before the iteration starts, and (2) we
add a check (based on file metadata) whenever the packed-refs cache is
accessed that it is still up-to-date WRT the packed-refs file, and if
not reread it (but leave the old copy in memory as long as its
refcount is nonzero).
Along the way, this patch series adds simple transactions around the
packed-refs file/cache. The transaction interface is public. I think
this is a step in a good direction, because other race conditions not
addressed by this patch series are likely to require transactions
across the whole reference namespace to be made 100% reliable.
As a stress test, the test suite can be run with a simulated
"hyperactive repository" in which the packed-refs file is made to look
like it changes every time it is checked (except when its lock is
held):
------------------------------------ refs.c ------------------------------------
@@ -1075,8 +1075,8 @@ static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs)
else
packed_refs_file = git_path("packed-refs");
- if (refs->packed &&
- !stat_validity_check(&refs->packed->validity, packed_refs_file))
+ if (refs->packed && !refs->packed->lock
+ /*!stat_validity_check(&refs->packed->validity, packed_refs_file)*/)
clear_packed_ref_cache(refs);
if (!refs->packed) {
It passes the stress test.
[1] http://thread.gmane.org/gmane.comp.version-control.git/223299/focus=223526
Jeff King (2):
get_packed_ref_cache: reload packed-refs file when it changes
for_each_ref: load all loose refs before packed refs
Michael Haggerty (10):
repack_without_ref(): split list curation and entry writing
pack_refs(): split creation of packed refs and entry writing
refs: wrap the packed refs cache in a level of indirection
refs: implement simple transactions for the packed-refs file
refs: manage lifetime of packed refs cache via reference counting
do_for_each_entry(): increment the packed refs cache refcount
packed_ref_cache: increment refcount when locked
Extract a struct stat_data from cache_entry
add a stat_validity struct
refs: do not invalidate the packed-refs cache unnecessarily
builtin/clone.c | 7 +-
builtin/ls-files.c | 12 ++-
cache.h | 60 +++++++++--
read-cache.c | 181 +++++++++++++++++++------------
refs.c | 308 ++++++++++++++++++++++++++++++++++++++++++++---------
refs.h | 27 ++++-
6 files changed, 464 insertions(+), 131 deletions(-)
--
1.8.3
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:42
Split repack_without_ref() into multiple passes:
* collect the list of refnames that should be deleted from packed_refs
* delete those refnames from the cache
* write the remainder to the packed-refs file
The purpose of this change is to make the "write the remainder" part
reusable.
Signed-off-by: Michael Haggerty <redacted>
---
refs.c | 48 +++++++++++++++++++++++++++++++++++++-----------
1 file changed, 37 insertions(+), 11 deletions(-)
@@ -1994,6 +1994,23 @@ static void write_packed_entry(int fd, char *refname, unsigned char *sha1,}}+/*+*Aneach_ref_entry_fnthatwritestheentrytoapacked-refsfile.+*/+staticintwrite_packed_entry_fn(structref_entry*entry,void*cb_data)+{+int*fd=cb_data;+enumpeel_statuspeel_status=peel_entry(entry,0);++if(peel_status!=PEEL_PEELED&&peel_status!=PEEL_NON_TAG)+error("internal error: %s is not a valid packed reference!",+entry->name);+write_packed_entry(*fd,entry->name,entry->u.value.sha1,+peel_status==PEEL_PEELED?+entry->u.value.peeled:NULL);+return0;+}+structref_to_prune{structref_to_prune*next;unsignedcharsha1[20];
@@ -2113,14 +2130,18 @@ int pack_refs(unsigned int flags)return0;}-staticintrepack_ref_fn(structref_entry*entry,void*cb_data)+/*+*Ifentryshouldbedeletedfrompacked-refs,addittothestring+*listpointedtobycb_data.+*/+staticintcurate_packed_ref_fn(structref_entry*entry,void*cb_data){-int*fd=cb_data;-enumpeel_statuspeel_status;+structstring_list*refs_to_delete=cb_data;if(entry->flag&REF_ISBROKEN){/* This shouldn't happen to packed refs. */error("%s is broken!",entry->name);+string_list_append(refs_to_delete,entry->name);return0;}if(!has_sha1_file(entry->u.value.sha1)){
@@ -2130,7 +2151,7 @@ static int repack_ref_fn(struct ref_entry *entry, void *cb_data)if(read_ref_full(entry->name,sha1,0,&flags))/* We should at least have found the packed ref. */die("Internal error");-if((flags&REF_ISSYMREF)||!(flags&REF_ISPACKED))+if((flags&REF_ISSYMREF)||!(flags&REF_ISPACKED)){/**Thispackedreferenceisoverriddenbya*loosereference,soitisOKthatitsvalue
@@ -2150,14 +2173,10 @@ static int repack_ref_fn(struct ref_entry *entry, void *cb_data)*theoutput.*/error("%s does not point to a valid object!",entry->name);+string_list_append(refs_to_delete,entry->name);return0;}-peel_status=peel_entry(entry,0);-write_packed_entry(*fd,entry->name,entry->u.value.sha1,-peel_status==PEEL_PEELED?-entry->u.value.peeled:NULL);-return0;}
@@ -2165,6 +2184,8 @@ static int repack_without_ref(const char *refname){intfd;structref_dir*packed;+structstring_listrefs_to_delete=STRING_LIST_INIT_DUP;+structstring_list_item*ref_to_delete;if(!get_packed_ref(refname))return0;/* refname does not exist in packed refs */
@@ -2185,8 +2206,13 @@ static int repack_without_ref(const char *refname)rollback_lock_file(&packlock);return0;}+do_for_each_entry_in_dir(packed,0,curate_packed_ref_fn,&refs_to_delete);+for_each_string_list_item(ref_to_delete,&refs_to_delete){+if(remove_entry(packed,ref_to_delete->string)==-1)+die("internal error");+}write_or_die(fd,PACKED_REFS_HEADER,strlen(PACKED_REFS_HEADER));-do_for_each_entry_in_dir(packed,0,repack_ref_fn,&fd);+do_for_each_entry_in_dir(packed,0,write_packed_entry_fn,&fd);returncommit_lock_file(&packlock);}
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:42
As we know, we can solve any problem in this manner. In this case,
the problem is to avoid freeing a packed refs cache while somebody is
using it. So add a level of indirection as a prelude to
reference-counting the packed refs cache.
Signed-off-by: Michael Haggerty <redacted>
---
refs.c | 32 ++++++++++++++++++++++++++------
1 file changed, 26 insertions(+), 6 deletions(-)
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:42
Split pack_refs() into multiple passes:
* Iterate over loose refs. For each one that can be turned into a
packed ref, create a corresponding entry in the packed refs cache.
* Write the packed refs to the packed-refs file.
This change isolates the mutation of the packed-refs file to a single
place.
Signed-off-by: Michael Haggerty <redacted>
---
refs.c | 48 ++++++++++++++++++++++++++++++++++--------------
1 file changed, 34 insertions(+), 14 deletions(-)
@@ -2019,35 +2019,50 @@ struct ref_to_prune {structpack_refs_cb_data{unsignedintflags;+structref_dir*packed_refs;structref_to_prune*ref_to_prune;-intfd;};-staticintpack_one_ref(structref_entry*entry,void*cb_data)+/*+*Aneach_ref_entry_fnthatisrunoverloosereferencesonly.If+*theloosereferencecanbepacked,addanentryinthepackedref+*cache.Ifthereferenceshouldbepruned,alsoadditto+*ref_to_pruneinthepack_refs_cb_data.+*/+staticintpack_if_possible_fn(structref_entry*entry,void*cb_data){structpack_refs_cb_data*cb=cb_data;enumpeel_statuspeel_status;+structref_entry*packed_entry;intis_tag_ref=!prefixcmp(entry->name,"refs/tags/");-/* ALWAYS pack refs that were already packed or are tags */-if(!(cb->flags&PACK_REFS_ALL)&&!is_tag_ref&&-!(entry->flag&REF_ISPACKED))+/* ALWAYS pack tags */+if(!(cb->flags&PACK_REFS_ALL)&&!is_tag_ref)return0;/* Do not pack symbolic or broken refs: */if((entry->flag&REF_ISSYMREF)||!ref_resolves_to_object(entry))return0;+/* Add a packed ref cache entry equivalent to the loose entry. */peel_status=peel_entry(entry,1);if(peel_status!=PEEL_PEELED&&peel_status!=PEEL_NON_TAG)die("internal error peeling reference %s (%s)",entry->name,sha1_to_hex(entry->u.value.sha1));-write_packed_entry(cb->fd,entry->name,entry->u.value.sha1,-peel_status==PEEL_PEELED?-entry->u.value.peeled:NULL);+packed_entry=find_ref(cb->packed_refs,entry->name);+if(packed_entry){+/* Overwrite existing packed entry with info from loose entry */+packed_entry->flag=REF_ISPACKED|REF_KNOWS_PEELED;+hashcpy(packed_entry->u.value.sha1,entry->u.value.sha1);+}else{+packed_entry=create_ref_entry(entry->name,entry->u.value.sha1,+REF_ISPACKED|REF_KNOWS_PEELED,0);+add_ref(cb->packed_refs,packed_entry);+}+hashcpy(packed_entry->u.value.peeled,entry->u.value.peeled);-/* If the ref was already packed, there is no need to prune it. */-if((cb->flags&PACK_REFS_PRUNE)&&!(entry->flag&REF_ISPACKED)){+/* Schedule the loose reference for pruning if requested. */+if((cb->flags&PACK_REFS_PRUNE)){intnamelen=strlen(entry->name)+1;structref_to_prune*n=xcalloc(1,sizeof(*n)+namelen);hashcpy(n->sha1,entry->u.value.sha1);
@@ -2114,16 +2129,21 @@ static struct lock_file packlock;intpack_refs(unsignedintflags){structpack_refs_cb_datacbdata;+intfd;memset(&cbdata,0,sizeof(cbdata));cbdata.flags=flags;-cbdata.fd=hold_lock_file_for_update(&packlock,git_path("packed-refs"),-LOCK_DIE_ON_ERROR);+fd=hold_lock_file_for_update(&packlock,git_path("packed-refs"),+LOCK_DIE_ON_ERROR);+cbdata.packed_refs=get_packed_refs(&ref_cache);-write_or_die(cbdata.fd,PACKED_REFS_HEADER,strlen(PACKED_REFS_HEADER));+do_for_each_entry_in_dir(get_loose_refs(&ref_cache),0,+pack_if_possible_fn,&cbdata);++write_or_die(fd,PACKED_REFS_HEADER,strlen(PACKED_REFS_HEADER));+do_for_each_entry_in_dir(cbdata.packed_refs,0,write_packed_entry_fn,&fd);-do_for_each_entry(&ref_cache,"",pack_one_ref,&cbdata);if(commit_lock_file(&packlock)<0)die_errno("unable to overwrite old ref-pack file");prune_refs(cbdata.ref_to_prune);
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:42
Handle simple transactions for the packed-refs file at the
packed_ref_cache level via new functions lock_packed_refs(),
commit_packed_refs(), and rollback_packed_refs().
Only allow the packed ref cache to be modified (via add_packed_ref())
while the packed refs file is locked.
Change clone to add the new references within a transaction.
Signed-off-by: Michael Haggerty <redacted>
---
The API docs are not clear about whether it is kosher to read
lock_file::fd directly. It is only done in one file outside of
lockfile.c. So this patch stores the fd of the lockfile separately in
struct packed_ref_cache, even though the same struct also has a
pointer to the struct lock_file.
So please let me know if it is OK to read lock_file::fd directly. If
so, then I will drop the fd member of struct packed_ref_cache, as well
as the local variable "fd" in lock_packed_refs().
builtin/clone.c | 7 ++++-
refs.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++----------
refs.h | 27 +++++++++++++++--
3 files changed, 106 insertions(+), 19 deletions(-)
@@ -804,6 +804,16 @@ static int is_refname_available(const char *refname, const char *oldrefname,structpacked_ref_cache{structref_entry*root;++/*+*Iffthepacked-refsfileassociatedwiththisinstanceis+*currentlylockedforwriting,thispointsattheassociated+*lock(whichisownedbysomebodyelse).+*/+structlock_file*lock;++/* If locked, the file descriptor of the lock file. */+intfd;};/*
@@ -2031,6 +2049,56 @@ static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)return0;}+intlock_packed_refs(structlock_file*lock,intflags)+{+intfd;+structpacked_ref_cache*packed_ref_cache;++/* Discard the old cache because it might be invalid: */+clear_packed_ref_cache(&ref_cache);+fd=hold_lock_file_for_update(lock,git_path("packed-refs"),flags);+if(fd<0)+return-1;+/* Read the current packed-refs while holding the lock: */+packed_ref_cache=get_packed_ref_cache(&ref_cache);+packed_ref_cache->lock=lock;+packed_ref_cache->fd=fd;+return0;+}++intcommit_packed_refs(void)+{+structpacked_ref_cache*packed_ref_cache=+get_packed_ref_cache(&ref_cache);+intfd=packed_ref_cache->fd;+interror=0;++if(!packed_ref_cache->lock)+die("internal error: packed-refs not locked");+write_or_die(fd,PACKED_REFS_HEADER,strlen(PACKED_REFS_HEADER));++do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache),+0,write_packed_entry_fn,&fd);+if(commit_lock_file(packed_ref_cache->lock))+error=-1;+packed_ref_cache->lock=NULL;+packed_ref_cache->fd=-1;+returnerror;+}++voidrollback_packed_refs(void)+{+structpacked_ref_cache*packed_ref_cache=+get_packed_ref_cache(&ref_cache);++if(!packed_ref_cache->lock)+die("internal error: packed-refs not locked");+rollback_lock_file(packed_ref_cache->lock);+packed_ref_cache->lock=NULL;+packed_ref_cache->fd=-1;+clear_packed_ref_cache(&ref_cache);+}+structref_to_prune{structref_to_prune*next;unsignedcharsha1[20];
@@ -2149,23 +2217,19 @@ static struct lock_file packlock;intpack_refs(unsignedintflags){structpack_refs_cb_datacbdata;-intfd;memset(&cbdata,0,sizeof(cbdata));cbdata.flags=flags;-fd=hold_lock_file_for_update(&packlock,git_path("packed-refs"),-LOCK_DIE_ON_ERROR);+lock_packed_refs(&packlock,LOCK_DIE_ON_ERROR);cbdata.packed_refs=get_packed_refs(&ref_cache);do_for_each_entry_in_dir(get_loose_refs(&ref_cache),0,pack_if_possible_fn,&cbdata);-write_or_die(fd,PACKED_REFS_HEADER,strlen(PACKED_REFS_HEADER));-do_for_each_entry_in_dir(cbdata.packed_refs,0,write_packed_entry_fn,&fd);--if(commit_lock_file(&packlock)<0)+if(commit_packed_refs())die_errno("unable to overwrite old ref-pack file");+prune_refs(cbdata.ref_to_prune);return0;}
@@ -2230,12 +2293,10 @@ static int repack_without_ref(const char *refname)if(!get_packed_ref(refname))return0;/* refname does not exist in packed refs */-fd=hold_lock_file_for_update(&packlock,git_path("packed-refs"),0);-if(fd<0){+if(lock_packed_refs(&packlock,0)){unable_to_lock_error(git_path("packed-refs"),errno);returnerror("cannot delete '%s' from packed refs",refname);}-clear_packed_ref_cache(&ref_cache);packed=get_packed_refs(&ref_cache);/* Remove refname from the cache. */if(remove_entry(packed,refname)==-1){
@@ -2243,7 +2304,7 @@ static int repack_without_ref(const char *refname)*Thepackedentrydisappearedwhilewewere*acquiringthelock.*/-rollback_lock_file(&packlock);+rollback_packed_refs();return0;}do_for_each_entry_in_dir(packed,0,curate_packed_ref_fn,&refs_to_delete);
@@ -2251,9 +2312,7 @@ static int repack_without_ref(const char *refname)if(remove_entry(packed,ref_to_delete->string)==-1)die("internal error");}-write_or_die(fd,PACKED_REFS_HEADER,strlen(PACKED_REFS_HEADER));-do_for_each_entry_in_dir(packed,0,write_packed_entry_fn,&fd);-returncommit_lock_file(&packlock);+returncommit_packed_refs();}intdelete_ref(constchar*refname,constunsignedchar*sha1,intdelopt)
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:42
Increment the packed_ref_cache reference count while it is locked to
prevent its being freed.
Signed-off-by: Michael Haggerty <redacted>
---
refs.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
@@ -2099,6 +2101,8 @@ int lock_packed_refs(struct lock_file *lock, int flags)packed_ref_cache=get_packed_ref_cache(&ref_cache);packed_ref_cache->lock=lock;packed_ref_cache->fd=fd;+/* Increment the reference count to prevent it from being freed: */+acquire_packed_ref_cache(packed_ref_cache);return0;}
@@ -2119,6 +2123,7 @@ int commit_packed_refs(void)error=-1;packed_ref_cache->lock=NULL;packed_ref_cache->fd=-1;+release_packed_ref_cache(packed_ref_cache);returnerror;}
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:42
It can sometimes be useful to know whether a path in the
filesystem has been updated without going to the work of
opening and re-reading its content. We trust the stat()
information on disk already to handle index updates, and we
can use the same trick here.
This patch introduces a "stat_validity" struct which
encapsulates the concept of checking the stat-freshness of a
file. It is implemented on top of "struct stat_data" to
reuse the logic about which stat entries to trust for a
particular platform, but hides the complexity behind two
simple functions: check and update.
Signed-off-by: Michael Haggerty <redacted>
---
This is *very* similar to a patch by Jeff King [off-list ref] [1],
except that it is based on the struct stat_data that I extracted from
cache_entry rather than using cache_entries directly. I would have
left Peff the author except that I don't want to risk putting him on
the hook for any mistakes that I might have made. But if it is
appropriate, don't hesitate to make him author again.
[1] http://article.gmane.org/gmane.comp.version-control.git/223526
cache.h | 27 +++++++++++++++++++++++++++
read-cache.c | 30 ++++++++++++++++++++++++++++++
2 files changed, 57 insertions(+)
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:42
Now that we keep track of the packed-refs file metadata, we can detect
when the packed-refs file has been modified since we last read it, and
we do so automatically every time that get_packed_ref_cache() is
called. So there is no need to invalidate the cache automatically
when lock_packed_refs() is called; usually the old copy will still be
valid.
Signed-off-by: Michael Haggerty <redacted>
---
This patch is optional. It makes the assumption that the metadata
stored in stat_validity are adequate to reliably detect when the
packed-refs file has changed. Given that we are about to rewrite the
file, it is perhaps even more crucial not to make a mistake in this
codepath than in others. So if the stat_validity check is not
considered safe enough, it might be prudent to omit this patch and
continue to reload the packed-refs data here unconditionally.
refs.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
@@ -2134,12 +2134,15 @@ int lock_packed_refs(struct lock_file *lock, int flags)intfd;structpacked_ref_cache*packed_ref_cache;-/* Discard the old cache because it might be invalid: */-clear_packed_ref_cache(&ref_cache);fd=hold_lock_file_for_update(lock,git_path("packed-refs"),flags);if(fd<0)return-1;-/* Read the current packed-refs while holding the lock: */+/*+*Getthecurrentpacked-refswhileholdingthelock.Ifthe+*packed-refsfilehasbeenmodifiedsincewelastreadit,+*thiswillautomaticallyinvalidatethecacheandre-read+*thepacked-refsfile.+*/packed_ref_cache=get_packed_ref_cache(&ref_cache);packed_ref_cache->lock=lock;packed_ref_cache->fd=fd;
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:42
Add public functions fill_stat_data() and match_stat_data() to work
with it. This infrastructure will later be used to check the validity
of other types of file.
Signed-off-by: Michael Haggerty <redacted>
---
I'm not too familiar with this part of the code, so please make sure
that I've put the dividing line at the right place.
builtin/ls-files.c | 12 +++--
cache.h | 33 +++++++++---
read-cache.c | 151 +++++++++++++++++++++++++++++------------------------
3 files changed, 116 insertions(+), 80 deletions(-)
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:42
From: Jeff King <redacted>
If we are iterating through the refs using for_each_ref (or
any of its sister functions), we can get into a race
condition with a simultaneous "pack-refs --prune" that looks
like this:
0. We have a large number of loose refs, and a few packed
refs. refs/heads/z/foo is loose, with no matching entry
in the packed-refs file.
1. Process A starts iterating through the refs. It loads
the packed-refs file from disk, then starts lazily
traversing through the loose ref directories.
2. Process B, running "pack-refs --prune", writes out the
new packed-refs file. It then deletes the newly packed
refs, including refs/heads/z/foo.
3. Meanwhile, process A has finally gotten to
refs/heads/z (it traverses alphabetically). It
descends, but finds nothing there. It checks its
cached view of the packed-refs file, but it does not
mention anything in "refs/heads/z/" at all (it predates
the new file written by B in step 2).
The traversal completes successfully without mentioning
refs/heads/z/foo at all (the name, of course, isn't
important; but the more refs you have and the farther down
the alphabetical list a ref is, the more likely it is to hit
the race). If refs/heads/z/foo did exist in the packed refs
file at state 0, we would see an entry for it, but it would
show whatever sha1 the ref had the last time it was packed
(which could be an arbitrarily long time ago).
This can be especially dangerous when process A is "git
prune", as it means our set of reachable tips will be
incomplete, and we may erroneously prune objects reachable
from that tip (the same thing can happen if "repack -ad" is
used, as it simply drops unreachable objects that are
packed).
This patch solves it by loading all of the loose refs for
our traversal into our in-memory cache, and then refreshing
the packed-refs cache. Because a pack-refs writer will
always put the new packed-refs file into place before
starting the prune, we know that any loose refs we fail to
see will either truly be missing, or will have already been
put in the packed-refs file by the time we refresh.
Signed-off-by: Michael Haggerty <redacted>
---
Ditto.
refs.c | 39 +++++++++++++++++++++++++++++++++++----
1 file changed, 35 insertions(+), 4 deletions(-)
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:42
In struct packed_ref_cache, keep a count of the number of users of the
data structure. Only free the packed ref cache when the reference
count goes to zero rather than when the packed ref cache is cleared.
This mechanism will be used to prevent the cache data structure from
being freed while it is being iterated over.
So far, only the reference in struct ref_cache::packed is counted;
other users will be adjusted in separate commits.
Signed-off-by: Michael Haggerty <redacted>
---
refs.c | 39 ++++++++++++++++++++++++++++++++++++---
1 file changed, 36 insertions(+), 3 deletions(-)
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:42
From: Jeff King <redacted>
Once we read the packed-refs file into memory, we cache it
to save work on future ref lookups. However, our cache may
be out of date with respect to what is on disk if another
process is simultaneously packing the refs. Normally it
is acceptable for us to be a little out of date, since there
is no guarantee whether we read the file before or after the
simultaneous update. However, there is an important special
case: our packed-refs file must be up to date with respect
to any loose refs we read. Otherwise, we risk the following
race condition:
0. There exists a loose ref refs/heads/master.
1. Process A starts and looks up the ref "master". It
first checks $GIT_DIR/master, which does not exist. It
then loads (and caches) the packed-refs file to see if
"master" exists in it, which it does not.
2. Meanwhile, process B runs "pack-refs --all --prune". It
creates a new packed-refs file which contains
refs/heads/master, and removes the loose copy at
$GIT_DIR/refs/heads/master.
3. Process A continues its lookup, and eventually tries
$GIT_DIR/refs/heads/master. It sees that the loose ref
is missing, and falls back to the packed-refs file. But
it examines its cached version, which does not have
refs/heads/master. After trying a few other prefixes,
it reports master as a non-existent ref.
There are many variants (e.g., step 1 may involve process A
looking up another ref entirely, so even a fully qualified
refname can fail). One of the most interesting ones is if
"refs/heads/master" is already packed. In that case process
A will not see it as missing, but rather will report
whatever value happened to be in the packed-refs file before
process B repacked (which might be an arbitrarily old
value).
We can fix this by making sure we reload the packed-refs
file from disk after looking at any loose refs. That's
unacceptably slow, so we can check its stat()-validity as a
proxy, and read it only when it appears to have changed.
Reading the packed-refs file after performing any loose-ref
system calls is sufficient because we know the ordering of
the pack-refs process: it always makes sure the newly
written packed-refs file is installed into place before
pruning any loose refs. As long as those operations by B
appear in their executed order to process A, by the time A
sees the missing loose ref, the new packed-refs file must be
in place.
Signed-off-by: Michael Haggerty <redacted>
---
This is Peff's work, rebased and with some smallish changes to fit it
in with the packed_ref_cache data structure.
refs.c | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
@@ -824,6 +824,9 @@ struct packed_ref_cache {/* If locked, the file descriptor of the lock file. */intfd;++/* The metadata from when this packed-refs cache was read */+structstat_validityvalidity;};/*
@@ -858,6 +861,7 @@ static int release_packed_ref_cache(struct packed_ref_cache *packed_refs){if(!--packed_refs->referrers){free_ref_entry(packed_refs->root);+stat_validity_clear(&packed_refs->validity);free(packed_refs);return1;}else{
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:42
This function calls a user-supplied callback function which could do
something that causes the packed refs cache to be invalidated. So
acquire a reference count on the data structure to prevent our copy
from being freed while we are iterating over it.
Signed-off-by: Michael Haggerty <redacted>
---
refs.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
From: Jeff King <hidden> Date: 2016-06-15 22:57:42
On Tue, Jun 11, 2013 at 11:48:21PM +0200, Michael Haggerty wrote:
Split repack_without_ref() into multiple passes:
* collect the list of refnames that should be deleted from packed_refs
* delete those refnames from the cache
* write the remainder to the packed-refs file
The purpose of this change is to make the "write the remainder" part
reusable.
It took me several reads to figure out what was going on here, because I
did not see the deleted ref passed to the list of items to delete from
packed_refs. The part I was missing is something like:
The repack_without_ref() function first removes the deleted ref from
the internal packed-refs list, then writes the packed-refs list to
disk, omitting any broken or stale entries. This patch splits that
second step into multiple passes:
...
Is that accurate?
-Peff
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:42
On 06/12/2013 01:38 PM, Jeff King wrote:
On Tue, Jun 11, 2013 at 11:48:21PM +0200, Michael Haggerty wrote:
quoted
Split repack_without_ref() into multiple passes:
* collect the list of refnames that should be deleted from packed_refs
* delete those refnames from the cache
* write the remainder to the packed-refs file
The purpose of this change is to make the "write the remainder" part
reusable.
It took me several reads to figure out what was going on here, because I
did not see the deleted ref passed to the list of items to delete from
packed_refs. The part I was missing is something like:
The repack_without_ref() function first removes the deleted ref from
the internal packed-refs list, then writes the packed-refs list to
disk, omitting any broken or stale entries. This patch splits that
second step into multiple passes:
...
Is that accurate?
Yes. You are right that I should make that clearer.
Thanks,
Michael
--
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/
From: Jeff King <hidden> Date: 2016-06-15 22:57:42
On Tue, Jun 11, 2013 at 11:48:24PM +0200, Michael Haggerty wrote:
The API docs are not clear about whether it is kosher to read
lock_file::fd directly. It is only done in one file outside of
lockfile.c. So this patch stores the fd of the lockfile separately in
struct packed_ref_cache, even though the same struct also has a
pointer to the struct lock_file.
So please let me know if it is OK to read lock_file::fd directly. If
so, then I will drop the fd member of struct packed_ref_cache, as well
as the local variable "fd" in lock_packed_refs().
I think it's fine; the fact that you have such an fd is a public part of
the interface, so you are only relying on the struct member being there.
And since the lock_file must hold the fd itself somewhere, I don't think
that's unreasonable.
I'm not sure how you got your "in one file" list, but it appears to
happen in credential-store.c, bundle.c, fast-import.c, and read-cache.c.
-Peff
From: Jeff King <hidden> Date: 2016-06-15 22:57:42
On Tue, Jun 11, 2013 at 11:48:32PM +0200, Michael Haggerty wrote:
Now that we keep track of the packed-refs file metadata, we can detect
when the packed-refs file has been modified since we last read it, and
we do so automatically every time that get_packed_ref_cache() is
called. So there is no need to invalidate the cache automatically
when lock_packed_refs() is called; usually the old copy will still be
valid.
Signed-off-by: Michael Haggerty <redacted>
---
This patch is optional. It makes the assumption that the metadata
stored in stat_validity are adequate to reliably detect when the
packed-refs file has changed. Given that we are about to rewrite the
file, it is perhaps even more crucial not to make a mistake in this
codepath than in others. So if the stat_validity check is not
considered safe enough, it might be prudent to omit this patch and
continue to reload the packed-refs data here unconditionally.
I doubt that the risk is very high, but I'd also doubt that this
provides any useful performance improvement, because it is mostly
happening during a `git pack-refs` call. The exception is packing for a
ref deletion, and it could potentially speed up a receive-pack that was
deleting many refs.
I don't have a strong opinion either way.
-Peff
From: Jeff King <hidden> Date: 2016-06-15 22:57:42
On Tue, Jun 11, 2013 at 11:48:20PM +0200, Michael Haggerty wrote:
*This patch series must be built on top of mh/reflife.*
Applying on top of what Junio has in mh/reflife seems to create
conflicts at the first patch. I didn't look into it, though, but just
read the patches and looked at the packed-refs-transactions branch at
git://github.com/mhagger/git.git.
The other problem was that the for_each_ref() functions will die if
the ref cache that they are iterating over is freed out from under
them. This problem is solved by using reference counts to avoid
freeing the old packed ref cache (even if it is no longer valid) until
all users are done with it.
I was worried that the reference-counting would end up invasive and
ugly, but it turned out quite nice.
Overall, I think the series goes in the right direction, and I didn't
see any implementation flaws. The core of the race-handling is the same
as in mine, but your "hyperactive repository" patch shows how badly mine
can break under load.
I sent a few comments to specific patches, but I'd be fine with applying
it as-is. Thanks for working on it.
-Peff
From: Ramsay Jones <hidden> Date: 2016-06-15 22:57:46
Michael Haggerty wrote:
*This patch series must be built on top of mh/reflife.*
[...]
The other problem was that the for_each_ref() functions will die if
the ref cache that they are iterating over is freed out from under
them. This problem is solved by using reference counts to avoid
freeing the old packed ref cache (even if it is no longer valid) until
all users are done with it.
Yes, I found exactly this happened to me on cygwin, earlier this week,
with the previous version of this code. After seeing this mail, I had
decided not to describe the failure on the old version, but wait and
test this version instead.
This version is a great improvement, but it still has some failures on
cygwin. So, it may be worth (briefly) describing the old failure anyway!
Note that several tests failed, but I will only mention t3211-peel-ref.sh
tests #7-8.
$ pwd
/home/ramsay/git/t/trash directory.t3211-peel-ref
$
$ ../../bin-wrappers/git show-ref -d
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/heads/master
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/outside/foo
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/outside/foo^{}
5 [main] git 3540 _cygtls::handle_exceptions: Error while dumping state (p
robably corrupted stack)
Segmentation fault (core dumped)
$
The stack-trace for the faulting code looks something like:
cmd_show_ref()
for_each_ref(show_ref, NULL)
do_for_each_ref(&ref_cache, "", show_ref, 0, 0, NULL)
do_for_each_entry(&ref_cache, "", do_one_ref, &data)
do_for_each_entry_in_dirs(packed_dir, loose_dir, do_one_ref, &data)
*dfeeid() recursive calls*
do_one_ref(entry, &data)
show_ref("refs/outside/foo", sha1, NULL) [2nd match]
peel_ref("refs/outside/foo", sha1)
peel_entry(entry, 0)
peel_object(name, sha1)
deref_tag_noverify(o)
parse_object(sha1 <eb0e854c2...>)
lookup_replace_object(sha1)
do_lookup_replace_object(sha1)
prepare_replace_object()
[un-indent here!]
for_each_replace_ref(register_replace_ref, NULL)
do_for_each_ref(&ref_cache, "refs/replace", fn, 13, 0, NULL)
do_for_each_entry(&ref_cache, "refs/replace", fn, &data)
get_packed_refs(&ref_cache)
clear_packed_ref_cache(&ref_cache) *free_ref_entries etc*
** return to show_ref() [2nd match] above **
** return to recursive dfeeid() call in original iteration
** dir1->entries has been free()-ed and reused => segmentation fault
[dir1->entries == 0x64633263 => dc2c => part of sha1 for refs/outside/foo]
So, the nested "replace-reference-iteration" causes the ref_cache to be
freed out from under the initial show-ref iteration, so this works:
$ GIT_NO_REPLACE_OBJECTS=1 ../../bin-wrappers/git show-ref -d
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/heads/master
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/outside/foo
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/outside/foo^{}
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/tags/base
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/tags/foo
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/tags/foo^{}
$
You may be wondering why clear_packed_ref_cache() is called? Well, that
is because stat_validity_check() *incorrectly* indicates that the
packed-refs file has changed. Why does it do that? Well, this is one
more example of the problems caused by the cygwin schizophrenic stat()
functions. :( [ARGHHHHHHHHH]
At this point, I tried running 'git show-ref' with core.checkstat set
on the command line; but that didn't work! I had to fix show-ref and
re-build git, and then, this works:
$ ../../bin-wrappers/git -c core.checkstat=minimal -c core.trustctime=f
alse show-ref -d
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/heads/master
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/outside/foo
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/outside/foo^{}
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/tags/base
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/tags/foo
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/tags/foo^{}
$
Now, turning to the new code, t3211-peel-ref.sh test #7 now works, but
test #8 still fails...
$ ./t3211-peel-ref.sh -i -v
...
ok 7 - refs are peeled outside of refs/tags (old packed)
expecting success:
git pack-refs --all &&
cp .git/packed-refs fully-peeled &&
git branch yadda &&
git pack-refs --all &&
git branch -d yadda &&
test_cmp fully-peeled .git/packed-refs
fatal: internal error: packed-ref cache cleared while locked
not ok 8 - peeled refs survive deletion of packed ref
#
# git pack-refs --all &&
# cp .git/packed-refs fully-peeled &&
# git branch yadda &&
# git pack-refs --all &&
# git branch -d yadda &&
# test_cmp fully-peeled .git/packed-refs
#
$ cd trash\ directory.t3211-peel-ref/
$ ../../bin-wrappers/git pack-refs --all
fatal: internal error: packed-ref cache cleared while locked
$ ls
actual base.t expect
$ ls .git
COMMIT_EDITMSG branches/ description index logs/ packed-refs
HEAD config hooks-disabled/ info/ objects/ refs/
$ ls -l .git/packed-refs
-rw-r--r-- 1 ramsay None 296 Jun 14 20:34 .git/packed-refs
$ cat .git/packed-refs
# pack-refs with: peeled
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/heads/master
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/outside/foo
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/tags/base
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/tags/foo
^d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e
$
Now, I have a test-stat program which prints the difference between
the two stat implementations used in cygwin git, thus:
$ ../../test-stat .git/packed-refs
stat for '.git/packed-refs':
*dev: -1395862925, 0
*ino: 166044, 0
*mode: 100644 -rw-, 100600 -rw-
nlink: 1, 1
*uid: 1005, 0
*gid: 513, 0
*rdev: -1395862925, 0
size: 296, 296
atime: 1371238550, 1371238550 Fri Jun 14 20:35:50 2013
mtime: 1371238469, 1371238469 Fri Jun 14 20:34:29 2013
ctime: 1371238469, 1371238469 Fri Jun 14 20:34:29 2013
$ ../../bin-wrappers/git -c core.checkstat=minimal pack-refs --all
fatal: internal error: packed-ref cache cleared while locked
$
Hmmm, that should have worked! Wait, fix 'git pack-refs' to support
setting config variables on the command line, rebuild and:
$ ../../bin-wrappers/git -c core.checkstat=minimal pack-refs --all
$ cat .git/packed-refs
# pack-refs with: peeled fully-peeled
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/heads/master
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/outside/foo
^d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/tags/base
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/tags/foo
^d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e
$
I haven't checked the remaining test failures to see if they are
caused by this code (I don't think so, but ...), but this failure
is clearly a cygwin specific issue.
HTH
ATB,
Ramsay Jones
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:46
Thanks for all of the information.
On 06/15/2013 10:13 PM, Ramsay Jones wrote:
Michael Haggerty wrote:
quoted
*This patch series must be built on top of mh/reflife.*
[...]
quoted
The other problem was that the for_each_ref() functions will die if
the ref cache that they are iterating over is freed out from under
them. This problem is solved by using reference counts to avoid
freeing the old packed ref cache (even if it is no longer valid) until
all users are done with it.
Yes, I found exactly this happened to me on cygwin, earlier this week,
with the previous version of this code. After seeing this mail, I had
decided not to describe the failure on the old version, but wait and
test this version instead.
This version is a great improvement, but it still has some failures on
cygwin. So, it may be worth (briefly) describing the old failure anyway!
Note that several tests failed, but I will only mention t3211-peel-ref.sh
tests #7-8.
$ pwd
/home/ramsay/git/t/trash directory.t3211-peel-ref
$
$ ../../bin-wrappers/git show-ref -d
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/heads/master
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/outside/foo
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/outside/foo^{}
5 [main] git 3540 _cygtls::handle_exceptions: Error while dumping state (p
robably corrupted stack)
Segmentation fault (core dumped)
$
The stack-trace for the faulting code looks something like:
cmd_show_ref()
for_each_ref(show_ref, NULL)
do_for_each_ref(&ref_cache, "", show_ref, 0, 0, NULL)
do_for_each_entry(&ref_cache, "", do_one_ref, &data)
do_for_each_entry_in_dirs(packed_dir, loose_dir, do_one_ref, &data)
*dfeeid() recursive calls*
do_one_ref(entry, &data)
show_ref("refs/outside/foo", sha1, NULL) [2nd match]
peel_ref("refs/outside/foo", sha1)
peel_entry(entry, 0)
peel_object(name, sha1)
deref_tag_noverify(o)
parse_object(sha1 <eb0e854c2...>)
lookup_replace_object(sha1)
do_lookup_replace_object(sha1)
prepare_replace_object()
[un-indent here!]
for_each_replace_ref(register_replace_ref, NULL)
do_for_each_ref(&ref_cache, "refs/replace", fn, 13, 0, NULL)
do_for_each_entry(&ref_cache, "refs/replace", fn, &data)
get_packed_refs(&ref_cache)
clear_packed_ref_cache(&ref_cache) *free_ref_entries etc*
** return to show_ref() [2nd match] above **
** return to recursive dfeeid() call in original iteration
** dir1->entries has been free()-ed and reused => segmentation fault
[dir1->entries == 0x64633263 => dc2c => part of sha1 for refs/outside/foo]
So, the nested "replace-reference-iteration" causes the ref_cache to be
freed out from under the initial show-ref iteration, so this works:
$ GIT_NO_REPLACE_OBJECTS=1 ../../bin-wrappers/git show-ref -d
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/heads/master
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/outside/foo
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/outside/foo^{}
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/tags/base
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/tags/foo
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/tags/foo^{}
$
You may be wondering why clear_packed_ref_cache() is called? Well, that
is because stat_validity_check() *incorrectly* indicates that the
packed-refs file has changed. Why does it do that? Well, this is one
more example of the problems caused by the cygwin schizophrenic stat()
functions. :( [ARGHHHHHHHHH]
At this point, I tried running 'git show-ref' with core.checkstat set
on the command line; but that didn't work! I had to fix show-ref and
re-build git, and then, this works:
$ ../../bin-wrappers/git -c core.checkstat=minimal -c core.trustctime=f
alse show-ref -d
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/heads/master
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/outside/foo
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/outside/foo^{}
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/tags/base
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/tags/foo
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/tags/foo^{}
$
So if I understand correctly, all of the above is *without* the
refcounting changes introduced in mh/ref-races? Is so, then it is not
surprising, as this is exactly the sort of problem that the reference
counting is meant to solve.
Now, turning to the new code, t3211-peel-ref.sh test #7 now works, but
test #8 still fails...
$ ./t3211-peel-ref.sh -i -v
...
ok 7 - refs are peeled outside of refs/tags (old packed)
expecting success:
git pack-refs --all &&
cp .git/packed-refs fully-peeled &&
git branch yadda &&
git pack-refs --all &&
git branch -d yadda &&
test_cmp fully-peeled .git/packed-refs
fatal: internal error: packed-ref cache cleared while locked
not ok 8 - peeled refs survive deletion of packed ref
#
# git pack-refs --all &&
# cp .git/packed-refs fully-peeled &&
# git branch yadda &&
# git pack-refs --all &&
# git branch -d yadda &&
# test_cmp fully-peeled .git/packed-refs
#
$ cd trash\ directory.t3211-peel-ref/
$ ../../bin-wrappers/git pack-refs --all
fatal: internal error: packed-ref cache cleared while locked
These "internal error: packed-ref cache cleared while locked" failures
result from an internal consistency check that clear_packed_ref_cache()
is not called while the write lock is held on the packed-refs file. A
call to c_p_r_c() could result from
* a programming error
* a determination based on the packed-refs file stat that the file needs
to be re-read
Judging from what you said about cygwin, I assume that the latter is
happening. It should be impossible, because the current process is
holding packed-refs.lock, and therefore other git processes should
refuse to change the packed-refs file.
But if the stat information is not reliable, then the current process
would *think* that the packed-refs file has been changed even though it
hasn't, then it would call c_p_r_c() even though it holds the lock on
packed-refs, and the internal consistency check would fail.
So apparently in these cases cygwin is reporting that the packed-refs
stat information has changed (in the sense defined by the new
stat_validity_check() function, which does essentially the same checks
as the old ce_match_stat_basic() function).
$ ls
actual base.t expect
$ ls .git
COMMIT_EDITMSG branches/ description index logs/ packed-refs
HEAD config hooks-disabled/ info/ objects/ refs/
$ ls -l .git/packed-refs
-rw-r--r-- 1 ramsay None 296 Jun 14 20:34 .git/packed-refs
$ cat .git/packed-refs
# pack-refs with: peeled
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/heads/master
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/outside/foo
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/tags/base
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/tags/foo
^d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e
$
Now, I have a test-stat program which prints the difference between
the two stat implementations used in cygwin git, thus:
$ ../../test-stat .git/packed-refs
stat for '.git/packed-refs':
*dev: -1395862925, 0
*ino: 166044, 0
*mode: 100644 -rw-, 100600 -rw-
nlink: 1, 1
*uid: 1005, 0
*gid: 513, 0
*rdev: -1395862925, 0
size: 296, 296
atime: 1371238550, 1371238550 Fri Jun 14 20:35:50 2013
mtime: 1371238469, 1371238469 Fri Jun 14 20:34:29 2013
ctime: 1371238469, 1371238469 Fri Jun 14 20:34:29 2013
Yikes! ECYGWINFAIL. I have no doubt they have reason why they cannot
implement this correctly, but this is rather limiting. (I has assumed
that the #ifdef magic that was already in ce_match_stat_basic() would
have papered over the problems in stat, but I guess that is not the case.)
You say that there are two stat references in cygwin. Is there a way to
ensure that the same one is used in both cases? Or is it so hopelessly
broken that there is no point?
Or let me step back and pose a slightly more abstract question:
How, under cygwin, can we implement a quick check of whether a file
might have changed? The check should not have any false negatives
(claiming that a file is unchanged when actually it was rewritten via
the usual lock_file mechanism) nor should it have any "strong" false
positives (claiming that a file has changed even though it has never
been touched), though a "weak" false positive would be OK (claiming that
a file has changed even though it was replaced by a version with
identical contents).
If such a check is possible, then we should build it into the
implementation of match_stat_data(). If not, we have to think of
another way to implement the checks of packed-refs cache up-to-dateness.
(One horrible hack would be: when in doubt, read the packed-refs file
into a temporary ref_dir, then compare *the contents* to the version in
the cache. If they are the same, then discard the newly-read version,
update the stat_validity, and continue to use the old version. If they
are different, *then* verify that the lock file was not held, call
clear_packed_ref_cache(), and start using the new version.)
$ ../../bin-wrappers/git -c core.checkstat=minimal pack-refs --all
fatal: internal error: packed-ref cache cleared while locked
$
Hmmm, that should have worked! Wait, fix 'git pack-refs' to support
setting config variables on the command line, rebuild and:
$ ../../bin-wrappers/git -c core.checkstat=minimal pack-refs --all
$ cat .git/packed-refs
# pack-refs with: peeled fully-peeled
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/heads/master
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/outside/foo
^d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e
d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e refs/tags/base
eb0e854c2cd2511c7571b1a5e3c8b8146193fb30 refs/tags/foo
^d1ff1c9224ae5e58a7656fb9ecc95865d42ed71e
$
I haven't checked the remaining test failures to see if they are
caused by this code (I don't think so, but ...), but this failure
is clearly a cygwin specific issue.
From: Ramsay Jones <hidden> Date: 2016-06-15 22:57:49
Michael Haggerty wrote:
Thanks for all of the information.
On 06/15/2013 10:13 PM, Ramsay Jones wrote:
quoted
Michael Haggerty wrote:
quoted
*This patch series must be built on top of mh/reflife.*
[ ... ]
quoted
You may be wondering why clear_packed_ref_cache() is called? Well, that
is because stat_validity_check() *incorrectly* indicates that the
packed-refs file has changed. Why does it do that? Well, this is one
more example of the problems caused by the cygwin schizophrenic stat()
functions. :( [ARGHHHHHHHHH]
[ ... ]
So if I understand correctly, all of the above is *without* the
refcounting changes introduced in mh/ref-races? Is so, then it is not
surprising, as this is exactly the sort of problem that the reference
counting is meant to solve.
Yes, as I said, this describes the old (non refcounted) series.
This particular problem (the segmentation fault) is fixed by the new
series (as noted below). [Note, however, that the packed-refs file
will still be re-read more often than needed.]
quoted
Now, turning to the new code, t3211-peel-ref.sh test #7 now works, but
test #8 still fails...
[ ... ]
These "internal error: packed-ref cache cleared while locked" failures
result from an internal consistency check that clear_packed_ref_cache()
is not called while the write lock is held on the packed-refs file. A
call to c_p_r_c() could result from
* a programming error
* a determination based on the packed-refs file stat that the file needs
to be re-read
Judging from what you said about cygwin, I assume that the latter is
happening.
Indeed.
It should be impossible, because the current process is
holding packed-refs.lock, and therefore other git processes should
refuse to change the packed-refs file.
:-P You are assuming that a single process can't lie to itself ...
[ ... ]
Yikes! ECYGWINFAIL.
Ah, NO, this should read ECYGWINGITFAIL.
This is a self-inflicted wound; it has nothing much to do with cygwin.
I should not have assumed that you knew what I meant by "schizophrenic
stat() functions" above; sorry about that! If you are interested, then
the following commits may be useful reading: adbc0b6, 7faee6b, 7974843,
05bab3ea, 924aaf3e and b8a97333.
[ ... ]
quoted
I haven't checked the remaining test failures to see if they are
caused by this code (I don't think so, but ...), but this failure
is clearly a cygwin specific issue.
Thanks again for the testing and analysis,
So, unless you feel the need to fix this yourself, you can probably
ignore this issue for now. I will hopefully find time to fix it up
before this topic progresses to next. (Although I don't have any
feeling for the time-frame of this topic).
HTH
ATB,
Ramsay Jones
From: Michael Haggerty <hidden> Date: 2016-06-15 22:57:49
On 06/18/2013 08:13 PM, Ramsay Jones wrote:
Michael Haggerty wrote:
quoted
On 06/15/2013 10:13 PM, Ramsay Jones wrote:
quoted
Now, turning to the new code, t3211-peel-ref.sh test #7 now works, but
test #8 still fails...
[ ... ]
quoted
It should be impossible, because the current process is
holding packed-refs.lock, and therefore other git processes should
refuse to change the packed-refs file.
:-P You are assuming that a single process can't lie to itself ...
[ ... ]
I should not have assumed that you knew what I meant by "schizophrenic
stat() functions" above; sorry about that! If you are interested, then
the following commits may be useful reading: adbc0b6, 7faee6b, 7974843,
05bab3ea, 924aaf3e and b8a97333.
Thanks, that helps.
quoted
quoted
I haven't checked the remaining test failures to see if they are
caused by this code (I don't think so, but ...), but this failure
is clearly a cygwin specific issue.
Thanks again for the testing and analysis,
So, unless you feel the need to fix this yourself, you can probably
ignore this issue for now. I will hopefully find time to fix it up
before this topic progresses to next. (Although I don't have any
feeling for the time-frame of this topic).
Despite reading the commits that you referenced, I still don't feel
competent to fix this myself so I gratefully accept your offer.
Ideally, whatever complexity is needed would be hidden in the functions
stat_validity_check() and stat_validity_update() added by patch 09/12 of
my series, and possibly match_stat_data() from 08/12.
Let me know if I can help.
Michael
--
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/