-
Notifications
You must be signed in to change notification settings - Fork 189
commit-graph: fix topo_levels slab propagation regression #2170
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1653,6 +1653,7 @@ static void compute_reachable_generation_numbers( | |
| { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Junio C Hamano wrote on the Git mailing list (how to reply to this email): "Kristofer Karlsson via GitGitGadget" <gitgitgadget@gmail.com>
writes:
> From: Kristofer Karlsson <krka@spotify.com>
>
> Add a step counter and trace2_data_intmax call to
> compute_reachable_generation_numbers() to make the cost of
> the generation number DFS observable. This exposes a
> regression introduced in 199d452758 (commit-graph: fix
> "filling in" topological levels, 2025-04-07) where
Where did "fix filling in" came from? Are you blaming
199d452758 (commit-graph: return the prepared commit graph from
`prepare_commit_graph()`, 2025-09-04)
or something else that happend in April that year?
> incremental commit-graph writes re-walk the entire commit
> ancestry instead of reading topo levels from lower graph
> layers.
> Add a test that demonstrates the problem: with a two-layer
> split commit-graph, writing a new incremental layer for a
> commit whose parent is in the base layer walks all the way
> down to the root (7 steps for 5 base commits) instead of
> reading the existing topo level and stopping immediately
> (1 step).
OK. I expect that [2/2] would update this exact test to demonstrate
that with code updated in [2/2] the extra walk will no longer happen.
> Signed-off-by: Kristofer Karlsson <krka@spotify.com>
> ---
> commit-graph.c | 5 +++++
> t/t5324-split-commit-graph.sh | 28 ++++++++++++++++++++++++++++
> 2 files changed, 33 insertions(+)
>
> diff --git a/commit-graph.c b/commit-graph.c
> index 801471a098..4e39a048c4 100644
> --- a/commit-graph.c
> +++ b/commit-graph.c
> @@ -1653,6 +1653,7 @@ static void compute_reachable_generation_numbers(
> {
> int i;
> struct commit_list *list = NULL;
> + intmax_t steps = 0;
>
> for (i = 0; i < info->commits->nr; i++) {
> struct commit *c = info->commits->items[i];
> @@ -1671,6 +1672,7 @@ static void compute_reachable_generation_numbers(
> int all_parents_computed = 1;
> timestamp_t max_gen = 0;
>
> + steps++;
> for (parent = current->parents; parent; parent = parent->next) {
> repo_parse_commit(info->r, parent->item);
> gen = info->get_generation(parent->item, info->data);
> @@ -1694,6 +1696,9 @@ static void compute_reachable_generation_numbers(
> }
> }
> }
> +
> + trace2_data_intmax("commit-graph", info->r,
> + "generation-dfs-steps", steps);
> }
Pretty-much trivial addition of a trace element.
> diff --git a/t/t5324-split-commit-graph.sh b/t/t5324-split-commit-graph.sh
> index 49a057cc2e..f9c57760f4 100755
> --- a/t/t5324-split-commit-graph.sh
> +++ b/t/t5324-split-commit-graph.sh
> @@ -718,6 +718,34 @@ test_expect_success 'write generation data chunk when commit-graph chain is repl
> )
> '
>
> +test_expect_success 'incremental write reads topo levels from all layers' '
> + git init topo-from-lower &&
> + (
> + cd topo-from-lower &&
> +
> + for i in $(test_seq 5)
> + do
> + test_commit base-$i || return 1
> + done &&
> + git commit-graph write --reachable &&
> +
> + test_commit extra &&
> + git commit-graph write --reachable --split=no-merge &&
> +
> + git checkout base-3 &&
> + test_commit new-branch &&
> +
> + GIT_TRACE2_EVENT="$(pwd)/trace.txt" \
> + git commit-graph write --reachable --split=no-merge &&
> +
> + # BUG: topo levels from lower graph layers are not
> + # propagated, so the DFS re-walks from base-3 down to
> + # the root (7 steps) instead of reading topo levels
> + # from the existing graph (1 step).
> + test_trace2_data commit-graph generation-dfs-steps 7 <trace.txt
> + )
> +'
> +
> test_expect_success 'temporary graph layer is discarded upon failure' '
> git init layer-discard &&
> (There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Kristofer Karlsson wrote on the Git mailing list (how to reply to this email): On Tue, 7 Jul 2026 at 18:56, Junio C Hamano <gitster@pobox.com> wrote:
> >
> > Add a step counter and trace2_data_intmax call to
> > compute_reachable_generation_numbers() to make the cost of
> > the generation number DFS observable. This exposes a
> > regression introduced in 199d452758 (commit-graph: fix
> > "filling in" topological levels, 2025-04-07) where
>
> Where did "fix filling in" came from? Are you blaming
>
> 199d452758 (commit-graph: return the prepared commit graph from
> `prepare_commit_graph()`, 2025-09-04)
>
> or something else that happend in April that year?
Hm, I actually don't remember that exact text, it must have been
an oversight during editing back and forth and I missed it in
my local review -- the commit oid is correct though, that is
the one I was referring to. I will clean this up and shrink it down.
I did not mean April though, but September 4th. I was using
the ISO 8601 date format out of habit.
> OK. I expect that [2/2] would update this exact test to demonstrate
> that with code updated in [2/2] the extra walk will no longer happen.
Yes, I first considered doing this as a single commit, but
I figured it would be easier to reason about the fix if the
problem was identified before-hand.
Thanks,
Kristofer |
||
| int i; | ||
| struct commit_list *list = NULL; | ||
| intmax_t steps = 0; | ||
|
|
||
| for (i = 0; i < info->commits->nr; i++) { | ||
| struct commit *c = info->commits->items[i]; | ||
|
|
@@ -1671,6 +1672,7 @@ static void compute_reachable_generation_numbers( | |
| int all_parents_computed = 1; | ||
| timestamp_t max_gen = 0; | ||
|
|
||
| steps++; | ||
| for (parent = current->parents; parent; parent = parent->next) { | ||
| repo_parse_commit(info->r, parent->item); | ||
| gen = info->get_generation(parent->item, info->data); | ||
|
|
@@ -1694,6 +1696,9 @@ static void compute_reachable_generation_numbers( | |
| } | ||
| } | ||
| } | ||
|
|
||
| trace2_data_intmax("commit-graph", info->r, | ||
| "generation-dfs-steps", steps); | ||
| } | ||
|
|
||
| static timestamp_t get_topo_level(struct commit *c, void *data) | ||
|
|
@@ -2605,7 +2610,7 @@ int write_commit_graph(struct odb_source *source, | |
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Taylor Blau wrote on the Git mailing list (how to reply to this email): On Tue, Jul 07, 2026 at 09:59:43AM +0000, Kristofer Karlsson via GitGitGadget wrote:
> diff --git a/commit-graph.c b/commit-graph.c
> index 4e39a048c4..c2a711cceb 100644
> --- a/commit-graph.c
> +++ b/commit-graph.c
> @@ -2610,7 +2610,7 @@ int write_commit_graph(struct odb_source *source,
>
> g = prepare_commit_graph(ctx.r);
> for (struct commit_graph *chain = g; chain; chain = chain->base_graph)
> - g->topo_levels = &topo_levels;
> + chain->topo_levels = &topo_levels;
>
> if (flags & COMMIT_GRAPH_WRITE_BLOOM_FILTERS)
> ctx.changed_paths = 1;
Looks obviously good.
I think that there is a more permanent fix, though, which would have not
allowed this bug to evade both its author, and reviewer (me). I *think*
that we may clear up some scoping issues if we removed g->topo_levels
entirely, and instead stored it in the write_commit_graph_ctx struct.
I haven't thought through the implications of doing so completely, so
it's entirely possible that this idea is bunk for some other reason. But
it was the first thing that came to mind, and so feels worth exploring
to see if it might have prevented something like this from ever
happening in the first place.
Thanks,
TaylorThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Kristofer Karlsson wrote on the Git mailing list (how to reply to this email): On Tue, 7 Jul 2026 at 15:49, Taylor Blau <me@ttaylorr.com> wrote:
>
> > g = prepare_commit_graph(ctx.r);
> > for (struct commit_graph *chain = g; chain; chain = chain->base_graph)
> > - g->topo_levels = &topo_levels;
> > + chain->topo_levels = &topo_levels;
> >
>
> Looks obviously good.
>
> I think that there is a more permanent fix, though, which would have not
> allowed this bug to evade both its author, and reviewer (me). I *think*
> that we may clear up some scoping issues if we removed g->topo_levels
> entirely, and instead stored it in the write_commit_graph_ctx struct.
I think that sounds feasible, but it would be a larger change.
I wanted to keep this fix minimal and restore
the code to match the pre-regression state. I can maybe look
into a refactoring as followup (or help review someone elses
refactoring?), though I would also be happy just to get that
extra 4 seconds back on every fetch for now :)
Thanks,
KristoferThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Kristofer Karlsson wrote on the Git mailing list (how to reply to this email): On Tue, 7 Jul 2026 at 15:49, Taylor Blau <me@ttaylorr.com> wrote:
>
> I think that there is a more permanent fix, though, which would have not
> allowed this bug to evade both its author, and reviewer (me). I *think*
> that we may clear up some scoping issues if we removed g->topo_levels
> entirely, and instead stored it in the write_commit_graph_ctx struct.
>
> I haven't thought through the implications of doing so completely, so
> it's entirely possible that this idea is bunk for some other reason. But
> it was the first thing that came to mind, and so feels worth exploring
> to see if it might have prevented something like this from ever
> happening in the first place.
>
I looked into the structural change you suggested and I think
it's doable, though not quite as simple as just moving
it into ctx (since fill_commit_graph_info() doesn't have ctx).
I found three approaches:
(a) Thread topo_levels through the call chain. This would
affect:
- fill_commit_graph_info()
- fill_commit_in_graph()
- parse_commit_in_graph_one()
- parse_commit_in_graph()
- load_commit_graph_info()
- lookup_commit_in_graph().
This is the most direct approach, but it touches many functions
and some callers would need to pass in NULL which makes it a bit
noisy.
(b) Move topo_levels to struct object_database. Since
fill_commit_graph_info() can already reach the odb via
g->odb_source->odb, no signature changes are needed.
The write side becomes a single assignment:
ctx.r->objects->topo_levels = &topo_levels;
and cleanup becomes:
ctx.r->objects->topo_levels = NULL;
No chain walk needed and the diff is fairly small.
I am not sure about the semantics of it though -- should the odb
have a reference to topo_levels?
(c) Introduce a struct for the chain as a whole, separating it from
the per-layer struct commit_graph. Right now struct commit_graph
represents a single layer but also serves as the chain head, so
chain-wide state like topo_levels gets duplicated on every layer
(only logically -- the actual overhead is still small).
A dedicated chain struct could own topo_levels and the linked list
of layers. IMO this is the cleanest model but a larger refactoring.
I have a prototype of (b) that compiles and passes the test suite.
For now though, I think the minimal bugfix is the right thing to do.
Thanks,
KristoferThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Junio C Hamano wrote on the Git mailing list (how to reply to this email): "Kristofer Karlsson via GitGitGadget" <gitgitgadget@gmail.com>
writes:
> From: Kristofer Karlsson <krka@spotify.com>
>
> Fix a regression introduced in 199d452758 (commit-graph: fix
> "filling in" topological levels, 2025-04-07) where the loop
I guess the same comment from [1/2] applies. We might be chasing
ghosts here. Is that elusive commit a total hallucination?
> On a repository with 2.78M commits and a multi-layer split
> commit-graph, this caused a single incremental commit-graph
> write to spend ~3.7 seconds in the generation DFS instead of
> microseconds.
Nice.
> Signed-off-by: Kristofer Karlsson <krka@spotify.com>
> ---
> commit-graph.c | 2 +-
> t/t5324-split-commit-graph.sh | 6 +-----
> 2 files changed, 2 insertions(+), 6 deletions(-)
>
> diff --git a/commit-graph.c b/commit-graph.c
> index 4e39a048c4..c2a711cceb 100644
> --- a/commit-graph.c
> +++ b/commit-graph.c
> @@ -2610,7 +2610,7 @@ int write_commit_graph(struct odb_source *source,
>
> g = prepare_commit_graph(ctx.r);
> for (struct commit_graph *chain = g; chain; chain = chain->base_graph)
> - g->topo_levels = &topo_levels;
> + chain->topo_levels = &topo_levels;
>
> if (flags & COMMIT_GRAPH_WRITE_BLOOM_FILTERS)
> ctx.changed_paths = 1;
> diff --git a/t/t5324-split-commit-graph.sh b/t/t5324-split-commit-graph.sh
> index f9c57760f4..9e5ab7dbd0 100755
> --- a/t/t5324-split-commit-graph.sh
> +++ b/t/t5324-split-commit-graph.sh
> @@ -738,11 +738,7 @@ test_expect_success 'incremental write reads topo levels from all layers' '
> GIT_TRACE2_EVENT="$(pwd)/trace.txt" \
> git commit-graph write --reachable --split=no-merge &&
>
> - # BUG: topo levels from lower graph layers are not
> - # propagated, so the DFS re-walks from base-3 down to
> - # the root (7 steps) instead of reading topo levels
> - # from the existing graph (1 step).
> - test_trace2_data commit-graph generation-dfs-steps 7 <trace.txt
> + test_trace2_data commit-graph generation-dfs-steps 1 <trace.txt
> )
> 'There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Kristofer Karlsson wrote on the Git mailing list (how to reply to this email): On Tue, 7 Jul 2026 at 19:00, Junio C Hamano <gitster@pobox.com> wrote:
> >
> > Fix a regression introduced in 199d452758 (commit-graph: fix
> > "filling in" topological levels, 2025-04-07) where the loop
>
> I guess the same comment from [1/2] applies. We might be chasing
> ghosts here. Is that elusive commit a total hallucination?
Oops! The commit exists but the date there is indeed wrong.
Will fix (or just remove it, I am starting to regret trying to make
the commit reference too detailed in the first place).
Thanks,
KristoferThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Junio C Hamano wrote on the Git mailing list (how to reply to this email): Kristofer Karlsson <krka@spotify.com> writes:
> On Tue, 7 Jul 2026 at 19:00, Junio C Hamano <gitster@pobox.com> wrote:
>> >
>> > Fix a regression introduced in 199d452758 (commit-graph: fix
>> > "filling in" topological levels, 2025-04-07) where the loop
>>
>> I guess the same comment from [1/2] applies. We might be chasing
>> ghosts here. Is that elusive commit a total hallucination?
>
> Oops! The commit exists but the date there is indeed wrong.
> Will fix (or just remove it, I am starting to regret trying to make
> the commit reference too detailed in the first place).
Heh, "git show -s --pretty=reference" would give the right amount of
information without giving leeway to users to decide what level of
detail they want ;-)
Thanks. Will mark the topic as "Expecting a reroll.".
|
||
| g = prepare_commit_graph(ctx.r); | ||
| for (struct commit_graph *chain = g; chain; chain = chain->base_graph) | ||
| g->topo_levels = &topo_levels; | ||
| chain->topo_levels = &topo_levels; | ||
|
|
||
| if (flags & COMMIT_GRAPH_WRITE_BLOOM_FILTERS) | ||
| ctx.changed_paths = 1; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Taylor Blau wrote on the Git mailing list (how to reply to this email):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kristofer Karlsson wrote on the Git mailing list (how to reply to this email):