Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
423 lines
14 KiB
Go
423 lines
14 KiB
Go
package meta_cache
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"math"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/filer"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
)
|
|
|
|
// A cached directory's listing is split into contiguous name-range sections so
|
|
// a burst of remote changes invalidates one section, not the whole listing.
|
|
// Events keep applying to a stale section; staleness only means the next
|
|
// listing re-validates that range against the filer before serving it, and
|
|
// lookups in it read through until then.
|
|
const (
|
|
// dirSectionSize is the target entries per section, fixed when a listing
|
|
// is built and re-derived when a re-listed section has outgrown it.
|
|
dirSectionSize = 1024
|
|
// sectionHotThreshold remote changes within sectionHotWindow invalidate
|
|
// the section they land in.
|
|
sectionHotThreshold = 64
|
|
sectionHotWindow = 2 * time.Second
|
|
// sectionRefreshTimeout bounds how long a readdir waits on re-validating
|
|
// a section before serving the maintained-but-unverified cache instead.
|
|
sectionRefreshTimeout = 5 * time.Second
|
|
// sectionRefreshMaxEntries is the most one refresh will carry; a section
|
|
// grown past it is cheaper to re-tile with a full directory rebuild.
|
|
sectionRefreshMaxEntries = 4 * dirSectionSize
|
|
)
|
|
|
|
// ErrRefreshRangeTooLarge reports a section that outgrew one refresh; the
|
|
// caller should drop the directory cache so a full rebuild re-tiles it.
|
|
var ErrRefreshRangeTooLarge = errors.New("section outgrew one refresh")
|
|
|
|
// sectionList: bounds[i] is the first name of section i+1; section 0 starts at
|
|
// the beginning of the namespace, the last section runs to the end. It is a
|
|
// plain state machine — no locking, no store; MetaCache drives it under its
|
|
// own mutex.
|
|
type sectionList struct {
|
|
bounds []string
|
|
sections []sectionState
|
|
}
|
|
|
|
type sectionState struct {
|
|
stale bool
|
|
updateCount int
|
|
windowStart time.Time
|
|
// unverifiable marks a stale section whose filer stamps no listing
|
|
// snapshots: re-listing it can never vouch for it, so stop trying and
|
|
// leave its lookups reading through.
|
|
unverifiable bool
|
|
// floorTsNs is the section's own listing snapshot: a refresh at it covered
|
|
// every name in the range, present or absent, so it fences like the
|
|
// directory floor but for this range alone.
|
|
floorTsNs int64
|
|
}
|
|
|
|
func newSectionTable(bounds []string) *sectionList {
|
|
return §ionList{bounds: bounds, sections: make([]sectionState, len(bounds)+1)}
|
|
}
|
|
|
|
// sectionBoundsCollector derives section boundaries from an ordered listing:
|
|
// every dirSectionSize-th name starts a new section.
|
|
type sectionBoundsCollector struct {
|
|
count int
|
|
bounds []string
|
|
}
|
|
|
|
func (c *sectionBoundsCollector) note(name string) {
|
|
if c.count > 0 && c.count%dirSectionSize == 0 {
|
|
c.bounds = append(c.bounds, name)
|
|
}
|
|
c.count++
|
|
}
|
|
|
|
// sectionRefresh carries one section's re-listing to the apply loop.
|
|
type sectionRefresh struct {
|
|
lo, hi string
|
|
entries []*filer.Entry
|
|
snapshotTsNs int64
|
|
}
|
|
|
|
func (sl *sectionList) sectionOf(name string) int {
|
|
idx := sort.SearchStrings(sl.bounds, name)
|
|
if idx < len(sl.bounds) && sl.bounds[idx] == name {
|
|
idx++
|
|
}
|
|
return idx
|
|
}
|
|
|
|
// sectionRange returns the [lo, hi) name range of section idx; "" is unbounded.
|
|
func (sl *sectionList) sectionRange(idx int) (lo, hi string) {
|
|
if idx > 0 {
|
|
lo = sl.bounds[idx-1]
|
|
}
|
|
if idx < len(sl.bounds) {
|
|
hi = sl.bounds[idx]
|
|
}
|
|
return
|
|
}
|
|
|
|
// noteChange counts one change against the section it lands in, marking the
|
|
// section stale when a burst crosses the threshold.
|
|
func (sl *sectionList) noteChange(name string, now time.Time) {
|
|
s := &sl.sections[sl.sectionOf(name)]
|
|
if s.stale {
|
|
return
|
|
}
|
|
if s.windowStart.IsZero() || now.Sub(s.windowStart) > sectionHotWindow {
|
|
s.windowStart = now
|
|
s.updateCount = 0
|
|
}
|
|
s.updateCount++
|
|
if s.updateCount >= sectionHotThreshold {
|
|
s.stale = true
|
|
}
|
|
}
|
|
|
|
func (sl *sectionList) isFresh(name string) bool {
|
|
return !sl.sections[sl.sectionOf(name)].stale
|
|
}
|
|
|
|
// floorOf returns the refresh snapshot covering this name, or zero when its
|
|
// section has never been re-listed. A stale section keeps fencing: what its
|
|
// last listing established stays established.
|
|
func (sl *sectionList) floorOf(name string) int64 {
|
|
return sl.sections[sl.sectionOf(name)].floorTsNs
|
|
}
|
|
|
|
type nameRange struct {
|
|
lo, hi string
|
|
}
|
|
|
|
// staleRangesAhead returns the invalidated ranges worth re-listing from the
|
|
// section holding startName to the end of the directory.
|
|
func (sl *sectionList) staleRangesAhead(startName string) (ranges []nameRange) {
|
|
for i := sl.sectionOf(startName); i < len(sl.sections); i++ {
|
|
if sl.sections[i].stale && !sl.sections[i].unverifiable {
|
|
lo, hi := sl.sectionRange(i)
|
|
ranges = append(ranges, nameRange{lo: lo, hi: hi})
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// hasRange reports whether the table still has a section covering exactly
|
|
// [lo, hi); a rebuild or re-split since a listing was taken retires the range
|
|
// it described.
|
|
func (sl *sectionList) hasRange(lo, hi string) bool {
|
|
curLo, curHi := sl.sectionRange(sl.sectionOf(lo))
|
|
return curLo == lo && curHi == hi
|
|
}
|
|
|
|
// completeRefresh marks the section covering exactly [lo, hi) fresh after a
|
|
// re-listing that fetched names at snapshotTsNs, re-splitting a section that
|
|
// outgrew twice its target size. The snapshot becomes the section's floor. A
|
|
// range the table no longer has is ignored — splicing bounds from a stale
|
|
// range could leave the table unsorted — and an unversioned listing vouches
|
|
// for nothing: the section stays stale, remembered as not worth re-listing.
|
|
func (sl *sectionList) completeRefresh(lo, hi string, names []string, snapshotTsNs int64) bool {
|
|
idx := sl.sectionOf(lo)
|
|
if curLo, curHi := sl.sectionRange(idx); curLo != lo || curHi != hi {
|
|
return false
|
|
}
|
|
if snapshotTsNs == 0 {
|
|
sl.sections[idx].unverifiable = true
|
|
return false
|
|
}
|
|
if len(names) > 2*dirSectionSize {
|
|
var newBounds []string
|
|
for i := dirSectionSize; i < len(names); i += dirSectionSize {
|
|
newBounds = append(newBounds, names[i])
|
|
}
|
|
bounds := make([]string, 0, len(sl.bounds)+len(newBounds))
|
|
bounds = append(bounds, sl.bounds[:idx]...)
|
|
bounds = append(bounds, newBounds...)
|
|
bounds = append(bounds, sl.bounds[idx:]...)
|
|
sections := make([]sectionState, 0, len(bounds)+1)
|
|
sections = append(sections, sl.sections[:idx]...)
|
|
for i := 0; i <= len(newBounds); i++ {
|
|
sections = append(sections, sectionState{floorTsNs: snapshotTsNs})
|
|
}
|
|
sections = append(sections, sl.sections[idx+1:]...)
|
|
sl.bounds, sl.sections = bounds, sections
|
|
} else {
|
|
sl.sections[idx] = sectionState{floorTsNs: snapshotTsNs}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// noteSectionChangeLocked counts one remote change against the section of the
|
|
// directory it lands in.
|
|
func (mc *MetaCache) noteSectionChangeLocked(fp util.FullPath, now time.Time) {
|
|
dir, name := fp.DirAndName()
|
|
if sl := mc.dirSections[util.FullPath(dir)]; sl != nil {
|
|
sl.noteChange(name, now)
|
|
}
|
|
}
|
|
|
|
// IsNameFresh reports whether the cached listing still vouches for this name.
|
|
// A directory without section state vouches for all of it.
|
|
func (mc *MetaCache) IsNameFresh(fp util.FullPath) bool {
|
|
dir, name := fp.DirAndName()
|
|
mc.RLock()
|
|
defer mc.RUnlock()
|
|
sl := mc.dirSections[util.FullPath(dir)]
|
|
return sl == nil || sl.isFresh(name)
|
|
}
|
|
|
|
func (mc *MetaCache) staleRangesAhead(dirPath util.FullPath, startName string) []nameRange {
|
|
mc.RLock()
|
|
defer mc.RUnlock()
|
|
sl := mc.dirSections[dirPath]
|
|
if sl == nil {
|
|
return nil
|
|
}
|
|
return sl.staleRangesAhead(startName)
|
|
}
|
|
|
|
func (mc *MetaCache) rangeStale(dirPath util.FullPath, lo string) bool {
|
|
mc.RLock()
|
|
defer mc.RUnlock()
|
|
sl := mc.dirSections[dirPath]
|
|
return sl != nil && !sl.isFresh(lo)
|
|
}
|
|
|
|
// EnsureListingFresh re-validates every invalidated section from startName to
|
|
// the end of the directory before a listing pages through them. A listing's
|
|
// reach is unknowable up front — a resumed handle can skip far ahead, and
|
|
// shrunken sections let one batch span many — so all of them are covered.
|
|
func EnsureListingFresh(ctx context.Context, mc *MetaCache, client filer_pb.FilerClient, dirPath util.FullPath, startName string) error {
|
|
ranges := mc.staleRangesAhead(dirPath, startName)
|
|
if len(ranges) == 0 {
|
|
return nil
|
|
}
|
|
ctx, cancel := context.WithTimeout(ctx, sectionRefreshTimeout)
|
|
defer cancel()
|
|
for _, r := range ranges {
|
|
if err := mc.refreshSection(ctx, client, dirPath, r.lo, r.hi); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (mc *MetaCache) refreshSection(ctx context.Context, client filer_pb.FilerClient, dirPath util.FullPath, lo, hi string) error {
|
|
_, err, _ := mc.visitGroup.Do(string(dirPath)+"\x00section\x00"+lo, func() (interface{}, error) {
|
|
if !mc.rangeStale(dirPath, lo) {
|
|
return nil, nil
|
|
}
|
|
entries, snapshotTsNs, err := mc.listFilerRange(ctx, client, dirPath, lo, hi)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return nil, mc.enqueueAndWait(ctx, metadataApplyRequest{
|
|
kind: metadataSectionRefresh,
|
|
buildPath: dirPath,
|
|
refresh: §ionRefresh{lo: lo, hi: hi, entries: entries, snapshotTsNs: snapshotTsNs},
|
|
})
|
|
})
|
|
return err
|
|
}
|
|
|
|
// listFilerRange reads [lo, hi) from the filer at one snapshot, paging by
|
|
// section-sized batches.
|
|
func (mc *MetaCache) listFilerRange(ctx context.Context, client filer_pb.FilerClient, dirPath util.FullPath, lo, hi string) (entries []*filer.Entry, snapshotTsNs int64, err error) {
|
|
startFrom, includeStart := lo, lo != ""
|
|
for {
|
|
var page []*filer.Entry
|
|
var pageCount int
|
|
var last string
|
|
done := false
|
|
err = client.WithFilerClient(false, func(sc filer_pb.SeaweedFilerClient) error {
|
|
// reset in case a failover retry re-runs a partly streamed page
|
|
page, pageCount, last, done = nil, 0, "", false
|
|
ts, listErr := filer_pb.DoSeaweedListWithSnapshot(ctx, sc, dirPath, "", func(pbEntry *filer_pb.Entry, isLast bool) error {
|
|
pageCount++
|
|
last = pbEntry.Name
|
|
if hi != "" && pbEntry.Name >= hi {
|
|
done = true
|
|
}
|
|
if done {
|
|
return nil
|
|
}
|
|
if !mc.includeSystemEntries && IsHiddenSystemEntry(string(dirPath), pbEntry.Name) {
|
|
return nil
|
|
}
|
|
page = append(page, filer.FromPbEntry(string(dirPath), pbEntry))
|
|
return nil
|
|
}, startFrom, includeStart, dirSectionSize, snapshotTsNs)
|
|
if listErr != nil {
|
|
return listErr
|
|
}
|
|
if snapshotTsNs == 0 {
|
|
snapshotTsNs = ts
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
entries = append(entries, page...)
|
|
if len(entries) > sectionRefreshMaxEntries {
|
|
return nil, 0, ErrRefreshRangeTooLarge
|
|
}
|
|
if done || pageCount < dirSectionSize {
|
|
return entries, snapshotTsNs, nil
|
|
}
|
|
startFrom, includeStart = last, false
|
|
}
|
|
}
|
|
|
|
// applySectionRefreshNow reconciles one section against a filer listing of its
|
|
// range, then marks it fresh. Runs on the apply loop; mutations go through the
|
|
// version gate so the listing cannot roll back a newer applied event, and
|
|
// pinned local-only entries (deferred creates not yet on the filer) survive.
|
|
func (mc *MetaCache) applySectionRefreshNow(ctx context.Context, dirPath util.FullPath, r *sectionRefresh) error {
|
|
lo, hi, snapshotTsNs := r.lo, r.hi, r.snapshotTsNs
|
|
|
|
// A build wipes and repopulates the store off-loop; reconciling against
|
|
// it would sweep children the build already inserted and publish the
|
|
// directory incomplete. The staleness dies with the build's fresh table.
|
|
if mc.isBuildingDir(dirPath) {
|
|
return nil
|
|
}
|
|
|
|
mc.Lock()
|
|
defer mc.Unlock()
|
|
|
|
// With no per-entry versions, only the section floor fences this work; a
|
|
// range the rebuilt or re-split table no longer has gets no floor, so it
|
|
// must not touch the store either. The lock is held through the floor
|
|
// install below, so the check cannot go stale.
|
|
sl := mc.dirSections[dirPath]
|
|
if sl == nil || !sl.hasRange(lo, hi) {
|
|
return nil
|
|
}
|
|
|
|
fetchedNames := make([]string, 0, len(r.entries))
|
|
fetched := make(map[string]struct{}, len(r.entries))
|
|
for _, entry := range r.entries {
|
|
fetchedNames = append(fetchedNames, entry.Name())
|
|
fetched[entry.Name()] = struct{}{}
|
|
if snapshotTsNs == 0 {
|
|
// A pre-upgrade filer stamps no snapshot, leaving nothing to
|
|
// order against: only fill gaps, so a concurrently applied event
|
|
// can never be rolled back.
|
|
if mc.entryExistsLocked(ctx, entry.FullPath) {
|
|
continue
|
|
}
|
|
if _, tombstone := mc.getEntryVersionRecordLocked(ctx, entry.FullPath); tombstone {
|
|
continue
|
|
}
|
|
if err := mc.localStore.InsertEntry(ctx, entry); err != nil {
|
|
return err
|
|
}
|
|
mc.setEntryVersionLocked(ctx, entry.FullPath, 0)
|
|
continue
|
|
}
|
|
if mc.entryVersionBlocksLocked(ctx, entry.FullPath, snapshotTsNs, false) {
|
|
continue
|
|
}
|
|
// An unversioned marker would bypass the section floor, so it cannot
|
|
// outlive the snapshot write that replaces its content — but pinned
|
|
// local-only state stays authoritative and is not replaced at all.
|
|
_, _, unversioned := mc.entryVersionRecordLocked(ctx, entry.FullPath)
|
|
if unversioned {
|
|
if existing, findErr := mc.localStore.FindEntry(ctx, entry.FullPath); findErr == nil && existing != nil && mc.pinnedChildFn != nil && mc.pinnedChildFn(existing) {
|
|
continue
|
|
}
|
|
}
|
|
// no per-entry version: the section floor set below covers the range
|
|
if err := mc.localStore.InsertEntry(ctx, entry); err != nil {
|
|
return err
|
|
}
|
|
if unversioned {
|
|
// only once the write landed: if it fails, the old content keeps
|
|
// the marker, and with it the right to be corrected by any event
|
|
mc.clearEntryVersionLocked(ctx, entry.FullPath)
|
|
}
|
|
}
|
|
|
|
// Deletions need the snapshot as an ordering reference; without one a
|
|
// name created after the listing would be swept away.
|
|
if snapshotTsNs != 0 {
|
|
var vanished []*filer.Entry
|
|
if _, err := mc.localStore.ListDirectoryEntries(ctx, dirPath, lo, true, math.MaxInt64, func(entry *filer.Entry) (bool, error) {
|
|
if hi != "" && entry.Name() >= hi {
|
|
return false, nil
|
|
}
|
|
if _, found := fetched[entry.Name()]; !found {
|
|
vanished = append(vanished, entry)
|
|
}
|
|
return true, nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
for _, entry := range vanished {
|
|
if mc.pinnedChildFn != nil && mc.pinnedChildFn(entry) {
|
|
continue
|
|
}
|
|
if mc.entryVersionBlocksLocked(ctx, entry.FullPath, snapshotTsNs, true) {
|
|
continue
|
|
}
|
|
if err := mc.localStore.DeleteEntry(ctx, entry.FullPath); err != nil {
|
|
return err
|
|
}
|
|
mc.clearEntryVersionLocked(ctx, entry.FullPath)
|
|
}
|
|
}
|
|
|
|
// The floor fences the whole range, absent names included; an unversioned
|
|
// listing sets none and the section stays stale, its lookups reading
|
|
// through, with no further re-listing attempts.
|
|
sl.completeRefresh(lo, hi, fetchedNames, snapshotTsNs)
|
|
return nil
|
|
}
|