git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH v2 2/2] midx: stop repeatedly looking up nonexistent packfiles

From
Jeff King <peff@peff.net>
Date
May 22, 2025, 16:59 UTC
Message-ID
<20250522165924.GA1613@coredump.intra.peff.net>
In-Reply-To
<xmqqbjrk7ikx.fsf@gitster.g>
On Thu, May 22, 2025 at 08:47:58AM -0700, Junio C Hamano wrote:
Show 6 quoted lines
> So the idea is to leave m->packs[] for unused pack NULL instead of
> magic value, so that users of that array do not need to check?
> 
> I think that is a lot safer than the magic "we know this fails"
> value that existing callers that have long trusted that a non-NULL
> .packs[] element is a valid pack.

Right. It would protect any further users of that assumption that we missed.

Show 12 quoted lines
> By the way, I suspect I am not reading the code correctly, but I am
> not sure what fill_midx_entry() does with a failed case.
> 
> 	midx_for_object(&m, pos);
> 	pack_int_id = nth_midxed_pack_int_id(m, pos);
> 
> 	if (prepare_midx_pack(r, m, pack_int_id))
> 		return 0;
> 
> With or without cached failure, this should return 0 when and only
> when m->packs[pack_int_id] is a usable pack.  But what about the
> access on the next line?

I think there's a subtlety here with incremental midx's, in that a pack id can be "global" within the whole midx chain, or a local index into a specific chain element's list of packs.

In fill_midx_entry(), I think we get such a global id back from nth_midxed_pack_int_id(). And then when we hand that to prepare_midx_pack(), it is converted into a local midx/id pair with:

  pack_int_id = midx_for_pack(&m, pack_int_id);
where the end of that function is the same:
  return pack_int_id - m->num_packs_in_base;

you saw elsewhere. So at that point we have a local midx and index, which is what prepare_midx_pack() fills via the m->packs[pack_int_id] field.

So when you say "only when m->packs[pack_int_id] is a usable pack", you are talking about the local m/pack_int_id within that function.

But back in the caller...
Show 6 quoted lines
> 	p = m->packs[pack_int_id - m->num_packs_in_base];
> 
> Do we have any guarantee that we called prepare_midx_pack() for 
> the pack at (pack_int_id - m->num_packs_in_base)th slot?  Can p
> be NULL here?  And with the magic "we know this fails" value, can p
> be that magic value?

Our pack_int_id is the global one, so it needs to be adjusted. But this pack pointer we access is the same one that was filled (or not) by prepare_midx_pack(). So it cannot be NULL or the magic "fails" value, because prepare_midx_pack() returned 0.

So I think this code is fine.

One thing that did puzzle me: in prepare_midx_pack() we not only adjust the pack_int_id, but we may walk back through the midx chain to find the correct multi_pack_index struct. Wouldn't the caller need to do the same?

The answer is that it does. The midx_for_object() call in fill_midx_entry() does that same walk, storing the result in its local "m" variable. So the walk backwards in prepare_midx_pack() is superfluous for this particular caller, who we know is already handing us the desired multi_pack_index struct, and it could just do:

  pack_int_id -= m->num_packs_in_base;

rather than calling midx_for_pack(). But the same is not necessarily true for other callers, so we should continue calling that function.

I suspect this would all be a bit more obvious if prepare_midx_pack() simply returned the pack pointer, avoiding the need for callers to look at m->packs at all (and making it a true cache, internally only to prepare_midx_pack()).

Looking at other callers of prepare_midx_pack():
  - in fill_packs_from_midx(), we do not adjust our "m" to match the
    index. But that is OK, because we adjust our local index (which we
    get by iterating from 0 to m->num_packs) to a global index when
    calling the function:
      if (prepare_midx_pack(ctx->repo, m, m->num_packs_in_base + i))
	...
      open_pack_index(m->packs[i]);
    which is fine.
  - I'm less sure of the call in expire_midx_packs(). It iterates over
    num_packs in the same way, but does:
      if (prepare_midx_pack(r, m, i))
              continue;
    and then looks at m->packs[i]. That would be wrong if "m" is not the
    first item in the chain. Ah, I see. Earlier we do:
      if (m->base_midx)
              die(_("cannot expire packs from an incremental multi-pack-index"));
    so we know that the global and local ids are equivalent in this
    instance (since the "base" midx . Still seems a bit fragile.
  - There's a similar case in midx-write.c:want_included_pack(). That
    one seems to have the same local/global confusion, but I do not
    obviously see anything preventing it from being fed a non-base midx.
    So it might possibly be buggy?
    Likewise fill_included_packs_batch() in the same file.
    In both cases I think if prepare_midx_pack() returned a pointer, we
    could just use it directly.
  - In nth_bitmapped_pack(), we call midx_for_pack() ourselves to get
    the right midx. That's good, but getting a pack pointer from
    prepare_midx_pack() wouldn't help us, because we still look at the
    midx struct for other reasons.

So I dunno. It might be a useful refactor, but it doesn't make the problem go away entirely. I'm suspicious of those calls in midx-write.c, but Taylor can probably say more about whether they're wrong or if there's some less-obvious reason we'd only see a base midx there.

-Peff
Previous: Junio C HamanoNext: Junio C Hamano
Message 15 of 53 in “packfile: avoid access(3p) calls for missing packs”
  1. packfile: avoid access(3p) calls for missing packsPatrick Steinhardt, May 16, 2025
  2. Junio C HamanoMay 16, 2025
  3. Jeff KingMay 19, 2025
  4. Junio C HamanoMay 19, 2025
  5. Patrick SteinhardtMay 20, 2025
  6. Jeff KingMay 22, 2025
  7. Taylor BlauMay 23, 2025
  8. Jeff KingMay 23, 2025
  9. 0/2 packfile: avoid access(3p) calls for missing packsPatrick Steinhardt, May 20, 2025
  10. 1/2 packfile: explain ordering of how we look up auxiliary pack filesPatrick Steinhardt, May 20, 2025
  11. Taylor BlauMay 23, 2025
  12. 2/2 midx: stop repeatedly looking up nonexistent packfilesPatrick Steinhardt, May 20, 2025
  13. Jeff KingMay 22, 2025
  14. Junio C HamanoMay 22, 2025
  15. Jeff KingMay 22, 2025
  16. Junio C HamanoMay 22, 2025
  17. Taylor BlauMay 23, 2025
  18. Jeff KingMay 23, 2025
  19. Taylor BlauMay 23, 2025
  20. 0/5 midx: improve prepare_midx_pack() ergonomicsTaylor Blau, May 25, 2025
  21. 1/5 pack-bitmap.c: fix broken warning() when missing MIDX'd packTaylor Blau, May 25, 2025
  22. Patrick SteinhardtMay 26, 2025
  23. Taylor BlauMay 28, 2025
  24. 2/5 midx-write.c: guard against incremental MIDXs in want_included_pack()Taylor Blau, May 25, 2025
  25. Patrick SteinhardtMay 26, 2025
  26. Taylor BlauMay 28, 2025
  27. 3/5 midx-write.c: simplify fill_packs_from_midx()Taylor Blau, May 25, 2025
  28. Patrick SteinhardtMay 26, 2025
  29. Taylor BlauMay 28, 2025
  30. 4/5 midx-write.c: extract inner loop from fill_packs_from_midx()Taylor Blau, May 25, 2025
  31. 5/5 midx: return a `packed_git` pointer from `prepare_midx_pack()`Taylor Blau, May 25, 2025
  32. Patrick SteinhardtMay 26, 2025
  33. Taylor BlauMay 28, 2025
  34. Patrick SteinhardtMay 28, 2025
  35. 0/4 midx: improve prepare_midx_pack() ergonomicsTaylor Blau, May 28, 2025
  36. 1/4 midx: access pack names through `nth_midxed_pack_name()`Taylor Blau, May 28, 2025
  37. Junio C HamanoMay 29, 2025
  38. Taylor BlauJun 3, 2025
  39. Junio C HamanoMay 29, 2025
  40. Taylor BlauJun 3, 2025
  41. 2/4 midx-write.c: guard against incremental MIDXs in want_included_pack()Taylor Blau, May 28, 2025
  42. 3/4 midx-write.c: extract inner loop from fill_packs_from_midx()Taylor Blau, May 28, 2025
  43. 4/4 midx: return a `packed_git` pointer from `prepare_midx_pack()`Taylor Blau, May 28, 2025
  44. Jeff KingMay 30, 2025
  45. Taylor BlauJun 3, 2025
  46. Junio C HamanoAug 28, 2025
  47. Taylor BlauMay 23, 2025
  48. Jeff KingMay 23, 2025
  49. Junio C HamanoMay 21, 2025
  50. 0/2 packfile: avoid access(3p) calls for missing packsPatrick Steinhardt, May 28, 2025
  51. 2/2 midx: stop repeatedly looking up nonexistent packfilesPatrick Steinhardt, May 28, 2025
  52. 1/2 packfile: explain ordering of how we look up auxiliary pack filesPatrick Steinhardt, May 28, 2025
  53. Jeff KingMay 30, 2025

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.