github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/f/death.go (about) 1 //Manage the death of your application. 2 package f 3 4 import ( 5 "fmt" 6 "io" 7 "log" 8 "os" 9 "os/signal" 10 "reflect" 11 "strings" 12 "sync" 13 "time" 14 ) 15 16 // Death manages the death of your application. 17 type Death struct { 18 wg *sync.WaitGroup 19 sigChannel chan os.Signal 20 callChannel chan struct{} 21 timeout time.Duration 22 } 23 24 // closer is a wrapper to the struct we are going to close with metadata 25 // to help with debug close. 26 type closer struct { 27 Index int 28 C io.Closer 29 Name string 30 PKGPath string 31 Err error 32 } 33 34 type Closable interface { 35 Close() error 36 } 37 type Closer struct { 38 } 39 40 func (c Closer) Close() error { 41 return nil 42 } 43 44 // NewDeath Create Death with the signals you want to die from. 45 func NewDeath(signals ...os.Signal) (death *Death) { 46 death = &Death{timeout: 10 * time.Second, 47 sigChannel: make(chan os.Signal, 1), 48 callChannel: make(chan struct{}, 1), 49 wg: &sync.WaitGroup{}, 50 } 51 signal.Notify(death.sigChannel, signals...) 52 death.wg.Add(1) 53 go death.listenForSignal() 54 return death 55 } 56 57 // SetTimeout Overrides the time death is willing to wait for a objects to be closed. 58 func (d *Death) SetTimeout(t time.Duration) *Death { 59 d.timeout = t 60 return d 61 } 62 63 // WaitForDeath wait for signal and then kill all items that need to die. If they fail to 64 // die when instructed we return an error 65 func (d *Death) WaitForDeath(closable ...io.Closer) (err error) { 66 d.wg.Wait() 67 log.Println("Shutdown started...") 68 count := len(closable) 69 log.Println("Closing ", count, " objects") 70 if count > 0 { 71 return d.closeInMass(closable...) 72 } 73 return nil 74 } 75 76 // WaitForDeathWithFunc allows you to have a single function get called when it's time to 77 // kill your application. 78 func (d *Death) WaitForDeathWithFunc(f func()) { 79 d.wg.Wait() 80 log.Println("Shutdown started...") 81 f() 82 } 83 84 // getPkgPath for an io closer. 85 func getPkgPath(c io.Closer) (name string, pkgPath string) { 86 t := reflect.TypeOf(c) 87 if t.Kind() == reflect.Ptr { 88 t = t.Elem() 89 } 90 return t.Name(), t.PkgPath() 91 } 92 93 // closeInMass Close all the objects at once and wait for them to finish with a channel. Return an 94 // error if you fail to close all the objects 95 func (d *Death) closeInMass(closable ...io.Closer) (err error) { 96 count := len(closable) 97 sentToClose := make(map[int]closer) 98 //call close async 99 doneClosers := make(chan closer, count) 100 for i, c := range closable { 101 name, pkgPath := getPkgPath(c) 102 closer := closer{Index: i, C: c, Name: name, PKGPath: pkgPath} 103 go d.closeObjects(closer, doneClosers) 104 sentToClose[i] = closer 105 } 106 107 // wait on channel for notifications. 108 timer := time.NewTimer(d.timeout) 109 failedClosers := []closer{} 110 for { 111 select { 112 case <-timer.C: 113 s := "failed to close: " 114 var pks []string 115 for _, c := range sentToClose { 116 pks = append(pks, fmt.Sprintf("%s/%s", c.PKGPath, c.Name)) 117 log.Println("Failed to close: ", c.PKGPath, "/", c.Name) 118 } 119 return fmt.Errorf("%s", fmt.Sprintf("%s %s", s, strings.Join(pks, ", "))) 120 case closer := <-doneClosers: 121 delete(sentToClose, closer.Index) 122 count-- 123 if closer.Err != nil { 124 failedClosers = append(failedClosers, closer) 125 } 126 127 if count != 0 || len(sentToClose) != 0 { 128 continue 129 } 130 131 if len(failedClosers) != 0 { 132 errString := generateErrString(failedClosers) 133 return fmt.Errorf("errors from closers: %s", errString) 134 } 135 136 return nil 137 } 138 } 139 } 140 141 // closeObjects and return a bool when finished on a channel. 142 func (d *Death) closeObjects(closer closer, done chan<- closer) { 143 err := closer.C.Close() 144 if err != nil { 145 log.Println(err) 146 closer.Err = err 147 } 148 done <- closer 149 } 150 151 // FallOnSword manually initiates the death process. 152 func (d *Death) FallOnSword() { 153 select { 154 case d.callChannel <- struct{}{}: 155 default: 156 } 157 } 158 159 // ListenForSignal Manage death of application by signal. 160 func (d *Death) listenForSignal() { 161 defer d.wg.Done() 162 for { 163 select { 164 case <-d.sigChannel: 165 return 166 case <-d.callChannel: 167 return 168 } 169 } 170 } 171 172 // generateErrString generates a string containing a list of tuples of pkgname to error message 173 func generateErrString(failedClosers []closer) (errString string) { 174 for i, fc := range failedClosers { 175 if i == 0 { 176 errString = fmt.Sprintf("%s/%s: %s", fc.PKGPath, fc.Name, fc.Err) 177 continue 178 } 179 errString = fmt.Sprintf("%s, %s/%s: %s", errString, fc.PKGPath, fc.Name, fc.Err) 180 } 181 182 return errString 183 }