diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go index 079e335fa6fb..cce30f526a9f 100644 --- a/core/rawdb/accessors_metadata.go +++ b/core/rawdb/accessors_metadata.go @@ -90,15 +90,47 @@ type crashList struct { const crashesToKeep = 10 -// PushUncleanShutdownMarker appends a new unclean shutdown marker and returns -// the previous data +func StartUncleanShutdownMarker(name string, db ethdb.KeyValueStore) { + switch name { + case "les.client": + db.(*nofreezedb).isChainDb = false + return + case "lightchaindata": + db.(*nofreezedb).isChainDb = true + case "chaindata": + break + case "": + break + default: + return + } + if uncleanShutdowns, discards, err := PushUncleanShutdownMarker(db); err != nil { + log.Error("Could not update unclean-shutdown-marker list", "error", err) + } else { + if discards > 0 { + log.Warn("Old unclean shutdowns found", "count", discards) + } + for _, tstamp := range uncleanShutdowns { + t := time.Unix(int64(tstamp), 0) + log.Warn("Unclean shutdown detected", "booted", t, "age", common.PrettyAge(t)) + } + } + return +} + +// PushUncleanShutdownMarker appends a new unclean shutdown marker and starts a goroutine +// to update the unclean shutdown marker every five minutes. It returns +// the previous unclean shutdown // - a list of timestamps // - a count of how many old unclean-shutdowns have been discarded func PushUncleanShutdownMarker(db ethdb.KeyValueStore) ([]uint64, uint64, error) { var uncleanShutdowns crashList // Read old data if data, err := db.Get(uncleanShutdownKey); err != nil { - log.Warn("Error reading unclean shutdown markers", "error", err) + // don't want to warn if there were no unclean shutdowns + if err.Error() != "leveldb: not found" { + log.Warn("Error reading unclean shutdown markers", "error", err) + } } else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil { return nil, 0, err } @@ -118,6 +150,11 @@ func PushUncleanShutdownMarker(db ethdb.KeyValueStore) ([]uint64, uint64, error) log.Warn("Failed to write unclean-shutdown marker", "err", err) return nil, 0, err } + if frdb, ok := db.(*freezerdb); ok { + go UpdateUncleanShutdownMarker(db, frdb.stopUncleanMarkerUpdateCh) + } else if nfrdb, ok := db.(*nofreezedb); ok { + go UpdateUncleanShutdownMarker(db, nfrdb.stopUncleanMarkerUpdateCh) + } return previous, discarded, nil } @@ -138,3 +175,34 @@ func PopUncleanShutdownMarker(db ethdb.KeyValueStore) { log.Warn("Failed to clear unclean-shutdown marker", "err", err) } } + +// UpdateUncleanShutdownMarker updates the current unclean shutdown marker +// every 5 minutes +func UpdateUncleanShutdownMarker(db ethdb.KeyValueStore, stopUncleanShutdownUpdateCh chan bool) { + var uncleanShutdowns crashList + // Read old data + if previous, err := db.Get(uncleanShutdownKey); err != nil { + log.Warn("Error reading unclean shutdown markers", "error", err) + } else if err := rlp.DecodeBytes(previous, &uncleanShutdowns); err != nil { + log.Error("Error decoding unclean shutdown markers", "error", err) // Should mos def _not_ happen + } + l := len(uncleanShutdowns.Recent) + if l == 0 { + l++ + } + // update marker every five minutes + ticker := time.NewTicker(300 * time.Second) + defer func() { ticker.Stop() }() + for { + select { + case <-ticker.C: + uncleanShutdowns.Recent[l-1] = uint64(time.Now().Unix()) + data, _ := rlp.EncodeToBytes(uncleanShutdowns) + if err := db.Put(uncleanShutdownKey, data); err != nil { + log.Warn("Failed to update unclean-shutdown marker", "err", err) + } + case <-stopUncleanShutdownUpdateCh: + return + } + } +} diff --git a/core/rawdb/database.go b/core/rawdb/database.go index c8bfdbace14e..f4d86117a646 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -33,15 +33,20 @@ import ( ) // freezerdb is a database wrapper that enabled freezer data retrievals. +// stopUncleanMarkerUpdateCh is used to stop the unclean shutdown marker +// that updates every 5 minutes. type freezerdb struct { ethdb.KeyValueStore ethdb.AncientStore + stopUncleanMarkerUpdateCh chan bool } // Close implements io.Closer, closing both the fast key-value store as well as // the slow ancient tables. func (frdb *freezerdb) Close() error { var errs []error + frdb.stopUncleanMarkerUpdateCh <- true + PopUncleanShutdownMarker(frdb.KeyValueStore) if err := frdb.AncientStore.Close(); err != nil { errs = append(errs, err) } @@ -75,8 +80,24 @@ func (frdb *freezerdb) Freeze(threshold uint64) error { } // nofreezedb is a database wrapper that disables freezer data retrievals. +// stopUncleanMarkerUpdateCh is used to stop the unclean shutdown marker +// that updates every 5 minutes. type nofreezedb struct { ethdb.KeyValueStore + stopUncleanMarkerUpdateCh chan bool + isChainDb bool +} + +func (db *nofreezedb) Close() error { + if db.isChainDb { + db.stopUncleanMarkerUpdateCh <- true + PopUncleanShutdownMarker(db.KeyValueStore) + } + if err := db.KeyValueStore.Close(); err != nil { + return err + } + return nil + } // HasAncient returns an error as we don't have a backing chain freezer. @@ -117,9 +138,11 @@ func (db *nofreezedb) Sync() error { // NewDatabase creates a high level database on top of a given key-value data // store without a freezer moving immutable chain segments into cold storage. func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { - return &nofreezedb{ - KeyValueStore: db, + frdb := &nofreezedb{ + KeyValueStore: db, + stopUncleanMarkerUpdateCh: make(chan bool), } + return frdb } // NewDatabaseWithFreezer creates a high level database on top of a given key- @@ -204,8 +227,9 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st }() } return &freezerdb{ - KeyValueStore: db, - AncientStore: frdb, + KeyValueStore: db, + AncientStore: frdb, + stopUncleanMarkerUpdateCh: make(chan bool), }, nil } diff --git a/eth/backend.go b/eth/backend.go index 3e770fe83d92..6d41d246a8bc 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -258,19 +258,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { stack.RegisterAPIs(eth.APIs()) stack.RegisterProtocols(eth.Protocols()) stack.RegisterLifecycle(eth) - // Check for unclean shutdown - if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil { - log.Error("Could not update unclean-shutdown-marker list", "error", err) - } else { - if discards > 0 { - log.Warn("Old unclean shutdowns found", "count", discards) - } - for _, tstamp := range uncleanShutdowns { - t := time.Unix(int64(tstamp), 0) - log.Warn("Unclean shutdown detected", "booted", t, - "age", common.PrettyAge(t)) - } - } return eth, nil } @@ -559,7 +546,6 @@ func (s *Ethereum) Stop() error { s.miner.Stop() s.blockchain.Stop() s.engine.Close() - rawdb.PopUncleanShutdownMarker(s.chainDb) s.chainDb.Close() s.eventMux.Stop() diff --git a/les/client.go b/les/client.go index 1d8a2c6f9a07..519a6b0fb16b 100644 --- a/les/client.go +++ b/les/client.go @@ -181,20 +181,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { stack.RegisterAPIs(leth.APIs()) stack.RegisterProtocols(leth.Protocols()) stack.RegisterLifecycle(leth) - - // Check for unclean shutdown - if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil { - log.Error("Could not update unclean-shutdown-marker list", "error", err) - } else { - if discards > 0 { - log.Warn("Old unclean shutdowns found", "count", discards) - } - for _, tstamp := range uncleanShutdowns { - t := time.Unix(int64(tstamp), 0) - log.Warn("Unclean shutdown detected", "booted", t, - "age", common.PrettyAge(t)) - } - } return leth, nil } @@ -383,7 +369,6 @@ func (s *LightEthereum) Stop() error { s.engine.Close() s.pruner.close() s.eventMux.Stop() - rawdb.PopUncleanShutdownMarker(s.chainDb) s.chainDb.Close() s.lesDb.Close() s.wg.Wait() diff --git a/node/node.go b/node/node.go index 1e65fff1c271..ec81d97718eb 100644 --- a/node/node.go +++ b/node/node.go @@ -556,12 +556,13 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, r var db ethdb.Database var err error + fmt.Println(n.config.DataDir) if n.config.DataDir == "" { db = rawdb.NewMemoryDatabase() } else { db, err = rawdb.NewLevelDBDatabase(n.ResolvePath(name), cache, handles, namespace, readonly) } - + rawdb.StartUncleanShutdownMarker(name, db) if err == nil { db = n.wrapDatabase(db) } @@ -584,6 +585,7 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, var err error if n.config.DataDir == "" { db = rawdb.NewMemoryDatabase() + rawdb.StartUncleanShutdownMarker("", db) } else { root := n.ResolvePath(name) switch { @@ -593,8 +595,8 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, freezer = n.ResolvePath(freezer) } db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly) + rawdb.StartUncleanShutdownMarker(name, db) } - if err == nil { db = n.wrapDatabase(db) }