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
const storeInternalStatePeriodMs = 59699
// exit codes from the main function
const exitCodeOK = 0
const exitCodeFatal = 255
var (
blockchain = flag.String("blockchaincfg", "", "path to blockchain RPC service configuration json file")
@ -100,6 +104,11 @@ func init() {
}
func main() {
os.Exit(mainWithExitCode())
}
// allow deferred functions to run even in case of fatal error
func mainWithExitCode() int {
flag.Parse()
defer glog.Flush()
@ -120,20 +129,20 @@ func main() {
if *repair {
if err := db.RepairRocksDB(*dbPath); err != nil {
glog.Errorf("RepairRocksDB %s: %v", *dbPath, err)
return
return exitCodeFatal
}
return
return exitCodeOK
}
if *blockchain == "" {
glog.Error("Missing blockchaincfg configuration parameter")
return
return exitCodeFatal
}
coin, coinShortcut, coinLabel, err := coins.GetCoinNameFromConfig(*blockchain)
if err != nil {
glog.Error("config: ", err)
return
return exitCodeFatal
}
// gspt.SetProcTitle("blockbook-" + normalizeName(coin))
@ -141,31 +150,31 @@ func main() {
metrics, err = common.GetMetrics(coin)
if err != nil {
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)
return
return exitCodeFatal
}
index, err = db.NewRocksDB(*dbPath, *dbCache, *dbMaxOpenFiles, chain.GetChainParser(), metrics)
if err != nil {
glog.Error("rocksDB: ", err)
return
return exitCodeFatal
}
defer index.Close()
internalState, err = newInternalState(coin, coinShortcut, coinLabel, index)
if err != nil {
glog.Error("internalState: ", err)
return
return exitCodeFatal
}
index.SetInternalState(internalState)
if internalState.DbState != common.DbStateClosed {
if internalState.DbState == common.DbStateInconsistent {
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")
}
@ -175,15 +184,16 @@ func main() {
err = index.ComputeInternalStateColumnStats(chanOsSignal)
if err != nil {
glog.Error("internalState: ", err)
return exitCodeFatal
}
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)
if err != nil {
glog.Errorf("NewSyncWorker %v", err)
return
return exitCodeFatal
}
// set the DbState to open at this moment, after all important workers are initialized
@ -191,17 +201,20 @@ func main() {
err = index.StoreInternalState(internalState)
if err != nil {
glog.Error("internalState: ", err)
return
return exitCodeFatal
}
if *rollbackHeight >= 0 {
performRollback()
return
err = performRollback()
if err != nil {
return exitCodeFatal
}
return exitCodeOK
}
if txCache, err = db.NewTxCache(index, chain, metrics, internalState, !*noTxCache); err != nil {
glog.Error("txCache ", err)
return
return exitCodeFatal
}
// report BlockbookAppInfo metric, only log possible error
@ -214,7 +227,7 @@ func main() {
internalServer, err = startInternalServer()
if err != nil {
glog.Error("internal server: ", err)
return
return exitCodeFatal
}
}
@ -223,7 +236,7 @@ func main() {
publicServer, err = startPublicServer()
if err != nil {
glog.Error("public server: ", err)
return
return exitCodeFatal
}
}
@ -231,8 +244,11 @@ func main() {
internalState.SyncMode = true
internalState.InitialSync = true
if err := syncWorker.ResyncIndex(nil, true); err != nil {
glog.Error("resyncIndex ", err)
return
if err != db.ErrSyncInterrupted {
glog.Error("resyncIndex ", err)
return exitCodeFatal
}
return exitCodeOK
}
// initialize mempool after the initial sync is complete
var addrDescForOutpoint bchain.AddrDescForOutpointFunc
@ -242,12 +258,12 @@ func main() {
err = chain.InitializeMempool(addrDescForOutpoint, onNewTxAddr)
if err != nil {
glog.Error("initializeMempool ", err)
return
return exitCodeFatal
}
var mempoolCount int
if mempoolCount, err = mempool.Resync(); err != nil {
glog.Error("resyncMempool ", err)
return
return exitCodeFatal
}
internalState.FinishedMempoolSync(mempoolCount)
go syncIndexLoop()
@ -272,8 +288,11 @@ func main() {
if !*synchronize {
if err = syncWorker.ConnectBlocksParallel(height, until); err != nil {
glog.Error("connectBlocksParallel ", err)
return
if err != db.ErrSyncInterrupted {
glog.Error("connectBlocksParallel ", err)
return exitCodeFatal
}
return exitCodeOK
}
}
}
@ -290,6 +309,7 @@ func main() {
<-chanSyncMempoolDone
<-chanStoreInternalStateDone
}
return exitCodeOK
}
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
}
func performRollback() {
func performRollback() error {
bestHeight, bestHash, err := index.GetBestBlock()
if err != nil {
glog.Error("rollbackHeight: ", err)
return
return err
}
if uint32(*rollbackHeight) > bestHeight {
glog.Infof("nothing to rollback, rollbackHeight %d, bestHeight: %d", *rollbackHeight, bestHeight)
@ -369,16 +389,17 @@ func performRollback() {
hash, err := index.GetBlockHash(height)
if err != nil {
glog.Error("rollbackHeight: ", err)
return
return err
}
hashes = append(hashes, hash)
}
err = syncWorker.DisconnectBlocks(uint32(*rollbackHeight), bestHeight, hashes)
if err != nil {
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 {

View File

@ -45,6 +45,9 @@ func NewSyncWorker(db *RocksDB, chain bchain.BlockChain, syncWorkers, syncChunk
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
// onNewBlock is called when new block is connected, but not in initial parallel sync
func (w *SyncWorker) ResyncIndex(onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
@ -202,7 +205,8 @@ func (w *SyncWorker) connectBlocks(onNewBlock bchain.OnNewBlockFunc, initialSync
for {
select {
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:
if res == empty {
break ConnectLoop
@ -325,7 +329,8 @@ ConnectLoop:
for h := lower; h <= higher; {
select {
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)
close(terminating)
break ConnectLoop

View File

@ -25,10 +25,8 @@ func testConnectBlocks(t *testing.T, h *TestHandler) {
close(ch)
}
}, true)
if err != nil {
if !strings.HasPrefix(err.Error(), "connectBlocks interrupted at height") {
t.Fatal(err)
}
if err != nil && err != db.ErrSyncInterrupted {
t.Fatal(err)
}
height, _, err := d.GetBestBlock()