github.com/MetalBlockchain/metalgo@v1.11.9/app/app.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package app 5 6 import ( 7 "fmt" 8 "os" 9 "os/signal" 10 "sync" 11 "syscall" 12 13 "go.uber.org/zap" 14 "golang.org/x/sync/errgroup" 15 16 "github.com/MetalBlockchain/metalgo/node" 17 "github.com/MetalBlockchain/metalgo/utils/logging" 18 "github.com/MetalBlockchain/metalgo/utils/perms" 19 "github.com/MetalBlockchain/metalgo/utils/ulimit" 20 ) 21 22 const Header = ` ███ ███ ███████ ████████ █████ ██ 23 ████ ████ ██ ██ ██ ██ ██ 24 ██ ████ ██ █████ ██ ███████ ██ 25 ██ ██ ██ ██ ██ ██ ██ ██ 26 ██ ██ ███████ ██ ██ ██ ███████` 27 28 var _ App = (*app)(nil) 29 30 type App interface { 31 // Start kicks off the application and returns immediately. 32 // Start should only be called once. 33 Start() error 34 35 // Stop notifies the application to exit and returns immediately. 36 // Stop should only be called after [Start]. 37 // It is safe to call Stop multiple times. 38 Stop() error 39 40 // ExitCode should only be called after [Start] returns with no error. It 41 // should block until the application finishes 42 ExitCode() (int, error) 43 } 44 45 func New(config node.Config) (App, error) { 46 // Set the data directory permissions to be read write. 47 if err := perms.ChmodR(config.DatabaseConfig.Path, true, perms.ReadWriteExecute); err != nil { 48 return nil, fmt.Errorf("failed to restrict the permissions of the database directory with: %w", err) 49 } 50 if err := perms.ChmodR(config.LoggingConfig.Directory, true, perms.ReadWriteExecute); err != nil { 51 return nil, fmt.Errorf("failed to restrict the permissions of the log directory with: %w", err) 52 } 53 54 logFactory := logging.NewFactory(config.LoggingConfig) 55 log, err := logFactory.Make("main") 56 if err != nil { 57 logFactory.Close() 58 return nil, fmt.Errorf("failed to initialize log: %w", err) 59 } 60 61 // update fd limit 62 fdLimit := config.FdLimit 63 if err := ulimit.Set(fdLimit, log); err != nil { 64 log.Fatal("failed to set fd-limit", 65 zap.Error(err), 66 ) 67 logFactory.Close() 68 return nil, err 69 } 70 71 n, err := node.New(&config, logFactory, log) 72 if err != nil { 73 log.Stop() 74 logFactory.Close() 75 return nil, fmt.Errorf("failed to initialize node: %w", err) 76 } 77 78 return &app{ 79 node: n, 80 log: log, 81 logFactory: logFactory, 82 }, nil 83 } 84 85 func Run(app App) int { 86 // start running the application 87 if err := app.Start(); err != nil { 88 return 1 89 } 90 91 // register signals to kill the application 92 signals := make(chan os.Signal, 1) 93 signal.Notify(signals, syscall.SIGINT) 94 signal.Notify(signals, syscall.SIGTERM) 95 96 // start up a new go routine to handle attempts to kill the application 97 var eg errgroup.Group 98 eg.Go(func() error { 99 for range signals { 100 return app.Stop() 101 } 102 return nil 103 }) 104 105 // wait for the app to exit and get the exit code response 106 exitCode, err := app.ExitCode() 107 108 // shut down the signal go routine 109 signal.Stop(signals) 110 close(signals) 111 112 // if there was an error closing or running the application, report that error 113 if eg.Wait() != nil || err != nil { 114 return 1 115 } 116 117 // return the exit code that the application reported 118 return exitCode 119 } 120 121 // app is a wrapper around a node that runs in this process 122 type app struct { 123 node *node.Node 124 log logging.Logger 125 logFactory logging.Factory 126 exitWG sync.WaitGroup 127 } 128 129 // Start the business logic of the node (as opposed to config reading, etc). 130 // Does not block until the node is done. Errors returned from this method 131 // are not logged. 132 func (a *app) Start() error { 133 // [p.ExitCode] will block until [p.exitWG.Done] is called 134 a.exitWG.Add(1) 135 go func() { 136 defer func() { 137 if r := recover(); r != nil { 138 fmt.Println("caught panic", r) 139 } 140 a.log.Stop() 141 a.logFactory.Close() 142 a.exitWG.Done() 143 }() 144 defer func() { 145 // If [p.node.Dispatch()] panics, then we should log the panic and 146 // then re-raise the panic. This is why the above defer is broken 147 // into two parts. 148 a.log.StopOnPanic() 149 }() 150 151 err := a.node.Dispatch() 152 a.log.Debug("dispatch returned", 153 zap.Error(err), 154 ) 155 }() 156 return nil 157 } 158 159 // Stop attempts to shutdown the currently running node. This function will 160 // return immediately. 161 func (a *app) Stop() error { 162 a.node.Shutdown(0) 163 return nil 164 } 165 166 // ExitCode returns the exit code that the node is reporting. This function 167 // blocks until the node has been shut down. 168 func (a *app) ExitCode() (int, error) { 169 a.exitWG.Wait() 170 return a.node.ExitCode(), nil 171 }