From: Jacob Keller <jacob.e.keller@intel.com>
To: Jeff King <peff@peff.net>, Taylor Blau <me@ttaylorr.com>
Cc: jean-christophe manciot <actionmystique@gmail.com>,
Patrick Steinhardt <ps@pks.im>, <git@vger.kernel.org>,
Derrick Stolee <stolee@gmail.com>
Subject: Re: unexpected auto-maintenance, was Re: git hogs the CPU, RAM and storage despite its config
Date: Mon, 11 May 2026 13:21:37 -0700 [thread overview]
Message-ID: <590781db-7c19-4aa8-8497-e16e5eb5eba1@intel.com> (raw)
In-Reply-To: <20260511200112.GA22912@coredump.intra.peff.net>
On 5/11/2026 1:01 PM, Jeff King wrote:
> On Sat, May 09, 2026 at 05:52:29PM -0400, Taylor Blau wrote:
>
>> Dropping and re-acquiring the lock is possible, but it's racy since
>> there is a gap during the critical window while we fork(). I believe
>> that the only airtight way to do this is to update the owner field of
>> the tempfiles we want to pass down during daemonization.
>
> It's racy in the sense that we might not get the lock back, but I don't
> think that's _wrong_ exactly. We just care that some instance runs the
> second half eventually.
>
> It is technically vulnerable to starvation, if processes keep taking the
> lock, doing the foreground jobs, and then losing the race to take it a
> second time. But besides the fact that you'd eventually win by chance,
> unless you have an infinite supply of processes coming along to run the
> foreground half, the final invocation will run the background tasks.
>
> I agree it feels pretty hacky. It is, however, how "git gc" seems to
> work (notice that it does a manual delete_tempfile() on the lock right
> before daemonizing).
>
>> (As an aside, do we want to do that for all tempfiles? It might be nice
>> to have a "->reassign_on_fork" flag or something on the tempfile struct
>> in case there are instances where the parent wants to retain ownership
>> of the tempfile after fork()-ing, but I can't think of any off the top
>> of my head. If we do introduce such a field, it should probably default
>> to "true" to avoid any foot-guns.)
>
> I think the answer is yes, we want it for all tempfiles. Because it is
> not a property of the individual tempfiles, but rather of the caller who
> is forking. The point of that owner field is so that when we spawn
> children, we don't end up with duplicate or unexpected cleanups.
>
> But daemonize() is not about spawning a child or otherwise splitting the
> process into two. There is still a single contiguous flow of execution,
> and the fact that we switch pids and call exit() is an implementation
> detail. So we would still want to remain the "owner" of any cleanup.
>
> And that is true for any other atexit() handlers besides the tempfile
> ones, too. If we called run_processes_parallel(), and then while that
> child was running we called daemonize(), we'd not want to kill the child
> then, but our atexit handler would do so! But we don't tend to leave
> processes running while doing stuff like daemonize(), which is why we
> never bothered to implement an owner field there in the first place.
>
>>> Ultimately fixing the lock bug will solve that. Though if doing so is
>>> too complicated for a quick maint release, I'm tempted to say we should
>>> consider reverting 452b12c2e0 for a potential v2.54.1 (as there were a
>>> few other regression fixes so far, I assume we'll have one soon-ish).
>>
>> I think something like the following (untested) would do the trick:
>
> Yeah, that's what I was envisioning. Note that there's a small race
> here:
>
>> + switch ((pid = fork())) {
>> case 0:
>> + reassign_tempfile_ownership(ppid, getpid());
>> break;
>> case -1:
>> die_errno(_("fork failed"));
>> default:
>> + reassign_tempfile_ownership(ppid, pid);
>> exit(0);
>> }
>
> The parent is giving up ownership and the child is taking it. So there
> may be a moment where both claim ownership, or a moment where the parent
> has relinquished but the child has not yet taken. We're not going to
> call exit() in the middle there, but a signal could kill us.
>
> And then either:
>
> - If both claim ownership, it's mostly OK, because they'll both try to
> unlink() which is idempotent-ish. Though there is a bad sequence
> where we delete somebody _else's_ lock, like:
>
> 1. Parent forks, but has not yet reassigned.
>
> 2. Child calls reassign to take ownership. Now both have
> ownership.
>
> 3. Signal kills both parent and child, so they enter cleanup
> code.
>
> 4. One of them (let's say the parent) deletes the lockfile.
>
> 5. Some other unrelated process (let's call it "git other") takes
> the lock.
>
> 6. The child deletes the lockfile.
>
> And at that point "git other" thinks it holds the lock, but it
> doesn't. It's quite an unlikely sequence in practice, though, I'd
> think.
>
> - If neither claims ownership and a signal kills both, then nobody
> cleans up the lock and it is left in place. This is annoying, but
> also something that can happen occasionally anyway (kill -9, etc).
>
> I don't have an easy suggestion for making it more atomic, though. You
> could choose one or the other direction using some synchronization
> between the two (e.g., child reassigns only after parent signals over a
> pipe that it has relinquished), but it's all kind of ugly.
>
Ya, that seems really ugly. My first thought was some way to disable
signals temporarily, but I am guessing that either has no good way to do
it or would introduce even more issues. Plus there is always kill -9...
> So it may be reasonable to just shrug and assume it's unlikely enough
> not to worry about.
>
> -Peff
>
Yea, I agree that this might qualify as unlikely enough to not worry.
next prev parent reply other threads:[~2026-05-11 20:21 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-04 15:27 git hogs the CPU, RAM and storage despite its config jean-christophe manciot
2026-05-08 18:03 ` unexpected auto-maintenance, was " Jeff King
2026-05-09 15:13 ` Mikael Magnusson
2026-05-09 17:53 ` Jeff King
2026-05-09 17:52 ` Jeff King
2026-05-09 21:52 ` Taylor Blau
2026-05-10 16:08 ` Derrick Stolee
2026-05-10 20:00 ` Taylor Blau
2026-05-11 6:42 ` Patrick Steinhardt
2026-05-11 20:22 ` Jeff King
2026-05-11 20:10 ` Jeff King
2026-05-11 20:01 ` Jeff King
2026-05-11 20:21 ` Jacob Keller [this message]
2026-05-11 20:35 ` Jeff King
2026-05-11 23:58 ` Jacob Keller
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=590781db-7c19-4aa8-8497-e16e5eb5eba1@intel.com \
--to=jacob.e.keller@intel.com \
--cc=actionmystique@gmail.com \
--cc=git@vger.kernel.org \
--cc=me@ttaylorr.com \
--cc=peff@peff.net \
--cc=ps@pks.im \
--cc=stolee@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.