blob: ff194a2eea59b923f174f203b3b033a0fa2953b8 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package server
// This file defines methods for consistently destroying unreferenced database
// stores by using a garbage collection log keeping dbInfos for inactive (not
// fully created or deleted) databases.
import (
"fmt"
"v.io/v23/context"
wire "v.io/v23/services/syncbase"
"v.io/v23/verror"
"v.io/v23/vom"
"v.io/x/lib/vlog"
"v.io/x/ref/services/syncbase/common"
"v.io/x/ref/services/syncbase/store"
storeutil "v.io/x/ref/services/syncbase/store/util"
)
func dbGCKey(path string) string {
return common.JoinKeyParts(common.DbGCPrefix, path)
}
// putDbGCEntry puts a dbInfo into the garbage collection log. It should be
// used before creating a new database and when marking a database for
// destruction (deleting the dbInfo from active databases).
func putDbGCEntry(ctx *context.T, stw store.StoreWriter, dbInfo *DbInfo) error {
return store.Put(ctx, stw, dbGCKey(dbInfo.RootDir), dbInfo)
}
// delDbGCEntry removes a dbInfo from the garbage collection log. It should
// be used after successfully destroying the database and when finalizing
// database creation (putting the dbInfo into active databases).
func delDbGCEntry(ctx *context.T, stw store.StoreWriter, dbInfo *DbInfo) error {
return store.Delete(ctx, stw, dbGCKey(dbInfo.RootDir))
}
// deleteDatabaseEntry marks a database for destruction by moving its dbInfo
// record from active databases into the garbage collection log. It returns
// the dbInfo that can be passed to finalizeDatabaseDestroy.
func deleteDatabaseEntry(ctx *context.T, tx store.Transaction, dbId wire.Id) (*DbInfo, error) {
dbInfo, err := getDbInfo(ctx, tx, dbId)
if err != nil {
return nil, err
}
if err := putDbGCEntry(ctx, tx, dbInfo); err != nil {
return nil, err
}
if err := delDbInfo(ctx, tx, dbId); err != nil {
return nil, err
}
return dbInfo, nil
}
// finalizeDatabaseDestroy attempts to close (if stRef is not nil) and destroy
// the database store. If successful, it removes the dbInfo record from the
// garbage collection log.
func finalizeDatabaseDestroy(ctx *context.T, stw store.StoreWriter, dbInfo *DbInfo, stRef store.Store) error {
vlog.VI(2).Infof("server: destroying store at %q for database %v (closing: %v)", dbInfo.RootDir, dbInfo.Id, stRef != nil)
if stRef != nil {
// TODO(ivanpi): Safer to crash syncbased on Close() failure? Otherwise,
// already running calls might continue using the database. Alternatively,
// explicitly cancel running calls in Destroy.
if err := stRef.Close(); err != nil {
return wrapGCError(dbInfo, err)
}
}
if err := storeutil.DestroyStore(dbInfo.Engine, dbInfo.RootDir); err != nil {
return wrapGCError(dbInfo, err)
}
if err := delDbGCEntry(ctx, stw, dbInfo); err != nil {
return wrapGCError(dbInfo, err)
}
return nil
}
// runGCInactiveDatabases iterates over the garbage collection log, attempting
// to destroy each database store, removing dbInfo records from the log when
// destruction is successful.
// NOTE: runGCInactiveDatabases should not be run in parallel with database
// creation or deletion since it can cause spurious failures (e.g. garbage
// collecting a database that is being created).
// TODO(ivanpi): Consider adding mutex to allow running GC concurrently.
func runGCInactiveDatabases(ctx *context.T, st store.Store) error {
vlog.VI(2).Infof("server: starting garbage collection of inactive databases")
total := 0
deleted := 0
gcIt := st.Scan(common.ScanPrefixArgs(common.DbGCPrefix, ""))
var diBytes []byte
for gcIt.Advance() {
diBytes = gcIt.Value(diBytes)
var dbInfo DbInfo
if err := vom.Decode(diBytes, &dbInfo); err != nil {
verror.New(verror.ErrInternal, ctx, err)
}
total++
if err := finalizeDatabaseDestroy(ctx, st, &dbInfo, nil); err != nil {
vlog.Error(err)
} else {
deleted++
}
}
if err := gcIt.Err(); err != nil {
return verror.New(verror.ErrInternal, ctx, err)
}
vlog.VI(2).Infof("server: garbage collected %d out of %d inactive databases", deleted, total)
return nil
}
func wrapGCError(dbInfo *DbInfo, err error) error {
return fmt.Errorf("failed to destroy store at %q for database %v: %v", dbInfo.RootDir, dbInfo.Id, err)
}