code.gitea.io/gitea@v1.19.3/modules/graceful/manager.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package graceful
     5  
     6  import (
     7  	"context"
     8  	"runtime/pprof"
     9  	"sync"
    10  	"time"
    11  
    12  	"code.gitea.io/gitea/modules/log"
    13  	"code.gitea.io/gitea/modules/process"
    14  	"code.gitea.io/gitea/modules/setting"
    15  )
    16  
    17  type state uint8
    18  
    19  const (
    20  	stateInit state = iota
    21  	stateRunning
    22  	stateShuttingDown
    23  	stateTerminate
    24  )
    25  
    26  // There are some places that could inherit sockets:
    27  //
    28  // * HTTP or HTTPS main listener
    29  // * HTTP or HTTPS install listener
    30  // * HTTP redirection fallback
    31  // * Builtin SSH listener
    32  //
    33  // If you add an additional place you must increment this number
    34  // and add a function to call manager.InformCleanup if it's not going to be used
    35  const numberOfServersToCreate = 4
    36  
    37  // Manager represents the graceful server manager interface
    38  var manager *Manager
    39  
    40  var initOnce = sync.Once{}
    41  
    42  // GetManager returns the Manager
    43  func GetManager() *Manager {
    44  	InitManager(context.Background())
    45  	return manager
    46  }
    47  
    48  // InitManager creates the graceful manager in the provided context
    49  func InitManager(ctx context.Context) {
    50  	initOnce.Do(func() {
    51  		manager = newGracefulManager(ctx)
    52  
    53  		// Set the process default context to the HammerContext
    54  		process.DefaultContext = manager.HammerContext()
    55  	})
    56  }
    57  
    58  // WithCallback is a runnable to call when the caller has finished
    59  type WithCallback func(callback func())
    60  
    61  // RunnableWithShutdownFns is a runnable with functions to run at shutdown and terminate
    62  // After the callback to atShutdown is called and is complete, the main function must return.
    63  // Similarly the callback function provided to atTerminate must return once termination is complete.
    64  // Please note that use of the atShutdown and atTerminate callbacks will create go-routines that will wait till their respective signals
    65  // - users must therefore be careful to only call these as necessary.
    66  type RunnableWithShutdownFns func(atShutdown, atTerminate func(func()))
    67  
    68  // RunWithShutdownFns takes a function that has both atShutdown and atTerminate callbacks
    69  // After the callback to atShutdown is called and is complete, the main function must return.
    70  // Similarly the callback function provided to atTerminate must return once termination is complete.
    71  // Please note that use of the atShutdown and atTerminate callbacks will create go-routines that will wait till their respective signals
    72  // - users must therefore be careful to only call these as necessary.
    73  func (g *Manager) RunWithShutdownFns(run RunnableWithShutdownFns) {
    74  	g.runningServerWaitGroup.Add(1)
    75  	defer g.runningServerWaitGroup.Done()
    76  	defer func() {
    77  		if err := recover(); err != nil {
    78  			log.Critical("PANIC during RunWithShutdownFns: %v\nStacktrace: %s", err, log.Stack(2))
    79  			g.doShutdown()
    80  		}
    81  	}()
    82  	run(func(atShutdown func()) {
    83  		g.lock.Lock()
    84  		defer g.lock.Unlock()
    85  		g.toRunAtShutdown = append(g.toRunAtShutdown,
    86  			func() {
    87  				defer func() {
    88  					if err := recover(); err != nil {
    89  						log.Critical("PANIC during RunWithShutdownFns: %v\nStacktrace: %s", err, log.Stack(2))
    90  						g.doShutdown()
    91  					}
    92  				}()
    93  				atShutdown()
    94  			})
    95  	}, func(atTerminate func()) {
    96  		g.RunAtTerminate(atTerminate)
    97  	})
    98  }
    99  
   100  // RunWithShutdownContext takes a function that has a context to watch for shutdown.
   101  // After the provided context is Done(), the main function must return once shutdown is complete.
   102  // (Optionally the HammerContext may be obtained and waited for however, this should be avoided if possible.)
   103  func (g *Manager) RunWithShutdownContext(run func(context.Context)) {
   104  	g.runningServerWaitGroup.Add(1)
   105  	defer g.runningServerWaitGroup.Done()
   106  	defer func() {
   107  		if err := recover(); err != nil {
   108  			log.Critical("PANIC during RunWithShutdownContext: %v\nStacktrace: %s", err, log.Stack(2))
   109  			g.doShutdown()
   110  		}
   111  	}()
   112  	ctx := g.ShutdownContext()
   113  	pprof.SetGoroutineLabels(ctx) // We don't have a label to restore back to but I think this is fine
   114  	run(ctx)
   115  }
   116  
   117  // RunAtTerminate adds to the terminate wait group and creates a go-routine to run the provided function at termination
   118  func (g *Manager) RunAtTerminate(terminate func()) {
   119  	g.terminateWaitGroup.Add(1)
   120  	g.lock.Lock()
   121  	defer g.lock.Unlock()
   122  	g.toRunAtTerminate = append(g.toRunAtTerminate,
   123  		func() {
   124  			defer g.terminateWaitGroup.Done()
   125  			defer func() {
   126  				if err := recover(); err != nil {
   127  					log.Critical("PANIC during RunAtTerminate: %v\nStacktrace: %s", err, log.Stack(2))
   128  				}
   129  			}()
   130  			terminate()
   131  		})
   132  }
   133  
   134  // RunAtShutdown creates a go-routine to run the provided function at shutdown
   135  func (g *Manager) RunAtShutdown(ctx context.Context, shutdown func()) {
   136  	g.lock.Lock()
   137  	defer g.lock.Unlock()
   138  	g.toRunAtShutdown = append(g.toRunAtShutdown,
   139  		func() {
   140  			defer func() {
   141  				if err := recover(); err != nil {
   142  					log.Critical("PANIC during RunAtShutdown: %v\nStacktrace: %s", err, log.Stack(2))
   143  				}
   144  			}()
   145  			select {
   146  			case <-ctx.Done():
   147  				return
   148  			default:
   149  				shutdown()
   150  			}
   151  		})
   152  }
   153  
   154  // RunAtHammer creates a go-routine to run the provided function at shutdown
   155  func (g *Manager) RunAtHammer(hammer func()) {
   156  	g.lock.Lock()
   157  	defer g.lock.Unlock()
   158  	g.toRunAtHammer = append(g.toRunAtHammer,
   159  		func() {
   160  			defer func() {
   161  				if err := recover(); err != nil {
   162  					log.Critical("PANIC during RunAtHammer: %v\nStacktrace: %s", err, log.Stack(2))
   163  				}
   164  			}()
   165  			hammer()
   166  		})
   167  }
   168  
   169  func (g *Manager) doShutdown() {
   170  	if !g.setStateTransition(stateRunning, stateShuttingDown) {
   171  		g.DoImmediateHammer()
   172  		return
   173  	}
   174  	g.lock.Lock()
   175  	g.shutdownCtxCancel()
   176  	atShutdownCtx := pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "post-shutdown"))
   177  	pprof.SetGoroutineLabels(atShutdownCtx)
   178  	for _, fn := range g.toRunAtShutdown {
   179  		go fn()
   180  	}
   181  	g.lock.Unlock()
   182  
   183  	if setting.GracefulHammerTime >= 0 {
   184  		go g.doHammerTime(setting.GracefulHammerTime)
   185  	}
   186  	go func() {
   187  		g.WaitForServers()
   188  		// Mop up any remaining unclosed events.
   189  		g.doHammerTime(0)
   190  		<-time.After(1 * time.Second)
   191  		g.doTerminate()
   192  		g.WaitForTerminate()
   193  		g.lock.Lock()
   194  		g.managerCtxCancel()
   195  		g.lock.Unlock()
   196  	}()
   197  }
   198  
   199  func (g *Manager) doHammerTime(d time.Duration) {
   200  	time.Sleep(d)
   201  	g.lock.Lock()
   202  	select {
   203  	case <-g.hammerCtx.Done():
   204  	default:
   205  		log.Warn("Setting Hammer condition")
   206  		g.hammerCtxCancel()
   207  		atHammerCtx := pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "post-hammer"))
   208  		pprof.SetGoroutineLabels(atHammerCtx)
   209  		for _, fn := range g.toRunAtHammer {
   210  			go fn()
   211  		}
   212  	}
   213  	g.lock.Unlock()
   214  }
   215  
   216  func (g *Manager) doTerminate() {
   217  	if !g.setStateTransition(stateShuttingDown, stateTerminate) {
   218  		return
   219  	}
   220  	g.lock.Lock()
   221  	select {
   222  	case <-g.terminateCtx.Done():
   223  	default:
   224  		log.Warn("Terminating")
   225  		g.terminateCtxCancel()
   226  		atTerminateCtx := pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "post-terminate"))
   227  		pprof.SetGoroutineLabels(atTerminateCtx)
   228  
   229  		for _, fn := range g.toRunAtTerminate {
   230  			go fn()
   231  		}
   232  	}
   233  	g.lock.Unlock()
   234  }
   235  
   236  // IsChild returns if the current process is a child of previous Gitea process
   237  func (g *Manager) IsChild() bool {
   238  	return g.isChild
   239  }
   240  
   241  // IsShutdown returns a channel which will be closed at shutdown.
   242  // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
   243  func (g *Manager) IsShutdown() <-chan struct{} {
   244  	return g.shutdownCtx.Done()
   245  }
   246  
   247  // IsHammer returns a channel which will be closed at hammer
   248  // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
   249  // Servers running within the running server wait group should respond to IsHammer
   250  // if not shutdown already
   251  func (g *Manager) IsHammer() <-chan struct{} {
   252  	return g.hammerCtx.Done()
   253  }
   254  
   255  // IsTerminate returns a channel which will be closed at terminate
   256  // The order of closure is IsShutdown, IsHammer (potentially), IsTerminate
   257  // IsTerminate will only close once all running servers have stopped
   258  func (g *Manager) IsTerminate() <-chan struct{} {
   259  	return g.terminateCtx.Done()
   260  }
   261  
   262  // ServerDone declares a running server done and subtracts one from the
   263  // running server wait group. Users probably do not want to call this
   264  // and should use one of the RunWithShutdown* functions
   265  func (g *Manager) ServerDone() {
   266  	g.runningServerWaitGroup.Done()
   267  }
   268  
   269  // WaitForServers waits for all running servers to finish. Users should probably
   270  // instead use AtTerminate or IsTerminate
   271  func (g *Manager) WaitForServers() {
   272  	g.runningServerWaitGroup.Wait()
   273  }
   274  
   275  // WaitForTerminate waits for all terminating actions to finish.
   276  // Only the main go-routine should use this
   277  func (g *Manager) WaitForTerminate() {
   278  	g.terminateWaitGroup.Wait()
   279  }
   280  
   281  func (g *Manager) getState() state {
   282  	g.lock.RLock()
   283  	defer g.lock.RUnlock()
   284  	return g.state
   285  }
   286  
   287  func (g *Manager) setStateTransition(old, new state) bool {
   288  	if old != g.getState() {
   289  		return false
   290  	}
   291  	g.lock.Lock()
   292  	if g.state != old {
   293  		g.lock.Unlock()
   294  		return false
   295  	}
   296  	g.state = new
   297  	g.lock.Unlock()
   298  	return true
   299  }
   300  
   301  func (g *Manager) setState(st state) {
   302  	g.lock.Lock()
   303  	defer g.lock.Unlock()
   304  
   305  	g.state = st
   306  }
   307  
   308  // InformCleanup tells the cleanup wait group that we have either taken a listener or will not be taking a listener.
   309  // At the moment the total number of servers (numberOfServersToCreate) are pre-defined as a const before global init,
   310  // so this function MUST be called if a server is not used.
   311  func (g *Manager) InformCleanup() {
   312  	g.createServerWaitGroup.Done()
   313  }
   314  
   315  // Done allows the manager to be viewed as a context.Context, it returns a channel that is closed when the server is finished terminating
   316  func (g *Manager) Done() <-chan struct{} {
   317  	return g.managerCtx.Done()
   318  }
   319  
   320  // Err allows the manager to be viewed as a context.Context done at Terminate
   321  func (g *Manager) Err() error {
   322  	return g.managerCtx.Err()
   323  }
   324  
   325  // Value allows the manager to be viewed as a context.Context done at Terminate
   326  func (g *Manager) Value(key interface{}) interface{} {
   327  	return g.managerCtx.Value(key)
   328  }
   329  
   330  // Deadline returns nil as there is no fixed Deadline for the manager, it allows the manager to be viewed as a context.Context
   331  func (g *Manager) Deadline() (deadline time.Time, ok bool) {
   332  	return g.managerCtx.Deadline()
   333  }