Return with non zero exit code in case of fatal error
parent
68fed56d12
commit
d23d0a9e4f
77
blockbook.go
77
blockbook.go
|
@ -33,6 +33,10 @@ const debounceResyncMempoolMs = 1009
|
||||||
// store internal state about once every minute
|
// store internal state about once every minute
|
||||||
const storeInternalStatePeriodMs = 59699
|
const storeInternalStatePeriodMs = 59699
|
||||||
|
|
||||||
|
// exit codes from the main function
|
||||||
|
const exitCodeOK = 0
|
||||||
|
const exitCodeFatal = 255
|
||||||
|
|
||||||
var (
|
var (
|
||||||
blockchain = flag.String("blockchaincfg", "", "path to blockchain RPC service configuration json file")
|
blockchain = flag.String("blockchaincfg", "", "path to blockchain RPC service configuration json file")
|
||||||
|
|
||||||
|
@ -100,6 +104,11 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
os.Exit(mainWithExitCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow deferred functions to run even in case of fatal error
|
||||||
|
func mainWithExitCode() int {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
defer glog.Flush()
|
defer glog.Flush()
|
||||||
|
@ -120,20 +129,20 @@ func main() {
|
||||||
if *repair {
|
if *repair {
|
||||||
if err := db.RepairRocksDB(*dbPath); err != nil {
|
if err := db.RepairRocksDB(*dbPath); err != nil {
|
||||||
glog.Errorf("RepairRocksDB %s: %v", *dbPath, err)
|
glog.Errorf("RepairRocksDB %s: %v", *dbPath, err)
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
return
|
return exitCodeOK
|
||||||
}
|
}
|
||||||
|
|
||||||
if *blockchain == "" {
|
if *blockchain == "" {
|
||||||
glog.Error("Missing blockchaincfg configuration parameter")
|
glog.Error("Missing blockchaincfg configuration parameter")
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
|
|
||||||
coin, coinShortcut, coinLabel, err := coins.GetCoinNameFromConfig(*blockchain)
|
coin, coinShortcut, coinLabel, err := coins.GetCoinNameFromConfig(*blockchain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("config: ", err)
|
glog.Error("config: ", err)
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
|
|
||||||
// gspt.SetProcTitle("blockbook-" + normalizeName(coin))
|
// gspt.SetProcTitle("blockbook-" + normalizeName(coin))
|
||||||
|
@ -141,31 +150,31 @@ func main() {
|
||||||
metrics, err = common.GetMetrics(coin)
|
metrics, err = common.GetMetrics(coin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("metrics: ", err)
|
glog.Error("metrics: ", err)
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
|
|
||||||
if chain, mempool, err = getBlockChainWithRetry(coin, *blockchain, pushSynchronizationHandler, metrics, 60); err != nil {
|
if chain, mempool, err = getBlockChainWithRetry(coin, *blockchain, pushSynchronizationHandler, metrics, 120); err != nil {
|
||||||
glog.Error("rpc: ", err)
|
glog.Error("rpc: ", err)
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
|
|
||||||
index, err = db.NewRocksDB(*dbPath, *dbCache, *dbMaxOpenFiles, chain.GetChainParser(), metrics)
|
index, err = db.NewRocksDB(*dbPath, *dbCache, *dbMaxOpenFiles, chain.GetChainParser(), metrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("rocksDB: ", err)
|
glog.Error("rocksDB: ", err)
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
defer index.Close()
|
defer index.Close()
|
||||||
|
|
||||||
internalState, err = newInternalState(coin, coinShortcut, coinLabel, index)
|
internalState, err = newInternalState(coin, coinShortcut, coinLabel, index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("internalState: ", err)
|
glog.Error("internalState: ", err)
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
index.SetInternalState(internalState)
|
index.SetInternalState(internalState)
|
||||||
if internalState.DbState != common.DbStateClosed {
|
if internalState.DbState != common.DbStateClosed {
|
||||||
if internalState.DbState == common.DbStateInconsistent {
|
if internalState.DbState == common.DbStateInconsistent {
|
||||||
glog.Error("internalState: database is in inconsistent state and cannot be used")
|
glog.Error("internalState: database is in inconsistent state and cannot be used")
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
glog.Warning("internalState: database was left in open state, possibly previous ungraceful shutdown")
|
glog.Warning("internalState: database was left in open state, possibly previous ungraceful shutdown")
|
||||||
}
|
}
|
||||||
|
@ -175,15 +184,16 @@ func main() {
|
||||||
err = index.ComputeInternalStateColumnStats(chanOsSignal)
|
err = index.ComputeInternalStateColumnStats(chanOsSignal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("internalState: ", err)
|
glog.Error("internalState: ", err)
|
||||||
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
glog.Info("DB size on disk: ", index.DatabaseSizeOnDisk(), ", DB size as computed: ", internalState.DBSizeTotal())
|
glog.Info("DB size on disk: ", index.DatabaseSizeOnDisk(), ", DB size as computed: ", internalState.DBSizeTotal())
|
||||||
return
|
return exitCodeOK
|
||||||
}
|
}
|
||||||
|
|
||||||
syncWorker, err = db.NewSyncWorker(index, chain, *syncWorkers, *syncChunk, *blockFrom, *dryRun, chanOsSignal, metrics, internalState)
|
syncWorker, err = db.NewSyncWorker(index, chain, *syncWorkers, *syncChunk, *blockFrom, *dryRun, chanOsSignal, metrics, internalState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("NewSyncWorker %v", err)
|
glog.Errorf("NewSyncWorker %v", err)
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the DbState to open at this moment, after all important workers are initialized
|
// set the DbState to open at this moment, after all important workers are initialized
|
||||||
|
@ -191,17 +201,20 @@ func main() {
|
||||||
err = index.StoreInternalState(internalState)
|
err = index.StoreInternalState(internalState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("internalState: ", err)
|
glog.Error("internalState: ", err)
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
|
|
||||||
if *rollbackHeight >= 0 {
|
if *rollbackHeight >= 0 {
|
||||||
performRollback()
|
err = performRollback()
|
||||||
return
|
if err != nil {
|
||||||
|
return exitCodeFatal
|
||||||
|
}
|
||||||
|
return exitCodeOK
|
||||||
}
|
}
|
||||||
|
|
||||||
if txCache, err = db.NewTxCache(index, chain, metrics, internalState, !*noTxCache); err != nil {
|
if txCache, err = db.NewTxCache(index, chain, metrics, internalState, !*noTxCache); err != nil {
|
||||||
glog.Error("txCache ", err)
|
glog.Error("txCache ", err)
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
|
|
||||||
// report BlockbookAppInfo metric, only log possible error
|
// report BlockbookAppInfo metric, only log possible error
|
||||||
|
@ -214,7 +227,7 @@ func main() {
|
||||||
internalServer, err = startInternalServer()
|
internalServer, err = startInternalServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("internal server: ", err)
|
glog.Error("internal server: ", err)
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +236,7 @@ func main() {
|
||||||
publicServer, err = startPublicServer()
|
publicServer, err = startPublicServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("public server: ", err)
|
glog.Error("public server: ", err)
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,8 +244,11 @@ func main() {
|
||||||
internalState.SyncMode = true
|
internalState.SyncMode = true
|
||||||
internalState.InitialSync = true
|
internalState.InitialSync = true
|
||||||
if err := syncWorker.ResyncIndex(nil, true); err != nil {
|
if err := syncWorker.ResyncIndex(nil, true); err != nil {
|
||||||
glog.Error("resyncIndex ", err)
|
if err != db.ErrSyncInterrupted {
|
||||||
return
|
glog.Error("resyncIndex ", err)
|
||||||
|
return exitCodeFatal
|
||||||
|
}
|
||||||
|
return exitCodeOK
|
||||||
}
|
}
|
||||||
// initialize mempool after the initial sync is complete
|
// initialize mempool after the initial sync is complete
|
||||||
var addrDescForOutpoint bchain.AddrDescForOutpointFunc
|
var addrDescForOutpoint bchain.AddrDescForOutpointFunc
|
||||||
|
@ -242,12 +258,12 @@ func main() {
|
||||||
err = chain.InitializeMempool(addrDescForOutpoint, onNewTxAddr)
|
err = chain.InitializeMempool(addrDescForOutpoint, onNewTxAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("initializeMempool ", err)
|
glog.Error("initializeMempool ", err)
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
var mempoolCount int
|
var mempoolCount int
|
||||||
if mempoolCount, err = mempool.Resync(); err != nil {
|
if mempoolCount, err = mempool.Resync(); err != nil {
|
||||||
glog.Error("resyncMempool ", err)
|
glog.Error("resyncMempool ", err)
|
||||||
return
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
internalState.FinishedMempoolSync(mempoolCount)
|
internalState.FinishedMempoolSync(mempoolCount)
|
||||||
go syncIndexLoop()
|
go syncIndexLoop()
|
||||||
|
@ -272,8 +288,11 @@ func main() {
|
||||||
|
|
||||||
if !*synchronize {
|
if !*synchronize {
|
||||||
if err = syncWorker.ConnectBlocksParallel(height, until); err != nil {
|
if err = syncWorker.ConnectBlocksParallel(height, until); err != nil {
|
||||||
glog.Error("connectBlocksParallel ", err)
|
if err != db.ErrSyncInterrupted {
|
||||||
return
|
glog.Error("connectBlocksParallel ", err)
|
||||||
|
return exitCodeFatal
|
||||||
|
}
|
||||||
|
return exitCodeOK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,6 +309,7 @@ func main() {
|
||||||
<-chanSyncMempoolDone
|
<-chanSyncMempoolDone
|
||||||
<-chanStoreInternalStateDone
|
<-chanStoreInternalStateDone
|
||||||
}
|
}
|
||||||
|
return exitCodeOK
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBlockChainWithRetry(coin string, configfile string, pushHandler func(bchain.NotificationType), metrics *common.Metrics, seconds int) (bchain.BlockChain, bchain.Mempool, error) {
|
func getBlockChainWithRetry(coin string, configfile string, pushHandler func(bchain.NotificationType), metrics *common.Metrics, seconds int) (bchain.BlockChain, bchain.Mempool, error) {
|
||||||
|
@ -355,11 +375,11 @@ func startPublicServer() (*server.PublicServer, error) {
|
||||||
return publicServer, err
|
return publicServer, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func performRollback() {
|
func performRollback() error {
|
||||||
bestHeight, bestHash, err := index.GetBestBlock()
|
bestHeight, bestHash, err := index.GetBestBlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("rollbackHeight: ", err)
|
glog.Error("rollbackHeight: ", err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
if uint32(*rollbackHeight) > bestHeight {
|
if uint32(*rollbackHeight) > bestHeight {
|
||||||
glog.Infof("nothing to rollback, rollbackHeight %d, bestHeight: %d", *rollbackHeight, bestHeight)
|
glog.Infof("nothing to rollback, rollbackHeight %d, bestHeight: %d", *rollbackHeight, bestHeight)
|
||||||
|
@ -369,16 +389,17 @@ func performRollback() {
|
||||||
hash, err := index.GetBlockHash(height)
|
hash, err := index.GetBlockHash(height)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("rollbackHeight: ", err)
|
glog.Error("rollbackHeight: ", err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
hashes = append(hashes, hash)
|
hashes = append(hashes, hash)
|
||||||
}
|
}
|
||||||
err = syncWorker.DisconnectBlocks(uint32(*rollbackHeight), bestHeight, hashes)
|
err = syncWorker.DisconnectBlocks(uint32(*rollbackHeight), bestHeight, hashes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("rollbackHeight: ", err)
|
glog.Error("rollbackHeight: ", err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func blockbookAppInfoMetric(db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState, metrics *common.Metrics) error {
|
func blockbookAppInfoMetric(db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState, metrics *common.Metrics) error {
|
||||||
|
|
|
@ -45,6 +45,9 @@ func NewSyncWorker(db *RocksDB, chain bchain.BlockChain, syncWorkers, syncChunk
|
||||||
|
|
||||||
var errSynced = errors.New("synced")
|
var errSynced = errors.New("synced")
|
||||||
|
|
||||||
|
// ErrSyncInterrupted is returned when synchronization is interrupted by OS signal
|
||||||
|
var ErrSyncInterrupted = errors.New("ErrSyncInterrupted")
|
||||||
|
|
||||||
// ResyncIndex synchronizes index to the top of the blockchain
|
// ResyncIndex synchronizes index to the top of the blockchain
|
||||||
// onNewBlock is called when new block is connected, but not in initial parallel sync
|
// onNewBlock is called when new block is connected, but not in initial parallel sync
|
||||||
func (w *SyncWorker) ResyncIndex(onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
|
func (w *SyncWorker) ResyncIndex(onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
|
||||||
|
@ -202,7 +205,8 @@ func (w *SyncWorker) connectBlocks(onNewBlock bchain.OnNewBlockFunc, initialSync
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-w.chanOsSignal:
|
case <-w.chanOsSignal:
|
||||||
return errors.Errorf("connectBlocks interrupted at height %d", lastRes.block.Height)
|
glog.Info("connectBlocks interrupted at height ", lastRes.block.Height)
|
||||||
|
return ErrSyncInterrupted
|
||||||
case res := <-bch:
|
case res := <-bch:
|
||||||
if res == empty {
|
if res == empty {
|
||||||
break ConnectLoop
|
break ConnectLoop
|
||||||
|
@ -325,7 +329,8 @@ ConnectLoop:
|
||||||
for h := lower; h <= higher; {
|
for h := lower; h <= higher; {
|
||||||
select {
|
select {
|
||||||
case <-w.chanOsSignal:
|
case <-w.chanOsSignal:
|
||||||
err = errors.Errorf("connectBlocksParallel interrupted at height %d", h)
|
glog.Info("connectBlocksParallel interrupted at height ", h)
|
||||||
|
err = ErrSyncInterrupted
|
||||||
// signal all workers to terminate their loops (error loops are interrupted below)
|
// signal all workers to terminate their loops (error loops are interrupted below)
|
||||||
close(terminating)
|
close(terminating)
|
||||||
break ConnectLoop
|
break ConnectLoop
|
||||||
|
|
|
@ -25,10 +25,8 @@ func testConnectBlocks(t *testing.T, h *TestHandler) {
|
||||||
close(ch)
|
close(ch)
|
||||||
}
|
}
|
||||||
}, true)
|
}, true)
|
||||||
if err != nil {
|
if err != nil && err != db.ErrSyncInterrupted {
|
||||||
if !strings.HasPrefix(err.Error(), "connectBlocks interrupted at height") {
|
t.Fatal(err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
height, _, err := d.GetBestBlock()
|
height, _, err := d.GetBestBlock()
|
||||||
|
|
Loading…
Reference in New Issue