Skip to content

mcs: scheduling mcs support gctuner#10212

Open
bufferflies wants to merge 6 commits intotikv:masterfrom
bufferflies:feat/gc
Open

mcs: scheduling mcs support gctuner#10212
bufferflies wants to merge 6 commits intotikv:masterfrom
bufferflies:feat/gc

Conversation

@bufferflies
Copy link
Contributor

@bufferflies bufferflies commented Feb 4, 2026

What problem does this PR solve?

Issue Number: Ref #10213

What is changed and how does it work?

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No code

Code changes

Side effects

  • Possible performance regression
  • Increased code complexity
  • Breaking backward compatibility

Related changes

Release note

None.

Summary by CodeRabbit

  • New Features

    • GC tuner with server memory limit, GC trigger ratio, and GC threshold controls; server-level GC tuner config is persisted and surfaced in public config.
  • Behavior

    • GC tuner initializes from total memory, logs memory info on startup, and applies live configuration updates without restart.
  • Tests

    • Unit and integration tests added to validate initialization, memory-limit handling, and live config synchronization.

Signed-off-by: bufferflies <1045931706@qq.com>
@ti-chi-bot
Copy link
Contributor

ti-chi-bot bot commented Feb 4, 2026

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@ti-chi-bot ti-chi-bot bot added do-not-merge/needs-linked-issue do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note-none Denotes a PR that doesn't merit a release note. dco-signoff: yes Indicates the PR's author has signed the dco. labels Feb 4, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Add a configurable GC tuner: new gctuner public API (Config, State, InitGCTuner, UpdateIfNeeded), server-level ServerConfig with validation/defaults, persistence and accessor wiring, watcher and cluster integration to initialize/update the tuner, and unit/integration tests plus config watch coverage.

Changes

Cohort / File(s) Summary
Core GC Tuner APIs & tests
pkg/gctuner/tuner.go, pkg/gctuner/tuner_test.go
Add public Config and State types, InitGCTuner(totalMem, cfg) and (*State) UpdateIfNeeded(cfg); memory-limit and GC-threshold calculations; logging and tests for init, zero-limit, and dynamic updates.
Server config type & validation
pkg/schedule/config/config.go
Add ServerConfig with ServerMemoryLimit, ServerMemoryLimitGCTrigger, EnableGOGCTuner, GCTunerThreshold; default constants, bounds, and Adjust() to apply defaults and clamp values.
Persisted config & accessors
pkg/mcs/scheduling/server/config/config.go
Persist new Server section via serverConfig atomic.Value; add setServerConfig, GetServerConfig, and GetGCTunerConfig() mapping persisted server config to gctuner.Config.
Watcher integration & reload flow
pkg/mcs/scheduling/server/config/watcher.go
Watcher now holds gcTunerState; initializes GC tuner using memory.MemTotal() and persisted server config; on config load updates persisted server config and calls UpdateIfNeeded() to sync tuner.
Runtime integration (cluster)
server/cluster/cluster.go
Replace inline GC tuning logic with InitGCTuner(totalMem, cfg) and periodic state.UpdateIfNeeded(cfg); add getGCTunerConfig() helper to map PD server config to gctuner.Config.
Server API exposure
pkg/mcs/scheduling/server/server.go
Populate returned public Config with Server field from persisted config (GetServerConfig()), exposing server GC/memory settings in API.
Integration tests & module
tests/integrations/mcs/scheduling/config_test.go, go.mod
Add TestGCTunerConfigWatch() integration test that persists PD server GC/memory settings and asserts watcher synchronization; update module deps.
Handlers & unit tests
pkg/schedule/schedulers/balance_range.go, pkg/schedule/schedulers/balance_range_test.go
Input validation tightened for engine/rule/alias in AddJob; added test for invalid field types returning 400.

Sequence Diagram(s)

sequenceDiagram
  participant Watcher
  participant PersistConfig
  participant Memory
  participant GCTuner
  participant RaftCluster

  Watcher->>Memory: MemTotal()
  Memory-->>Watcher: totalMem
  Watcher->>PersistConfig: load persisted Server config
  PersistConfig-->>Watcher: server config
  Watcher->>GCTuner: InitGCTuner(totalMem, mappedConfig)
  GCTuner-->>Watcher: State
  RaftCluster->>GCTuner: periodic tick -> UpdateIfNeeded(cfg)
  GCTuner-->>RaftCluster: applied?/updated (bool)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • rleungx
  • lhy1024
  • okJiang

Poem

🐰 I nudged the mem, I tuned the beat,
Thresholds hopped in tidy rows so neat,
Watcher hums, the cluster gives a wink,
State updates quick as a blink—
Rabbit hops off, GC tuned and sweet.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description follows the required template structure but has critical gaps. It includes an issue reference ('Ref #10213') and addresses the checklist items, but the commit-message block is empty, no detailed explanation of changes is provided, and no description of how the gctuner integration works is given. Add a detailed commit message explaining the gctuner integration; expand 'What is changed and how does it work?' section with specifics on gctuner initialization, config handling, and integration points; provide more context about the implementation.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'mcs: scheduling mcs support gctuner' directly relates to the main change—adding GC tuner support to the scheduling MCS system. However, it contains redundancy ('mcs' appears twice) and could be more specific about what 'support' entails.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ti-chi-bot ti-chi-bot bot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Feb 4, 2026
@bufferflies bufferflies marked this pull request as ready for review February 4, 2026 08:33
@ti-chi-bot ti-chi-bot bot removed do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. do-not-merge/needs-linked-issue labels Feb 4, 2026
@codecov
Copy link

codecov bot commented Feb 4, 2026

Codecov Report

❌ Patch coverage is 85.08772% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.90%. Comparing base (b668f43) to head (eb2a7be).
⚠️ Report is 42 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #10212      +/-   ##
==========================================
+ Coverage   78.59%   78.90%   +0.30%     
==========================================
  Files         520      527       +7     
  Lines       70014    70990     +976     
==========================================
+ Hits        55028    56014     +986     
+ Misses      11008    10968      -40     
- Partials     3978     4008      +30     
Flag Coverage Δ
unittests 78.90% <85.08%> (+0.30%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@pkg/gctuner/tuner_test.go`:
- Around line 129-153: Tests mutate package globals (EnableGOGCTuner,
memory.ServerMemoryLimit, GlobalMemoryLimitTuner/globalTuner via Tuning) and do
not restore them; update TestInitGCTuner, TestInitGCTunerWithZeroMemoryLimit,
and TestUpdateIfNeeded to capture current values at test start (e.g., prevEnable
:= EnableGOGCTuner.Load(), prevServerLimit := memory.ServerMemoryLimit,
prevGlobal := GlobalMemoryLimitTuner) and register t.Cleanup() to restore those
values (set EnableGOGCTuner back, reset memory.ServerMemoryLimit, and call
Tuning(prevGlobal) or reassign GlobalMemoryLimitTuner appropriately) so global
state is returned after each test.

In `@pkg/gctuner/tuner.go`:
- Around line 271-283: The update block currently only triggers when
newMemoryLimitBytes or newMemoryLimitGCTriggerBytes change, so changes to
newMemoryLimitGCTriggerRatio can be ignored due to rounding; modify the
conditional that gates the update to also compare newMemoryLimitGCTriggerRatio
against s.MemoryLimitGCTriggerRatio (and/or include an epsilon for float
comparison) so that when the GC trigger ratio changes you still set
s.MemoryLimitGCTriggerRatio, call GlobalMemoryLimitTuner.SetPercentage and
UpdateMemoryLimit; update references in this block to use
newMemoryLimitGCTriggerRatio, s.MemoryLimitGCTriggerRatio,
GlobalMemoryLimitTuner, memory.ServerMemoryLimit.Store and ensure updated is set
when the ratio-only change occurs.

In `@pkg/mcs/scheduling/server/config/config.go`:
- Around line 149-152: The ServerConfig adjustment is using the wrong metadata
key: replace the call c.Server.Adjust(configMetaData.Child("server")) with
c.Server.Adjust(configMetaData.Child("pd-server")) so the Adjust on ServerConfig
(c.Server) uses the TOML tag name `pd-server` and preserves user-provided values
instead of treating them as undefined; update the metadata key where
c.Server.Adjust is invoked to "pd-server".

In `@pkg/mcs/scheduling/server/config/watcher.go`:
- Around line 92-97: Replace the plain string concatenation error creation with
errors.Wrap to preserve context: after calling memory.MemTotal() in the watcher
initialization, keep the cancel() call but return errors.Wrap(err, "fail to get
total memory") instead of errors.New("fail to get total memory: "+err.Error());
this uses github.com/pingcap/errors and preserves the original error and stack
trace while keeping the same control flow around memory.MemTotal() and cancel().

Signed-off-by: bufferflies <1045931706@qq.com>
Copy link
Member

@okJiang okJiang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Tuning sets the threshold of heap which will be respect by gogc tuner.
// When Tuning, the env GOGC will not be take effect.
// threshold: disable tuning if threshold == 0
func Tuning(threshold uint64) {
	// disable gc tuner if percent is zero
	if t := globalTuner.Load(); t == nil {
		t1 := newTuner(threshold)
		globalTuner.CompareAndSwap(nil, &t1)
	} else {
		if threshold <= 0 {
			(*t).stop()
			globalTuner.CompareAndSwap(t, nil)
		} else {
			(*t).setThreshold(threshold)
		}
	}
}

The Tuning annotation says that threshold==0 is disabled, but newTuner(0) still starts the finalizer loop when globalTuner is empty; And if CompareAndSwap(nil, &t1) fails during concurrent initialization, the tuner created by the losing party will not stop (it may survive for a long time and participate in parameter adjustment when enabled later).

@okJiang
Copy link
Member

okJiang commented Feb 5, 2026

Do other microservice components also need to support gctunner?

@rleungx
Copy link
Member

rleungx commented Feb 5, 2026

Do other microservice components also need to support gctunner?

We should support it, and we can enable it for the specified case.

@bufferflies
Copy link
Contributor Author

// Tuning sets the threshold of heap which will be respect by gogc tuner.
// When Tuning, the env GOGC will not be take effect.
// threshold: disable tuning if threshold == 0
func Tuning(threshold uint64) {
	// disable gc tuner if percent is zero
	if t := globalTuner.Load(); t == nil {
		t1 := newTuner(threshold)
		globalTuner.CompareAndSwap(nil, &t1)
	} else {
		if threshold <= 0 {
			(*t).stop()
			globalTuner.CompareAndSwap(t, nil)
		} else {
			(*t).setThreshold(threshold)
		}
	}
}

The Tuning annotation says that threshold==0 is disabled, but newTuner(0) still starts the finalizer loop when globalTuner is empty; And if CompareAndSwap(nil, &t1) fails during concurrent initialization, the tuner created by the losing party will not stop (it may survive for a long time and participate in parameter adjustment when enabled later).

Yes, the tuning should fix this bug

Signed-off-by: bufferflies <1045931706@qq.com>
@bufferflies
Copy link
Contributor Author

// Tuning sets the threshold of heap which will be respect by gogc tuner.
// When Tuning, the env GOGC will not be take effect.
// threshold: disable tuning if threshold == 0
func Tuning(threshold uint64) {
	// disable gc tuner if percent is zero
	if t := globalTuner.Load(); t == nil {
		t1 := newTuner(threshold)
		globalTuner.CompareAndSwap(nil, &t1)
	} else {
		if threshold <= 0 {
			(*t).stop()
			globalTuner.CompareAndSwap(t, nil)
		} else {
			(*t).setThreshold(threshold)
		}
	}
}

The Tuning annotation says that threshold==0 is disabled, but newTuner(0) still starts the finalizer loop when globalTuner is empty; And if CompareAndSwap(nil, &t1) fails during concurrent initialization, the tuner created by the losing party will not stop (it may survive for a long time and participate in parameter adjustment when enabled later).

Yes, have fixed this bug

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/gctuner/tuner.go (1)

67-82: ⚠️ Potential issue | 🟠 Major

Leaked tuner on concurrent CompareAndSwap failure.

When two goroutines call Tuning(threshold) concurrently with threshold > 0 and globalTuner is nil, both create a newTuner (which starts a finalizer loop). The loser of CompareAndSwap silently discards its tuner without calling stop(), leaking a goroutine. This was also identified by @bufferflies in the PR discussion.

🔧 Proposed fix — stop the tuner if CAS fails
 	if t := globalTuner.Load(); t == nil {
 		// init gc tuner only when threshold > 0, otherwise do nothing
 		if threshold > 0 {
 			t1 := newTuner(threshold)
-			globalTuner.CompareAndSwap(nil, &t1)
+			if !globalTuner.CompareAndSwap(nil, &t1) {
+				t1.stop()
+				// Another goroutine won; update its threshold instead.
+				if t2 := globalTuner.Load(); t2 != nil {
+					(*t2).setThreshold(threshold)
+				}
+			}
 		}
🧹 Nitpick comments (2)
pkg/gctuner/tuner.go (2)

249-259: Duplicated computation between InitGCTuner and UpdateIfNeeded.

Lines 253-259 replicate the same derivation logic as lines 221-227 in InitGCTuner. Consider extracting a helper (e.g., computeState(totalMem uint64, cfg *Config) (...)) to keep the two paths in sync and reduce maintenance burden.

♻️ Sketch
func computeDerivedState(totalMem uint64, cfg *Config) (memLimitBytes, gcThresholdBytes uint64, gcTriggerRatio float64, gcTriggerBytes uint64) {
	memLimitBytes = uint64(float64(totalMem) * cfg.ServerMemoryLimit)
	gcThresholdBytes = uint64(float64(memLimitBytes) * cfg.GCTunerThreshold)
	if memLimitBytes == 0 {
		gcThresholdBytes = uint64(float64(totalMem) * cfg.GCTunerThreshold)
	}
	gcTriggerRatio = cfg.ServerMemoryLimitGCTrigger
	gcTriggerBytes = uint64(float64(memLimitBytes) * gcTriggerRatio)
	return
}

Then call it from both InitGCTuner and UpdateIfNeeded.


218-227: MemoryLimitGCTriggerBytes is never used downstream, so the inconsistency is a code-cleanliness issue, not a functional bug.

While the inconsistency is real—GCThresholdBytes has a fallback to totalMem when MemoryLimitBytes == 0 (lines 223-225), but MemoryLimitGCTriggerBytes is computed as 0 * ratio = 0 with no fallback (line 227)—the field itself is never read anywhere in the codebase. The actual memory limit tuning uses MemoryLimitGCTriggerRatio (the ratio) and ServerMemoryLimit directly; GlobalMemoryLimitTuner.calcMemoryLimit() has its own fallback logic that returns math.MaxInt64 when the computed limit is 0.

The same pattern exists in UpdateIfNeeded at line 259. Since MemoryLimitGCTriggerBytes is private and unused, consider removing it entirely or adding a fallback for consistency.

@ti-chi-bot ti-chi-bot bot added the needs-1-more-lgtm Indicates a PR needs 1 more LGTM. label Feb 6, 2026
@bufferflies
Copy link
Contributor Author

/ping @lhy1024

if threshold > 0 {
t1 := newTuner(threshold)
globalTuner.CompareAndSwap(nil, &t1)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

threshold == 0 is fixed now, but the concurrent first-time initialization case is still unsafe. If two goroutines call Tuning(threshold > 0) while globalTuner is nil, both of them will create newTuner(threshold) before the CAS. Since newTuner() already starts the finalizer loop, the loser of tuning() on later GCs once EnableGOGCTuner is enabled.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/gctuner/tuner.go`:
- Around line 68-88: Concurrent calls to Tuning can allocate a newTuner whose
finalizer loop starts immediately and gets leaked if the goroutine loses the
CompareAndSwap on globalTuner; modify Tuning so that newTuner does not start its
finalizer until after globalTuner.CompareAndSwap(nil, &t1) returns true, or if
you must create the tuner before the CAS then call (*t1).stop() (or t1.stop())
when CompareAndSwap fails; specifically change the flow around
newTuner(threshold) + globalTuner.CompareAndSwap(nil, &t1) to either defer
starting the finalizer inside newTuner until after CAS success or invoke the
tuner's stop method on the loser path so no running finalizer is left behind,
and ensure code uses the same symbols Tuning, globalTuner, newTuner,
CompareAndSwap, and stop/setThreshold when implementing this fix.

In `@pkg/mcs/scheduling/server/config/config.go`:
- Around line 302-305: PersistConfig.setServerConfig currently stores the
incoming cfg directly, bypassing validation/clamping; call cfg.Adjust(nil) (or
equivalent clamp-only variant) before storing to ensure ServerMemoryLimit and
other fields are validated and defaults applied so downstream users like
GetGCTunerConfig() receive sane values; update PersistConfig.setServerConfig to
run ServerConfig.Adjust() on the provided cfg and then
o.serverConfig.Store(cfg).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c98acc6e-702e-47e1-a616-f5cf766226be

📥 Commits

Reviewing files that changed from the base of the PR and between b3dac8b and 33451c9.

📒 Files selected for processing (3)
  • pkg/gctuner/tuner.go
  • pkg/mcs/scheduling/server/config/config.go
  • pkg/mcs/scheduling/server/server.go

Signed-off-by: tongjian <1045931706@qq.com>
@ti-chi-bot ti-chi-bot bot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Mar 10, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/schedule/schedulers/balance_range.go`:
- Around line 98-102: The validation is checking the uninitialized field
job.Engine instead of the parsed engine value, causing valid requests to be
rejected; change the conditional to compare the local variable engine against
core.EngineTiFlash and core.EngineTiKV (validate engine first), return the
bad-request via handler.rd.JSON if engine is invalid, and only then assign
job.Engine = engine so job.Engine is set after successful validation;
references: job.Engine, engine, core.EngineTiFlash, core.EngineTiKV,
handler.rd.JSON.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d001e565-3b90-49ad-89ab-da46e7d0304e

📥 Commits

Reviewing files that changed from the base of the PR and between 33451c9 and 484b9dc.

📒 Files selected for processing (5)
  • pkg/gctuner/tuner.go
  • pkg/mcs/scheduling/server/config/config.go
  • pkg/mcs/scheduling/server/server.go
  • pkg/schedule/schedulers/balance_range.go
  • pkg/schedule/schedulers/balance_range_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/mcs/scheduling/server/server.go

Signed-off-by: tongjian <1045931706@qq.com>
@ti-chi-bot ti-chi-bot bot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Mar 10, 2026
@bufferflies bufferflies requested a review from lhy1024 March 10, 2026 03:14
@bufferflies
Copy link
Contributor Author

/ping @lhy1024

@bufferflies
Copy link
Contributor Author

/retest

if threshold <= 0 {
(*t).stop()
globalTuner.CompareAndSwap(t, nil)
for range 3 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tuning() now publishes &t1 into globalTuner before calling t1.start(). That means another goroutine concurrent Tuning(0) wins the CAS-to-nil path, it immediately calls (*t).stop(), which dereferences t.finalizer and panics.

I reproduced this locally with a small unit test by placing a newTunerWithoutStart() instance into

Also, even if stop() were made nil-safe, the publish-before-start ordering would still be problematic: would leave behind a detached started tuner again. So the root issue is that the tuner is visible to other
goroutines before it is fully initialized.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  func TestTuningDisableHandlesPublishedButNotStartedTuner(t *testing.T) {
        re := require.New(t)

        old := globalTuner.Load()
        t.Cleanup(func() {
                globalTuner.Store(old)
        })

        tn := newTunerWithoutStart(1)
        globalTuner.Store(&tn)

        re.NotPanics(func() {
                Tuning(0)
        })
        re.Nil(globalTuner.Load())
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will add a lock to keep it atomic.

Signed-off-by: tongjian <1045931706@qq.com>
@ti-chi-bot ti-chi-bot bot added lgtm and removed needs-1-more-lgtm Indicates a PR needs 1 more LGTM. labels Mar 10, 2026
@ti-chi-bot
Copy link
Contributor

ti-chi-bot bot commented Mar 10, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: lhy1024, okJiang
Once this PR has been reviewed and has the lgtm label, please assign yudongusa for approval. For more information see the Code Review Process.
Please ensure that each of them provides their approval before proceeding.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ti-chi-bot
Copy link
Contributor

ti-chi-bot bot commented Mar 10, 2026

[LGTM Timeline notifier]

Timeline:

  • 2026-02-06 06:26:58.999325829 +0000 UTC m=+421090.100724550: ☑️ agreed by okJiang.
  • 2026-03-10 08:24:07.911073778 +0000 UTC m=+338479.423131439: ☑️ agreed by lhy1024.

@bufferflies
Copy link
Contributor Author

/retest

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dco-signoff: yes Indicates the PR's author has signed the dco. lgtm release-note-none Denotes a PR that doesn't merit a release note. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants