github.com/qioalice/ekago/v3@v3.3.2-0.20221202205325-5c262d586ee4/ekadeath/death.go (about) 1 // Copyright © 2019-2021. All rights reserved. 2 // Author: Ilya Stroy. 3 // Contacts: iyuryevich@pm.me, https://github.com/qioalice 4 // License: https://opensource.org/licenses/MIT 5 6 package ekadeath 7 8 import ( 9 "os" 10 "reflect" 11 ) 12 13 // ---------------------------------------------------------------------------- // 14 // 15 // This package is need to manage graceful shutdown of the whole service. 16 // Package allows you to call some functions before os.Exit(), SIGKILL, SIGTERM. 17 // 18 // Registered destructors is called in LIFO order, 19 // meaning the last registered destructor will be called firstly. 20 // 21 // It's useful to do some prepares before gonna die, e.g.: 22 // - close DB connections, close files, 23 // - flush logs, requests 24 // - notify about it 25 // 26 // All you need is: 27 // 28 // 1. DO NOT USE os.Exit ANYMORE. HERE AND EVERYWHERE ELSE. NEVER. STOP IT! 29 // USE Die() FUNC INSTEAD! 30 // 31 // 2. You need your foo func to be called when Die() is called or SIGTERM/SIGKILL? 32 // Just call Reg(foo), where foo is your func and it's done! 33 // 34 // 3. Want to know what exit code has been used to stop service? 35 // Pass your destructor func to Reg with "func (code int)" signature. 36 // Now in your destructor you have 'code' argument and yes, it's exit code. 37 // 38 // 4. Want to stop service? 39 // Call Die(). In this case, be default, the exit code is 1. 40 // Want another? Pass your desired exit code to Die (e.g. Die(2)) 41 // and combine it with your own extended destructor "func (code int)". 42 // 43 // 5. Do not want to multiply "if exitCode == 1 {} else if exitCode == 2 {} ..." 44 // and you hate switch construction? Do not worry! 45 // Register your destructors that will be called only for specified exit code. 46 // Call Reg(exitCodeToBind int, yourDestructor func()). 47 // Want to know in your destructor that you bind it e.g. to exitCode, dunno, 5? 48 // No problem, Reg(5, foo), where foo is func(code){...}. 49 // Now in your foo, code is 5. It's simple, isn't? 50 // 51 // ---------------------------------------------------------------------------- // 52 // 53 // What functions you can use as destructors? 54 // Only 2 signatures of destructors are allowable: 55 // - func(): no arguments, no returns. Just your callback (or closure). 56 // - func(code int): one argument, no returns. Argument is exit code Die() called with. 57 // 58 // ---------------------------------------------------------------------------- // 59 60 type ( 61 // DestructorSimple is an alias to the function that you may register 62 // using Reg() function to be executed after Die() or Exit() is called. 63 DestructorSimple = func() 64 DestructorWithExitCode = func(code int) 65 ) 66 67 // Reg registers destructors to be called when app should be stopped 68 // when Die() or Exit() is called or SIGTERM/SIGKILL received. 69 // 70 // You can use Reg to do: 71 // 1. Just reg one or many destructor(s): Reg(foo), Reg(foo1, foo2, foo3). 72 // 2. Reg destructor (one or many) to be called for special exitCode only: 73 // Reg(exitCode, foo), Reg(exitCode, foo1, foo2, foo3), 74 // where exitCode should be: int, int8, int16, int32, int64 and the same uint's. 75 // 76 // Nil destructors will be ignored. 77 // 78 // Despite of fact that all int/uint types are available to be used as exit code, 79 // their values must be in the int32 range. UB otherwise. 80 // 81 // Since v3 version of ekago/ekadeath you may register a new destructors 82 // when a death is requested and other destructors are under executing now. 83 // In that case, added destructor will be executed just after the destructor, 84 // that is under executing now. 85 func Reg(args ...any) { 86 87 if l := len(args); l == 0 { 88 return 89 90 } else if v0 := reflect.ValueOf(args[0]); l == 1 { 91 reg(false, 0, args[0]) 92 93 } else if k := v0.Kind(); k >= reflect.Int && k <= reflect.Int64 { 94 reg(true, int(v0.Int()), args[1:]...) 95 96 } else if k >= reflect.Uint && k <= reflect.Uint64 { 97 reg(true, int(v0.Uint()), args[1:]...) 98 99 } else { 100 reg(false, 0, args...) 101 } 102 } 103 104 // Exit is the same as Die(0). 105 func Exit() { 106 Die(0) 107 } 108 109 // Die calls all registered destructors and then shutdowns an app using os.Exit. 110 // Thus all goroutines will be forcibly stopped. 111 // 112 // You can pass one int argument as exit code. In this case only common 113 // and associated with specified exit code destructors will be called. 114 // By default, exit code is 1. 2nd and next codes are ignored. 115 func Die(code ...int) { 116 117 exitCode := 1 // default value, could be overwritten by first arg 118 if len(code) > 0 { 119 exitCode = code[0] 120 } 121 122 for elem, found := destructors.Pop(); found; elem, found = destructors.Pop() { 123 destructor := elem.(destructorRegistered) 124 if destructor.callAnyway || destructor.bindToExitCode == exitCode { 125 invoke(destructor.f, exitCode) 126 } 127 } 128 129 os.Exit(exitCode) 130 } 131 132 // RegisteredNum reports how much destructors are registered for now. 133 func RegisteredNum() int { 134 return destructors.Len() 135 }