Skip to content

Conversation

@sendya
Copy link
Member

@sendya sendya commented Jan 19, 2026

This pull request introduces several significant enhancements and refactorings to support directory-level cache push with mark-expired semantics, improve purge control, and add a path trie utility for efficient path matching. It also updates documentation to reflect new features and includes supporting code for marking cached directories as expired rather than immediately deleting them. The changes are grouped below by theme.

Directory Mark-Expired Push and Purge Enhancements

  • Introduces a new wrapper (storage/marked) around storage and buckets to support marking cached directories as expired (mark-expired) instead of immediate hard deletion. This is achieved by checking a path trie of purge tasks and updating object metadata to expire if matched. (storage/marked/wrap_storage.go, storage/marked/bucket.go, storage/marked/marked.go) [1] [2] [3]
  • Refactors the main application to wrap the storage layer with the new mark-expired logic and initializes the checker with the shared key-value store. (main.go) [1] [2] [3]
  • Updates the purge plugin to parse and handle new purge control header values, supporting both directory and file purges with mark-expired or hard delete modes. (plugin/purge/purge.go) [1] [2] [3] [4]

PathTrie Utility for Path Matching

  • Adds a generic PathTrie implementation for efficient path and prefix matching, supporting wildcards and regex, along with an example test. This is used by the mark-expired checker to track directory-level purge tasks. (pkg/pathtire/pathtire.go, pkg/pathtire/pathtire_example_test.go) [1] [2]

Documentation Updates

  • Updates both English and Chinese README.md files to show that directory mark-expired push and fuzzy refresh are now implemented. [1] [2]

Other Improvements and Refactoring

  • Refines the PurgeControl struct with a String() method for better logging and debugging, and improves import organization. (api/defined/v1/storage/storage.go) [1] [2]
  • Adjusts the native storage purge logic to properly handle mark-expired directory purges by skipping shared key-value index hits and falling back to a full scan. (storage/storage.go) [1] [2]
  • Minor logging and import cleanups. (storage/bucket/disk/disk.go)

References:
Directory mark-expired push: [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14]
PathTrie utility: [1] [2]
Documentation: [1] [2]
Other improvements:

Copilot AI review requested due to automatic review settings January 19, 2026 02:24
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request implements directory-level cache purge with mark-expired semantics, adding the ability to mark cached directories as expired rather than immediately deleting them. The implementation introduces a new PathTrie data structure for efficient path matching and wraps the storage layer to intercept lookup operations and mark matching objects as expired.

Changes:

  • Introduces a PathTrie utility for efficient path prefix matching with wildcard and regex support
  • Adds a storage wrapper layer that intercepts lookups and marks objects as expired based on purge tasks stored in the trie
  • Updates the purge plugin to parse and handle mark-expired vs hard-delete modes for both files and directories
  • Updates documentation to reflect that directory mark-expired push and fuzzy refresh are now implemented

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 20 comments.

Show a summary per file
File Description
pkg/pathtire/pathtire.go New PathTrie implementation for efficient path/prefix matching
pkg/pathtire/pathtire_example_test.go Example test demonstrating PathTrie usage
storage/marked/wrap_storage.go Storage wrapper that integrates mark-expired logic
storage/marked/marked.go Checker implementation using PathTrie and SharedKV
storage/marked/bucket.go Bucket wrapper that marks objects as expired during lookup
main.go Integration of marked storage wrapper into application initialization
storage/storage.go Skip SharedKV index for mark-expired directory purges
plugin/purge/purge.go Parse purge control headers and default to mark-expired mode
api/defined/v1/storage/storage.go Add String() method to PurgeControl for logging
storage/bucket/disk/disk.go Minor formatting cleanup
README.md Update feature checklist to show DIR mark-expired as implemented
README.zh-CN.md Update Chinese feature checklist to show DIR mark-expired as implemented

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

func NewSharedKVChecker(kv storagev1.SharedKV, opts ...SharedKVOption) *checker {
c := &checker{
KV: kv,
pathtire: pathtire.NewPathTrie[string, int64](),
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Function call has a spelling error. Should be "pathtrie.NewPathTrie" (not "pathtire.NewPathTrie").

Copilot uses AI. Check for mistakes.
Comment on lines 38 to 40
// Mark expired by setting ExpiresAt into the past and store back.
md.ExpiresAt = time.Now().Add(-1 * time.Second).Unix()
// _ = b.base.Store(ctx, md)
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Commented-out code should either be removed or implemented. The Store call at line 40 is commented out, which means metadata changes are not being persisted back to storage. This could be intentional for performance reasons, but needs clarification or TODO comment explaining why.

Suggested change
// Mark expired by setting ExpiresAt into the past and store back.
md.ExpiresAt = time.Now().Add(-1 * time.Second).Unix()
// _ = b.base.Store(ctx, md)
// Mark as expired for this lookup by setting ExpiresAt into the past.
// NOTE: We intentionally do not persist this change via Store here to avoid
// additional writes during Lookup; callers should treat the returned metadata
// as expired.
md.ExpiresAt = time.Now().Add(-1 * time.Second).Unix()

Copilot uses AI. Check for mistakes.
Comment on lines +181 to +197
ctrl := storagev1.PurgeControl{
MarkExpired: true, // 默认用过期标记
}

if len(param) >= 1 {
ctrl.Dir = param[0] == "dir"
}

// 配置推送模式 hard / mark_expired
if len(param) == 2 {
hasHard := param[1] == "hard"

ctrl.Hard = hasHard
ctrl.MarkExpired = !hasHard
}

// 没有配置推送选项
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

The default behavior sets MarkExpired to true even when no header is provided. This changes the default purge behavior from hard delete to mark-expired, which could be a breaking change for existing deployments. Consider whether this default is intentional and if it should be documented or made configurable.

Suggested change
ctrl := storagev1.PurgeControl{
MarkExpired: true, // 默认用过期标记
}
if len(param) >= 1 {
ctrl.Dir = param[0] == "dir"
}
// 配置推送模式 hard / mark_expired
if len(param) == 2 {
hasHard := param[1] == "hard"
ctrl.Hard = hasHard
ctrl.MarkExpired = !hasHard
}
// 没有配置推送选项
// Do not change default purge behavior when no header is provided.
// Only set Hard / MarkExpired when explicitly configured via the header.
ctrl := storagev1.PurgeControl{}
if len(param) >= 1 {
ctrl.Dir = param[0] == "dir"
}
// 配置推送模式 hard / mark_expired
if len(param) >= 2 {
switch param[1] {
case "hard":
ctrl.Hard = true
ctrl.MarkExpired = false
case "mark_expired":
ctrl.MarkExpired = true
ctrl.Hard = false
}
}
// 没有配置推送选项时,保持 storagev1.PurgeControl 的零值,让底层保持原有默认行为

Copilot uses AI. Check for mistakes.

func ExampleNewPathTrie() {

trie := pathtire.NewPathTrie[string, int64]()
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Variable name has a spelling error. Should be "pathtrie.NewPathTrie" (not "pathtire.NewPathTrie").

Copilot uses AI. Check for mistakes.
Comment on lines 73 to 77
func (c *checker) TireAdd(ctx context.Context, storePath string) {
unix := time.Now().Unix()
c.pathtire.Insert(storePath, unix)
log.Infof("purge add pathtire %s, drop-time %d", storePath, unix)
}
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Method naming issue. "TireAdd" should be renamed to "TrieAdd" or a more descriptive name like "AddPath" to match Go naming conventions and correct spelling.

Suggested change
func (c *checker) TireAdd(ctx context.Context, storePath string) {
unix := time.Now().Unix()
c.pathtire.Insert(storePath, unix)
log.Infof("purge add pathtire %s, drop-time %d", storePath, unix)
}
// TrieAdd adds the given storePath to the path trie with the current Unix timestamp.
func (c *checker) TrieAdd(ctx context.Context, storePath string) {
unix := time.Now().Unix()
c.pathtire.Insert(storePath, unix)
log.Infof("purge add pathtire %s, drop-time %d", storePath, unix)
}
// TireAdd is kept for backward compatibility; use TrieAdd instead.
func (c *checker) TireAdd(ctx context.Context, storePath string) {
c.TrieAdd(ctx, storePath)
}

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,214 @@
package pathtire
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Package name has a spelling error. The correct spelling should be "pathtrie" (not "pathtire"). A trie is a tree-like data structure, whereas "tire" refers to a wheel component. This typo affects the package name, which should be corrected throughout the codebase.

Suggested change
package pathtire
package pathtrie

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,25 @@
package pathtire_test
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Package name has a spelling error. The correct spelling should be "pathtrie_test" (not "pathtire_test"). This should match the corrected package name.

Suggested change
package pathtire_test
package pathtrie_test

Copilot uses AI. Check for mistakes.
Comment on lines 6 to 11
"github.com/omalloc/tavern/pkg/pathtire"
)

func ExampleNewPathTrie() {

trie := pathtire.NewPathTrie[string, int64]()
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Import path has a spelling error. Should be "github.com/omalloc/tavern/pkg/pathtrie" (not "pathtire").

Suggested change
"github.com/omalloc/tavern/pkg/pathtire"
)
func ExampleNewPathTrie() {
trie := pathtire.NewPathTrie[string, int64]()
"github.com/omalloc/tavern/pkg/pathtrie"
)
func ExampleNewPathTrie() {
trie := pathtrie.NewPathTrie[string, int64]()

Copilot uses AI. Check for mistakes.
storagev1 "github.com/omalloc/tavern/api/defined/v1/storage"
"github.com/omalloc/tavern/api/defined/v1/storage/object"
"github.com/omalloc/tavern/contrib/log"
"github.com/omalloc/tavern/pkg/pathtire"
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Import path has a spelling error. Should be "github.com/omalloc/tavern/pkg/pathtrie" (not "pathtire").

Copilot uses AI. Check for mistakes.
// If AutoClear is true, the key is deleted after a hit.
type checker struct {
KV storagev1.SharedKV
pathtire *pathtire.PathTrie[string, int64]
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

Variable type has a spelling error. Should be "pathtrie.PathTrie" (not "pathtire.PathTrie").

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants