Return with non zero exit code in case of fatal error

pull/161/head
Martin Boehm 2019-04-23 14:18:32 +02:00
parent 68fed56d12
commit d23d0a9e4f
3 changed files with 58 additions and 34 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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()