mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-23 22:40:39 +02:00
* shell: volume.fsck reads the filer from one store snapshot when it can volume.fsck collects the filer's chunk references by walking directory by directory, so each directory is seen at a different time. A file renamed from a directory not yet listed into one already listed is never seen, its chunks look like orphans, and -reallyDeleteFromVolume deletes live data. Checkpoint rotation and write-to-tmp-then-rename hit this. Add an optional filer store capability, EntryScanner, that visits every entry below a directory from one consistent snapshot. The SQL stores implement it with one REPEATABLE READ read-only transaction read in primary-key pages, so a concurrent rename is seen under exactly one of its names. Key-value rows are skipped by their dirhash, a bucket directory whose table is missing fails the scan (from the snapshot it cannot be told from one dropped a moment ago), and hard links are resolved in the same transaction. SQLite offers it only in WAL mode, on its own connection. TraverseBfsMetadata serves the snapshot when asked via request metadata sw-traverse-mode=snapshot and reports the mode in a response header; without the metadata it walks as before, so peer bootstrap and admin export are unchanged. Snapshot responses carry no entry_ts_ns, whose per-entry lookup would cost more than the scan. volume.fsck requests the snapshot and falls back to the walk on an older filer or a store that cannot scan. * filer, shell: fail a scan that cannot start, not fall back to the walk A transient BeginTx failure and a preliminary GetEntry error were treated like a missing capability or missing root, so fsck would take the directory walk - the very path that misses renamed-away entries. Only a genuinely unsupported scan or a confirmed-absent root may fall back. * filer: only snapshot-listed bucket names keep a missing table fatal A cached-only name, whose bucket may have been deleted between copying the cache and opening the snapshot, is skipped when its table is gone. * filer: roll back to a savepoint before skipping a cached-only table On PostgreSQL a failed statement aborts the snapshot transaction, so ignoring a missing-table error without the savepoint still left every later cached-only table unreadable. --------- Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
497 lines
16 KiB
Go
497 lines
16 KiB
Go
package filer
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/viant/ptrie"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/stats"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
)
|
|
|
|
var (
|
|
_ = VirtualFilerStore(&FilerStoreWrapper{})
|
|
_ = Debuggable(&FilerStoreWrapper{})
|
|
_ = EntryScanner(&FilerStoreWrapper{})
|
|
)
|
|
|
|
type VirtualFilerStore interface {
|
|
FilerStore
|
|
DeleteHardLink(ctx context.Context, hardLinkId HardLinkId) error
|
|
DeleteOneEntry(ctx context.Context, entry *Entry) error
|
|
InsertEntryKnownAbsent(ctx context.Context, entry *Entry) error
|
|
AddPathSpecificStore(path string, storeId string, store FilerStore)
|
|
OnBucketCreation(bucket string)
|
|
OnBucketDeletion(bucket string)
|
|
CanDropWholeBucket() bool
|
|
SameActualStore(a, b util.FullPath) bool
|
|
}
|
|
|
|
type FilerStoreWrapper struct {
|
|
defaultStore FilerStore
|
|
pathToStore ptrie.Trie[string]
|
|
storeIdToStore map[string]FilerStore
|
|
hasPathSpecificStore bool // fast check to skip MatchPrefix when no path-specific stores
|
|
}
|
|
|
|
func NewFilerStoreWrapper(store FilerStore) *FilerStoreWrapper {
|
|
if innerStore, ok := store.(*FilerStoreWrapper); ok {
|
|
return innerStore
|
|
}
|
|
return &FilerStoreWrapper{
|
|
defaultStore: store,
|
|
pathToStore: ptrie.New[string](),
|
|
storeIdToStore: make(map[string]FilerStore),
|
|
}
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) CanDropWholeBucket() bool {
|
|
if ba, ok := fsw.defaultStore.(BucketAware); ok {
|
|
return ba.CanDropWholeBucket()
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) OnBucketCreation(bucket string) {
|
|
for _, store := range fsw.storeIdToStore {
|
|
if ba, ok := store.(BucketAware); ok {
|
|
ba.OnBucketCreation(bucket)
|
|
}
|
|
}
|
|
if ba, ok := fsw.defaultStore.(BucketAware); ok {
|
|
ba.OnBucketCreation(bucket)
|
|
}
|
|
}
|
|
func (fsw *FilerStoreWrapper) OnBucketDeletion(bucket string) {
|
|
for _, store := range fsw.storeIdToStore {
|
|
if ba, ok := store.(BucketAware); ok {
|
|
ba.OnBucketDeletion(bucket)
|
|
}
|
|
}
|
|
if ba, ok := fsw.defaultStore.(BucketAware); ok {
|
|
ba.OnBucketDeletion(bucket)
|
|
}
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) AddPathSpecificStore(path string, storeId string, store FilerStore) {
|
|
fsw.storeIdToStore[storeId] = NewFilerStorePathTranslator(path, store)
|
|
err := fsw.pathToStore.Put([]byte(path), storeId)
|
|
if err != nil {
|
|
glog.Fatalf("put path specific store: %v", err)
|
|
}
|
|
fsw.hasPathSpecificStore = true
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) getActualStore(path util.FullPath) (store FilerStore) {
|
|
store = fsw.defaultStore
|
|
// Fast path: skip MatchPrefix if no path-specific stores are configured (common case)
|
|
if !fsw.hasPathSpecificStore {
|
|
return
|
|
}
|
|
if path == "/" || path == "//" {
|
|
return
|
|
}
|
|
var storeId string
|
|
fsw.pathToStore.MatchPrefix([]byte(path), func(key []byte, value string) bool {
|
|
storeId = value
|
|
return false
|
|
})
|
|
if storeId != "" {
|
|
store = fsw.storeIdToStore[storeId]
|
|
}
|
|
return
|
|
}
|
|
|
|
// SameActualStore reports whether two paths resolve to the same underlying
|
|
// store. When path-specific stores are configured, different subtrees may
|
|
// be served by different backends.
|
|
func (fsw *FilerStoreWrapper) SameActualStore(a, b util.FullPath) bool {
|
|
return fsw.getActualStore(a) == fsw.getActualStore(b)
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) getDefaultStore() (store FilerStore) {
|
|
return fsw.defaultStore
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) GetName() string {
|
|
return fsw.getDefaultStore().GetName()
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) Initialize(configuration util.Configuration, prefix string) error {
|
|
return fsw.getDefaultStore().Initialize(configuration, prefix)
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) InsertEntry(ctx context.Context, entry *Entry) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
ctx = context.WithoutCancel(ctx)
|
|
actualStore := fsw.getActualStore(entry.FullPath)
|
|
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "insert").Inc()
|
|
start := time.Now()
|
|
defer func() {
|
|
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "insert").Observe(time.Since(start).Seconds())
|
|
}()
|
|
|
|
filer_pb.BeforeEntrySerialization(entry.GetChunks())
|
|
normalizeEntryMimeForStore(entry)
|
|
delete(entry.Extended, ExtKeepRemoteObjectKey)
|
|
|
|
if len(entry.HardLinkId) > 0 {
|
|
glog.V(4).InfofCtx(ctx, "InsertEntry %s has HardLinkId %x counter=%d",
|
|
entry.FullPath, entry.HardLinkId, entry.HardLinkCounter)
|
|
}
|
|
|
|
if err := fsw.handleUpdateToHardLinks(ctx, entry); err != nil {
|
|
return err
|
|
}
|
|
|
|
return actualStore.InsertEntry(ctx, entry)
|
|
}
|
|
|
|
// InsertEntryKnownAbsent skips the pre-insert FindEntry path when the caller has
|
|
// already established that the target path does not exist.
|
|
func (fsw *FilerStoreWrapper) InsertEntryKnownAbsent(ctx context.Context, entry *Entry) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
ctx = context.WithoutCancel(ctx)
|
|
actualStore := fsw.getActualStore(entry.FullPath)
|
|
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "insert").Inc()
|
|
start := time.Now()
|
|
defer func() {
|
|
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "insert").Observe(time.Since(start).Seconds())
|
|
}()
|
|
|
|
filer_pb.BeforeEntrySerialization(entry.GetChunks())
|
|
normalizeEntryMimeForStore(entry)
|
|
delete(entry.Extended, ExtKeepRemoteObjectKey)
|
|
|
|
if len(entry.HardLinkId) > 0 {
|
|
glog.V(4).InfofCtx(ctx, "InsertEntryKnownAbsent %s has HardLinkId %x counter=%d",
|
|
entry.FullPath, entry.HardLinkId, entry.HardLinkCounter)
|
|
if err := fsw.setHardLink(ctx, entry); err != nil {
|
|
return fmt.Errorf("setHardLink %x: %v", entry.HardLinkId, err)
|
|
}
|
|
}
|
|
|
|
return actualStore.InsertEntry(ctx, entry)
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) UpdateEntry(ctx context.Context, entry *Entry) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
ctx = context.WithoutCancel(ctx)
|
|
actualStore := fsw.getActualStore(entry.FullPath)
|
|
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "update").Inc()
|
|
start := time.Now()
|
|
defer func() {
|
|
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "update").Observe(time.Since(start).Seconds())
|
|
}()
|
|
|
|
filer_pb.BeforeEntrySerialization(entry.GetChunks())
|
|
normalizeEntryMimeForStore(entry)
|
|
delete(entry.Extended, ExtKeepRemoteObjectKey)
|
|
|
|
if len(entry.HardLinkId) > 0 {
|
|
glog.V(4).InfofCtx(ctx, "UpdateEntry %s has HardLinkId %x counter=%d",
|
|
entry.FullPath, entry.HardLinkId, entry.HardLinkCounter)
|
|
}
|
|
|
|
if err := fsw.handleUpdateToHardLinks(ctx, entry); err != nil {
|
|
return err
|
|
}
|
|
|
|
return actualStore.UpdateEntry(ctx, entry)
|
|
}
|
|
|
|
func normalizeEntryMimeForStore(entry *Entry) {
|
|
if entry == nil {
|
|
return
|
|
}
|
|
if !entry.IsDirectory() && entry.Mime == "application/octet-stream" {
|
|
entry.Mime = ""
|
|
}
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) FindEntry(ctx context.Context, fp util.FullPath) (entry *Entry, err error) {
|
|
ctx = context.WithoutCancel(ctx)
|
|
actualStore := fsw.getActualStore(fp)
|
|
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "find").Inc()
|
|
start := time.Now()
|
|
defer func() {
|
|
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "find").Observe(time.Since(start).Seconds())
|
|
}()
|
|
|
|
entry, err = actualStore.FindEntry(ctx, fp)
|
|
// glog.V(4).Infof("FindEntry %s: %v", fp, err)
|
|
if err != nil {
|
|
if fsw.CanDropWholeBucket() && strings.Contains(err.Error(), "Table") && strings.Contains(err.Error(), "doesn't exist") {
|
|
err = filer_pb.ErrNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
fsw.maybeReadHardLink(ctx, entry)
|
|
|
|
filer_pb.AfterEntryDeserialization(entry.GetChunks())
|
|
return
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) DeleteEntry(ctx context.Context, fp util.FullPath) (err error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
ctx = context.WithoutCancel(ctx)
|
|
actualStore := fsw.getActualStore(fp)
|
|
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "delete").Inc()
|
|
start := time.Now()
|
|
defer func() {
|
|
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "delete").Observe(time.Since(start).Seconds())
|
|
}()
|
|
|
|
existingEntry, findErr := fsw.FindEntry(ctx, fp)
|
|
if findErr == filer_pb.ErrNotFound || existingEntry == nil {
|
|
return nil
|
|
}
|
|
if len(existingEntry.HardLinkId) != 0 {
|
|
// remove hard link
|
|
op := ctx.Value("OP")
|
|
if op != "MV" {
|
|
glog.V(4).InfofCtx(ctx, "DeleteHardLink %s", existingEntry.FullPath)
|
|
if hlErr := fsw.DeleteHardLink(ctx, existingEntry.HardLinkId); hlErr != nil {
|
|
// Log but continue: the directory entry must be removed
|
|
// even if hard link counter cleanup fails, otherwise the
|
|
// parent directory cannot be removed (rmdir ENOTEMPTY).
|
|
glog.Warningf("DeleteHardLink %s (id %x): %v", existingEntry.FullPath, existingEntry.HardLinkId, hlErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
return actualStore.DeleteEntry(ctx, fp)
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) DeleteOneEntry(ctx context.Context, existingEntry *Entry) (err error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
ctx = context.WithoutCancel(ctx)
|
|
actualStore := fsw.getActualStore(existingEntry.FullPath)
|
|
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "delete").Inc()
|
|
start := time.Now()
|
|
defer func() {
|
|
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "delete").Observe(time.Since(start).Seconds())
|
|
}()
|
|
|
|
if len(existingEntry.HardLinkId) != 0 {
|
|
// remove hard link
|
|
op := ctx.Value("OP")
|
|
if op != "MV" {
|
|
glog.V(4).InfofCtx(ctx, "DeleteHardLink %s", existingEntry.FullPath)
|
|
if hlErr := fsw.DeleteHardLink(ctx, existingEntry.HardLinkId); hlErr != nil {
|
|
// Log the hard link cleanup error but continue to delete
|
|
// the directory entry. If we return early here, the entry
|
|
// remains in the store and the parent directory cannot be
|
|
// removed (rmdir returns ENOTEMPTY).
|
|
glog.Warningf("DeleteHardLink %s (id %x): %v", existingEntry.FullPath, existingEntry.HardLinkId, hlErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
return actualStore.DeleteEntry(ctx, existingEntry.FullPath)
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) DeleteFolderChildren(ctx context.Context, fp util.FullPath) (err error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
ctx = context.WithoutCancel(ctx)
|
|
actualStore := fsw.getActualStore(fp + "/")
|
|
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "deleteFolderChildren").Inc()
|
|
start := time.Now()
|
|
defer func() {
|
|
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "deleteFolderChildren").Observe(time.Since(start).Seconds())
|
|
}()
|
|
|
|
return actualStore.DeleteFolderChildren(ctx, fp)
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) ListDirectoryEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int64, eachEntryFunc ListEachEntryFunc) (string, error) {
|
|
ctx = context.WithoutCancel(ctx)
|
|
actualStore := fsw.getActualStore(dirPath + "/")
|
|
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "list").Inc()
|
|
start := time.Now()
|
|
defer func() {
|
|
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "list").Observe(time.Since(start).Seconds())
|
|
}()
|
|
|
|
// glog.V(4).Infof("ListDirectoryEntries %s from %s limit %d", dirPath, startFileName, limit)
|
|
return actualStore.ListDirectoryEntries(ctx, dirPath, startFileName, includeStartFile, limit, func(entry *Entry) (bool, error) {
|
|
fsw.maybeReadHardLink(ctx, entry)
|
|
filer_pb.AfterEntryDeserialization(entry.GetChunks())
|
|
return eachEntryFunc(entry)
|
|
})
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) ListDirectoryPrefixedEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int64, prefix string, eachEntryFunc ListEachEntryFunc) (lastFileName string, err error) {
|
|
ctx = context.WithoutCancel(ctx)
|
|
actualStore := fsw.getActualStore(dirPath + "/")
|
|
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "prefixList").Inc()
|
|
start := time.Now()
|
|
defer func() {
|
|
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "prefixList").Observe(time.Since(start).Seconds())
|
|
}()
|
|
if limit > math.MaxInt32-1 {
|
|
limit = math.MaxInt32 - 1
|
|
}
|
|
// glog.V(4).Infof("ListDirectoryPrefixedEntries %s from %s prefix %s limit %d", dirPath, startFileName, prefix, limit)
|
|
adjustedEntryFunc := func(entry *Entry) (bool, error) {
|
|
fsw.maybeReadHardLink(ctx, entry)
|
|
filer_pb.AfterEntryDeserialization(entry.GetChunks())
|
|
return eachEntryFunc(entry)
|
|
}
|
|
lastFileName, err = actualStore.ListDirectoryPrefixedEntries(ctx, dirPath, startFileName, includeStartFile, limit, prefix, adjustedEntryFunc)
|
|
if err == ErrUnsupportedListDirectoryPrefixed {
|
|
lastFileName, err = fsw.prefixFilterEntries(ctx, dirPath, startFileName, includeStartFile, limit, prefix, adjustedEntryFunc)
|
|
}
|
|
return lastFileName, err
|
|
}
|
|
|
|
// ScanEntries delegates to the default store. With path-specific stores
|
|
// configured, entries below dirPath could live in several stores and one
|
|
// snapshot per store is not one snapshot, so the scan is declined. Hard links
|
|
// are resolved inside the snapshot; a store error fails the scan (the per-path
|
|
// copy can be stale), a missing record falls back to it as listings do.
|
|
func (fsw *FilerStoreWrapper) ScanEntries(ctx context.Context, dirPath util.FullPath, fn func(ctx context.Context, entry *Entry) error) error {
|
|
if fsw.hasPathSpecificStore {
|
|
return ErrUnsupportedEntryScan
|
|
}
|
|
scanner, ok := fsw.defaultStore.(EntryScanner)
|
|
if !ok {
|
|
return ErrUnsupportedEntryScan
|
|
}
|
|
stats.FilerStoreCounter.WithLabelValues(fsw.defaultStore.GetName(), "scan").Inc()
|
|
start := time.Now()
|
|
defer func() {
|
|
stats.FilerStoreHistogram.WithLabelValues(fsw.defaultStore.GetName(), "scan").Observe(time.Since(start).Seconds())
|
|
}()
|
|
return scanner.ScanEntries(ctx, dirPath, func(scanCtx context.Context, entry *Entry) error {
|
|
if err := fsw.maybeReadHardLink(scanCtx, entry); err != nil && !errors.Is(err, ErrKvNotFound) {
|
|
return fmt.Errorf("resolve hard link of %s: %w", entry.FullPath, err)
|
|
}
|
|
filer_pb.AfterEntryDeserialization(entry.GetChunks())
|
|
return fn(scanCtx, entry)
|
|
})
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) prefixFilterEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int64, prefix string, eachEntryFunc ListEachEntryFunc) (lastFileName string, err error) {
|
|
actualStore := fsw.getActualStore(dirPath + "/")
|
|
|
|
if prefix == "" {
|
|
return actualStore.ListDirectoryEntries(ctx, dirPath, startFileName, includeStartFile, limit, eachEntryFunc)
|
|
}
|
|
|
|
var notPrefixed []*Entry
|
|
lastFileName, err = actualStore.ListDirectoryEntries(ctx, dirPath, startFileName, includeStartFile, limit, func(entry *Entry) (bool, error) {
|
|
notPrefixed = append(notPrefixed, entry)
|
|
return true, nil
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
count := int64(0)
|
|
for count < limit && len(notPrefixed) > 0 {
|
|
for _, entry := range notPrefixed {
|
|
if strings.HasPrefix(entry.Name(), prefix) {
|
|
count++
|
|
res, resErr := eachEntryFunc(entry)
|
|
|
|
if resErr != nil {
|
|
err = fmt.Errorf("failed to process eachEntryFunc for entry %q: %w", entry.Name(), resErr)
|
|
return
|
|
}
|
|
|
|
if !res {
|
|
return
|
|
}
|
|
if count >= limit {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if count < limit && lastFileName < prefix {
|
|
notPrefixed = notPrefixed[:0]
|
|
lastFileName, err = actualStore.ListDirectoryEntries(ctx, dirPath, lastFileName, false, limit, func(entry *Entry) (bool, error) {
|
|
notPrefixed = append(notPrefixed, entry)
|
|
return true, nil
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) BeginTransaction(ctx context.Context) (context.Context, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
ctx = context.WithoutCancel(ctx)
|
|
return fsw.getDefaultStore().BeginTransaction(ctx)
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) CommitTransaction(ctx context.Context) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
ctx = context.WithoutCancel(ctx)
|
|
return fsw.getDefaultStore().CommitTransaction(ctx)
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) RollbackTransaction(ctx context.Context) error {
|
|
ctx = context.WithoutCancel(ctx)
|
|
return fsw.getDefaultStore().RollbackTransaction(ctx)
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) Shutdown() {
|
|
fsw.getDefaultStore().Shutdown()
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) KvPut(ctx context.Context, key []byte, value []byte) (err error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
ctx = context.WithoutCancel(ctx)
|
|
return fsw.getDefaultStore().KvPut(ctx, key, value)
|
|
}
|
|
func (fsw *FilerStoreWrapper) KvGet(ctx context.Context, key []byte) (value []byte, err error) {
|
|
ctx = context.WithoutCancel(ctx)
|
|
return fsw.getDefaultStore().KvGet(ctx, key)
|
|
}
|
|
func (fsw *FilerStoreWrapper) KvDelete(ctx context.Context, key []byte) (err error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
ctx = context.WithoutCancel(ctx)
|
|
return fsw.getDefaultStore().KvDelete(ctx, key)
|
|
}
|
|
|
|
func (fsw *FilerStoreWrapper) Debug(writer io.Writer) {
|
|
if debuggable, ok := fsw.getDefaultStore().(Debuggable); ok {
|
|
debuggable.Debug(writer)
|
|
}
|
|
}
|