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  }