github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/os/interrupt.go (about)

     1  // Copyright (c) 2019 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package xos
    22  
    23  import (
    24  	"fmt"
    25  	"os"
    26  	"os/signal"
    27  	"sync"
    28  	"syscall"
    29  
    30  	"go.uber.org/zap"
    31  )
    32  
    33  // InterruptOptions are options to use when waiting for an interrupt.
    34  type InterruptOptions struct {
    35  	// InterruptChannel is an existing interrupt channel, if none
    36  	// specified one will be created.
    37  	InterruptCh <-chan error
    38  
    39  	// InterruptedChannel is a channel that will be closed once an
    40  	// interrupt has been seen. Use this to pass to goroutines who
    41  	// want to be notified about interruptions that you don't want
    42  	// consuming from the main interrupt channel.
    43  	InterruptedCh chan struct{}
    44  }
    45  
    46  // InterruptError is an error representing an interrupt.
    47  type InterruptError struct {
    48  	interrupt string
    49  }
    50  
    51  // ErrInterrupted is an error indicating that the interrupted channel was closed,
    52  // meaning an interrupt was received on the main interrupt channel.
    53  var ErrInterrupted = NewInterruptError("interrupted")
    54  
    55  // NewInterruptOptions creates InterruptOptions with sane defaults.
    56  func NewInterruptOptions() InterruptOptions {
    57  	return InterruptOptions{
    58  		InterruptCh:   NewInterruptChannel(1),
    59  		InterruptedCh: make(chan struct{}),
    60  	}
    61  }
    62  
    63  // NewInterruptError creates a new InterruptError.
    64  func NewInterruptError(interrupt string) error {
    65  	return &InterruptError{interrupt: interrupt}
    66  }
    67  
    68  func (i *InterruptError) Error() string {
    69  	return i.interrupt
    70  }
    71  
    72  // WatchForInterrupt watches for interrupts in a non-blocking fashion and closes
    73  // the interrupted channel when an interrupt is found. Use this method to
    74  // watch for interrupts while the caller continues to execute (e.g. during
    75  // server startup). To ensure child goroutines get properly closed, pass them
    76  // the interrupted channel. If the interrupted channel is closed, then the
    77  // goroutine knows to stop its work. This method returns a function that
    78  // can be used to stop the watch.
    79  func WatchForInterrupt(logger *zap.Logger, opts InterruptOptions) func() {
    80  	interruptCh := opts.InterruptCh
    81  	closed := make(chan struct{})
    82  	go func() {
    83  		select {
    84  		case err := <-interruptCh:
    85  			logger.Warn("interrupt", zap.Error(err))
    86  			close(opts.InterruptedCh)
    87  		case <-closed:
    88  			logger.Info("interrupt watch stopped")
    89  			return
    90  		}
    91  	}()
    92  
    93  	var doOnce sync.Once
    94  	return func() {
    95  		doOnce.Do(func() {
    96  			close(closed)
    97  		})
    98  	}
    99  }
   100  
   101  // WaitForInterrupt will wait for an interrupt to occur and return when done.
   102  func WaitForInterrupt(logger *zap.Logger, opts InterruptOptions) {
   103  	// Handle interrupts.
   104  	interruptCh := opts.InterruptCh
   105  	if interruptCh == nil {
   106  		// Need to catch our own interrupts.
   107  		interruptCh = NewInterruptChannel(1)
   108  		logger.Info("registered new interrupt handler")
   109  	} else {
   110  		logger.Info("using registered interrupt handler")
   111  	}
   112  
   113  	logger.Warn("interrupt", zap.Error(<-interruptCh))
   114  
   115  	if opts.InterruptedCh != nil {
   116  		close(opts.InterruptedCh)
   117  	}
   118  }
   119  
   120  // NewInterruptChannel will return an interrupt channel useful with multiple
   121  // listeners.
   122  func NewInterruptChannel(numListeners int) <-chan error {
   123  	interruptCh := make(chan error, numListeners)
   124  	go func() {
   125  		err := NewInterruptError(fmt.Sprintf("%v", <-interrupt()))
   126  		for i := 0; i < numListeners; i++ {
   127  			interruptCh <- err
   128  		}
   129  	}()
   130  	return interruptCh
   131  }
   132  
   133  func interrupt() <-chan os.Signal {
   134  	c := make(chan os.Signal)
   135  	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
   136  	return c
   137  }