Skip to content

Commit

Permalink
Return with non zero exit code in case of fatal error
Browse files Browse the repository at this point in the history
  • Loading branch information
martinboehm committed Apr 23, 2019
1 parent 68fed56 commit d23d0a9
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 34 deletions.
77 changes: 49 additions & 28 deletions blockbook.go
Expand Up @@ -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")

Expand Down Expand Up @@ -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()
Expand All @@ -120,52 +129,52 @@ 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))

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")
}
Expand All @@ -175,33 +184,37 @@ 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
internalState.DbState = common.DbStateOpen
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
Expand All @@ -214,7 +227,7 @@ func main() {
internalServer, err = startInternalServer()
if err != nil {
glog.Error("internal server: ", err)
return
return exitCodeFatal
}
}

Expand All @@ -223,16 +236,19 @@ func main() {
publicServer, err = startPublicServer()
if err != nil {
glog.Error("public server: ", err)
return
return exitCodeFatal
}
}

if *synchronize {
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
Expand All @@ -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()
Expand All @@ -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
}
}
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down
9 changes: 7 additions & 2 deletions db/sync.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 2 additions & 4 deletions tests/sync/connectblocks.go
Expand Up @@ -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()
Expand Down

0 comments on commit d23d0a9

Please sign in to comment.