Skip to content

Commit

Permalink
Unfreeze even if freezing is disabled. (#2671)
Browse files Browse the repository at this point in the history
Freezing may be disabled after an indexer becomes frozen to prevent the indexer from freezing again. Unfreezing the indexer should still work even if freezing is disabled. This PR makes this possible, whereass previously an indexer could not be unfrozen if freezing was disabled.

Closes #2670

This PR also allows disk usage to be queried even when freezing is disabled.
  • Loading branch information
gammazero authored Oct 12, 2024
1 parent f10725e commit ee8dd34
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 38 deletions.
10 changes: 6 additions & 4 deletions config/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ type Indexer struct {
DHStoreClusterURLs []string
// DHStoreHttpClientTimeout is a timeout for the DHStore http client
DHStoreHttpClientTimeout Duration
// FreezeAtPercent is the percent used, of the file system that
// ValueStoreDir is on, at which to trigger the indexer to enter frozen
// mode. A zero value uses the default. A negative value disables freezing.
// FreezeAtPercent is the filesystem percent used at which to trigger the
// indexer to enter frozen mode. The indexer checks the file system of
// ValueStoreDir, Datastore.Dir, and Datastore.TmpDir and enters frozen
// mode if any one's usage is at or above FreezeAtPercent. A zero value
// uses the default. A negative value disables freezing.
FreezeAtPercent float64
// ShutdownTimeout is the duration that a graceful shutdown has to complete
// before the daemon process is terminated. If unset or zero, configures no
Expand Down Expand Up @@ -62,7 +64,7 @@ func NewIndexer() Indexer {
CacheSize: 300000,
PebbleBlockCacheSize: 1 << 30, // 1 Gi
ConfigCheckInterval: Duration(30 * time.Second),
FreezeAtPercent: 90.0,
FreezeAtPercent: 95.0,
ShutdownTimeout: 0,
ValueStoreDir: "valuestore",
ValueStoreType: "pebble",
Expand Down
60 changes: 48 additions & 12 deletions internal/freeze/freezer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package freeze

import (
"context"
"errors"
"fmt"
"strconv"
"time"
Expand All @@ -14,6 +15,8 @@ import (
var log = logging.Logger("indexer/freezer")

const (
defaultFreezeAtPercent = 99.0

frozenKey = "/freeze/frozen"

maxCheckInterval = time.Hour
Expand All @@ -27,6 +30,8 @@ const (
logCriticalRemaining = 2.0
)

var ErrNoFreeze = errors.New("freezing disabled")

// Freezer monitors disk usage and triggers a freeze if the usage reaches a
// specified threshold.
type Freezer struct {
Expand All @@ -37,6 +42,7 @@ type Freezer struct {
freezeAtStr string
freezeFunc func() error
frozen chan struct{}
noFreeze bool
trigger chan struct{}
triggerErr chan error
paths []string
Expand All @@ -48,14 +54,27 @@ func New(dirPaths []string, freezeAtPercent float64, dstore datastore.Datastore,
if err != nil {
return nil, err
}

f := &Freezer{
dstore: dstore,
freezeAt: freezeAtPercent,
freezeAtStr: fmt.Sprintf("%s%%", strconv.FormatFloat(freezeAtPercent, 'f', -1, 64)),
freezeFunc: freezeFunc,
frozen: make(chan struct{}),
paths: dirPaths,
dstore: dstore,
frozen: make(chan struct{}),
paths: dirPaths,
}

if freezeAtPercent < 0 {
f.noFreeze = true
} else {
if freezeAtPercent == 0 {
freezeAtPercent = defaultFreezeAtPercent
}

f.freezeAt = freezeAtPercent
f.freezeFunc = freezeFunc
f.freezeAtStr = fmt.Sprintf("%s%%", strconv.FormatFloat(freezeAtPercent, 'f', -1, 64))

log.Infow("freezing enabled", "freezeAt", f.freezeAtStr)
}

frozen, err := f.loadFrozenState()
if err != nil {
return nil, err
Expand All @@ -66,6 +85,10 @@ func New(dirPaths []string, freezeAtPercent float64, dstore datastore.Datastore,
return f, nil
}

if f.noFreeze {
return f, nil
}

// If not frozen, check disk usage and start monitor.
nextCheck, frozen, err := f.check()
if err != nil {
Expand All @@ -85,6 +108,9 @@ func New(dirPaths []string, freezeAtPercent float64, dstore datastore.Datastore,

// Freeze manually triggers the indexer to enter frozen mode.
func (f *Freezer) Freeze() error {
if f.noFreeze {
return ErrNoFreeze
}
select {
case f.trigger <- struct{}{}:
return <-f.triggerErr
Expand All @@ -108,6 +134,9 @@ func (f *Freezer) CheckNow() bool {
if f.Frozen() {
return true
}
if f.noFreeze {
return false
}
checkDone := make(chan struct{})
select {
case f.checkNow <- checkDone:
Expand Down Expand Up @@ -165,13 +194,20 @@ func Unfreeze(ctx context.Context, dirPaths []string, freezeAtPercent float64, d
return nil
}

for _, dirPath := range dirPaths {
du, err := disk.Usage(dirPath)
if err != nil {
return fmt.Errorf("cannot get disk usage for freeze check at path %q: %w", dirPath, err)
// If freezing enabled, then check for sufficient space to unfreeze.
if freezeAtPercent >= 0 {
if freezeAtPercent == 0 {
freezeAtPercent = defaultFreezeAtPercent
}
if du.Percent >= freezeAtPercent {
return fmt.Errorf("cannot unfreeze: disk usage above %f", freezeAtPercent)

for _, dirPath := range dirPaths {
du, err := disk.Usage(dirPath)
if err != nil {
return fmt.Errorf("cannot get disk usage for freeze check at path %q: %w", dirPath, err)
}
if du.Percent >= freezeAtPercent {
return fmt.Errorf("cannot unfreeze: disk usage above %f", freezeAtPercent)
}
}
}

Expand Down
22 changes: 19 additions & 3 deletions internal/freeze/freezer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,26 @@ func TestCheckFreeze(t *testing.T) {
}
f.Close()

// Check that freeze does not happen when enough storage.
f, err = freeze.New(dirs, du.Percent*2.0, dstore, freezeFunc)
require.NoError(t, err)
require.False(t, f.Frozen())
f.Close()

require.Zero(t, freezeCount)

// Check that freeze happens when insufficient storage.
f, err = freeze.New(dirs, du.Percent/2.0, dstore, freezeFunc)
require.NoError(t, err)
require.True(t, f.Frozen())
require.True(t, f.CheckNow())
f.Close()

require.Equal(t, 1, freezeCount)

// Check that freeze does not happen again.
f, err = freeze.New(dirs, du.Percent/2.0, dstore, freezeFunc)
require.NoError(t, err)
require.True(t, f.Frozen())
f.Close()

require.Equal(t, 1, freezeCount)

// Unfreeze no directories.
Expand All @@ -90,6 +90,16 @@ func TestCheckFreeze(t *testing.T) {
// Unfreeze already unfrozen.
err = freeze.Unfreeze(context.Background(), dirs, du.Percent*2.0, dstore)
require.NoError(t, err)

// Freeze and check that unfreeze works when freeze is disabled.
f, err = freeze.New(dirs, du.Percent/2.0, dstore, freezeFunc)
require.NoError(t, err)
require.True(t, f.Frozen())
f.Close()
require.Equal(t, 2, freezeCount)

err = freeze.Unfreeze(context.Background(), dirs, -1, dstore)
require.NoError(t, err)
}

func TestManualFreeze(t *testing.T) {
Expand Down Expand Up @@ -121,5 +131,11 @@ func TestManualFreeze(t *testing.T) {
require.NoError(t, f.Freeze())
f.Close()

f, err = freeze.New(dirs, -1, dstore, freezeFunc)
require.NoError(t, err)
require.ErrorIs(t, f.Freeze(), freeze.ErrNoFreeze)
require.True(t, f.Frozen())
f.Close()

require.Equal(t, 1, freezeCount)
}
1 change: 0 additions & 1 deletion internal/registry/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ var (
ErrMissingProviderAddr = errors.New("advertisement missing provider address")
ErrNotAllowed = errors.New("peer not allowed by policy")
ErrNoDiscovery = errors.New("discovery not available")
ErrNoFreeze = errors.New("freeze not configured")
ErrNotVerified = errors.New("provider cannot be verified")
ErrPublisherNotAllowed = errors.New("publisher not allowed by policy")
ErrTooSoon = errors.New("not enough time since previous discovery")
Expand Down
21 changes: 4 additions & 17 deletions internal/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,9 @@ func New(ctx context.Context, cfg config.Discovery, dstore datastore.Datastore,
}
}

if opts.freezeAtPercent >= 0 {
r.freezer, err = freeze.New(opts.freezeDirs, opts.freezeAtPercent, dstore, r.freeze)
if err != nil {
return nil, fmt.Errorf("cannot create freezer: %s", err)
}
r.freezer, err = freeze.New(opts.freezeDirs, opts.freezeAtPercent, dstore, r.freeze)
if err != nil {
return nil, fmt.Errorf("cannot create freezer: %s", err)
}

if cfg.PollInterval != 0 {
Expand Down Expand Up @@ -411,9 +409,7 @@ func makePollOverrideMap(poll polling, cfgPollOverrides []config.Polling) (map[p
// Close stops the registry and waits for polling to finish.
func (r *Registry) Close() {
r.closeOnce.Do(func() {
if r.freezer != nil {
r.freezer.Close()
}
r.freezer.Close()
close(r.closing)
<-r.tmpBlockCheckDone
if r.pollDone != nil {
Expand Down Expand Up @@ -1114,9 +1110,6 @@ func (r *Registry) CheckSequence(peerID peer.ID, seq uint64) error {
//
// The registry in not frozen directly, but the Freezer is triggered instead.
func (r *Registry) Freeze() error {
if r.freezer == nil {
return ErrNoFreeze
}
return r.freezer.Freeze()
}

Expand Down Expand Up @@ -1168,16 +1161,10 @@ func (r *Registry) freezeProviders() error {

// Frozen returns true if indexer is frozen.
func (r *Registry) Frozen() bool {
if r.freezer == nil {
return false
}
return r.freezer.Frozen()
}

func (r *Registry) ValueStoreUsage() (*disk.UsageStats, error) {
if r.freezer == nil {
return nil, ErrNoFreeze
}
return r.freezer.Usage()
}

Expand Down
3 changes: 2 additions & 1 deletion server/admin/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/ipfs/go-cid"
"github.com/ipni/go-indexer-core"
"github.com/ipni/storetheindex/admin/model"
"github.com/ipni/storetheindex/internal/freeze"
"github.com/ipni/storetheindex/internal/httpserver"
"github.com/ipni/storetheindex/internal/ingest"
"github.com/ipni/storetheindex/internal/registry"
Expand Down Expand Up @@ -468,7 +469,7 @@ func (h *adminHandler) freeze(w http.ResponseWriter, r *http.Request) {

err := h.reg.Freeze()
if err != nil {
if errors.Is(err, registry.ErrNoFreeze) {
if errors.Is(err, freeze.ErrNoFreeze) {
log.Infow("Cannot freeze indexer", "reason", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
Expand Down

0 comments on commit ee8dd34

Please sign in to comment.