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  }