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

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  // This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
     4  
     5  //go:build windows
     6  
     7  package graceful
     8  
     9  import (
    10  	"context"
    11  	"os"
    12  	"runtime/pprof"
    13  	"strconv"
    14  	"sync"
    15  	"time"
    16  
    17  	"code.gitea.io/gitea/modules/log"
    18  	"code.gitea.io/gitea/modules/setting"
    19  
    20  	"golang.org/x/sys/windows/svc"
    21  	"golang.org/x/sys/windows/svc/debug"
    22  )
    23  
    24  // WindowsServiceName is the name of the Windows service
    25  var WindowsServiceName = "gitea"
    26  
    27  const (
    28  	hammerCode       = 128
    29  	hammerCmd        = svc.Cmd(hammerCode)
    30  	acceptHammerCode = svc.Accepted(hammerCode)
    31  )
    32  
    33  // Manager manages the graceful shutdown process
    34  type Manager struct {
    35  	ctx                    context.Context
    36  	isChild                bool
    37  	lock                   *sync.RWMutex
    38  	state                  state
    39  	shutdownCtx            context.Context
    40  	hammerCtx              context.Context
    41  	terminateCtx           context.Context
    42  	managerCtx             context.Context
    43  	shutdownCtxCancel      context.CancelFunc
    44  	hammerCtxCancel        context.CancelFunc
    45  	terminateCtxCancel     context.CancelFunc
    46  	managerCtxCancel       context.CancelFunc
    47  	runningServerWaitGroup sync.WaitGroup
    48  	createServerWaitGroup  sync.WaitGroup
    49  	terminateWaitGroup     sync.WaitGroup
    50  	shutdownRequested      chan struct{}
    51  
    52  	toRunAtShutdown  []func()
    53  	toRunAtHammer    []func()
    54  	toRunAtTerminate []func()
    55  }
    56  
    57  func newGracefulManager(ctx context.Context) *Manager {
    58  	manager := &Manager{
    59  		isChild: false,
    60  		lock:    &sync.RWMutex{},
    61  		ctx:     ctx,
    62  	}
    63  	manager.createServerWaitGroup.Add(numberOfServersToCreate)
    64  	manager.start()
    65  	return manager
    66  }
    67  
    68  func (g *Manager) start() {
    69  	// Make contexts
    70  	g.terminateCtx, g.terminateCtxCancel = context.WithCancel(g.ctx)
    71  	g.shutdownCtx, g.shutdownCtxCancel = context.WithCancel(g.ctx)
    72  	g.hammerCtx, g.hammerCtxCancel = context.WithCancel(g.ctx)
    73  	g.managerCtx, g.managerCtxCancel = context.WithCancel(g.ctx)
    74  
    75  	// Next add pprof labels to these contexts
    76  	g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "with-terminate"))
    77  	g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("graceful-lifecycle", "with-shutdown"))
    78  	g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "with-hammer"))
    79  	g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "with-manager"))
    80  
    81  	// Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
    82  	pprof.SetGoroutineLabels(g.managerCtx)
    83  	defer pprof.SetGoroutineLabels(g.ctx)
    84  
    85  	// Make channels
    86  	g.shutdownRequested = make(chan struct{})
    87  
    88  	// Set the running state
    89  	g.setState(stateRunning)
    90  	if skip, _ := strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")); skip {
    91  		log.Trace("Skipping SVC check as SKIP_MINWINSVC is set")
    92  		return
    93  	}
    94  
    95  	// Make SVC process
    96  	run := svc.Run
    97  
    98  	//lint:ignore SA1019 We use IsAnInteractiveSession because IsWindowsService has a different permissions profile
    99  	isAnInteractiveSession, err := svc.IsAnInteractiveSession()
   100  	if err != nil {
   101  		log.Error("Unable to ascertain if running as an Windows Service: %v", err)
   102  		return
   103  	}
   104  	if isAnInteractiveSession {
   105  		log.Trace("Not running a service ... using the debug SVC manager")
   106  		run = debug.Run
   107  	}
   108  	go func() {
   109  		_ = run(WindowsServiceName, g)
   110  	}()
   111  }
   112  
   113  // Execute makes Manager implement svc.Handler
   114  func (g *Manager) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
   115  	if setting.StartupTimeout > 0 {
   116  		status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout / time.Millisecond)}
   117  	} else {
   118  		status <- svc.Status{State: svc.StartPending}
   119  	}
   120  
   121  	log.Trace("Awaiting server start-up")
   122  	// Now need to wait for everything to start...
   123  	if !g.awaitServer(setting.StartupTimeout) {
   124  		log.Trace("... start-up failed ... Stopped")
   125  		return false, 1
   126  	}
   127  
   128  	log.Trace("Sending Running state to SVC")
   129  
   130  	// We need to implement some way of svc.AcceptParamChange/svc.ParamChange
   131  	status <- svc.Status{
   132  		State:   svc.Running,
   133  		Accepts: svc.AcceptStop | svc.AcceptShutdown | acceptHammerCode,
   134  	}
   135  
   136  	log.Trace("Started")
   137  
   138  	waitTime := 30 * time.Second
   139  
   140  loop:
   141  	for {
   142  		select {
   143  		case <-g.ctx.Done():
   144  			log.Trace("Shutting down")
   145  			g.DoGracefulShutdown()
   146  			waitTime += setting.GracefulHammerTime
   147  			break loop
   148  		case <-g.shutdownRequested:
   149  			log.Trace("Shutting down")
   150  			waitTime += setting.GracefulHammerTime
   151  			break loop
   152  		case change := <-changes:
   153  			switch change.Cmd {
   154  			case svc.Interrogate:
   155  				log.Trace("SVC sent interrogate")
   156  				status <- change.CurrentStatus
   157  			case svc.Stop, svc.Shutdown:
   158  				log.Trace("SVC requested shutdown - shutting down")
   159  				g.DoGracefulShutdown()
   160  				waitTime += setting.GracefulHammerTime
   161  				break loop
   162  			case hammerCode:
   163  				log.Trace("SVC requested hammer - shutting down and hammering immediately")
   164  				g.DoGracefulShutdown()
   165  				g.DoImmediateHammer()
   166  				break loop
   167  			default:
   168  				log.Debug("Unexpected control request: %v", change.Cmd)
   169  			}
   170  		}
   171  	}
   172  
   173  	log.Trace("Sending StopPending state to SVC")
   174  	status <- svc.Status{
   175  		State:    svc.StopPending,
   176  		WaitHint: uint32(waitTime / time.Millisecond),
   177  	}
   178  
   179  hammerLoop:
   180  	for {
   181  		select {
   182  		case change := <-changes:
   183  			switch change.Cmd {
   184  			case svc.Interrogate:
   185  				log.Trace("SVC sent interrogate")
   186  				status <- change.CurrentStatus
   187  			case svc.Stop, svc.Shutdown, hammerCmd:
   188  				log.Trace("SVC requested hammer - hammering immediately")
   189  				g.DoImmediateHammer()
   190  				break hammerLoop
   191  			default:
   192  				log.Debug("Unexpected control request: %v", change.Cmd)
   193  			}
   194  		case <-g.hammerCtx.Done():
   195  			break hammerLoop
   196  		}
   197  	}
   198  
   199  	log.Trace("Stopped")
   200  	return false, 0
   201  }
   202  
   203  // DoImmediateHammer causes an immediate hammer
   204  func (g *Manager) DoImmediateHammer() {
   205  	g.doHammerTime(0 * time.Second)
   206  }
   207  
   208  // DoGracefulShutdown causes a graceful shutdown
   209  func (g *Manager) DoGracefulShutdown() {
   210  	g.lock.Lock()
   211  	select {
   212  	case <-g.shutdownRequested:
   213  		g.lock.Unlock()
   214  	default:
   215  		close(g.shutdownRequested)
   216  		g.lock.Unlock()
   217  		g.doShutdown()
   218  	}
   219  }
   220  
   221  // RegisterServer registers the running of a listening server.
   222  // Any call to RegisterServer must be matched by a call to ServerDone
   223  func (g *Manager) RegisterServer() {
   224  	g.runningServerWaitGroup.Add(1)
   225  }
   226  
   227  func (g *Manager) awaitServer(limit time.Duration) bool {
   228  	c := make(chan struct{})
   229  	go func() {
   230  		defer close(c)
   231  		g.createServerWaitGroup.Wait()
   232  	}()
   233  	if limit > 0 {
   234  		select {
   235  		case <-c:
   236  			return true // completed normally
   237  		case <-time.After(limit):
   238  			return false // timed out
   239  		case <-g.IsShutdown():
   240  			return false
   241  		}
   242  	} else {
   243  		select {
   244  		case <-c:
   245  			return true // completed normally
   246  		case <-g.IsShutdown():
   247  			return false
   248  		}
   249  	}
   250  }