code.gitea.io/gitea@v1.22.3/modules/graceful/restart_unix.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  // This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
     5  
     6  //go:build !windows
     7  
     8  package graceful
     9  
    10  import (
    11  	"fmt"
    12  	"net"
    13  	"os"
    14  	"os/exec"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  	"syscall"
    19  	"time"
    20  )
    21  
    22  var killParent sync.Once
    23  
    24  // KillParent sends the kill signal to the parent process if we are a child
    25  func KillParent() {
    26  	killParent.Do(func() {
    27  		if GetManager().IsChild() {
    28  			ppid := syscall.Getppid()
    29  			if ppid > 1 {
    30  				_ = syscall.Kill(ppid, syscall.SIGTERM)
    31  			}
    32  		}
    33  	})
    34  }
    35  
    36  // RestartProcess starts a new process passing it the active listeners. It
    37  // doesn't fork, but starts a new process using the same environment and
    38  // arguments as when it was originally started. This allows for a newly
    39  // deployed binary to be started. It returns the pid of the newly started
    40  // process when successful.
    41  func RestartProcess() (int, error) {
    42  	listeners := getActiveListeners()
    43  
    44  	// Extract the fds from the listeners.
    45  	files := make([]*os.File, len(listeners))
    46  	for i, l := range listeners {
    47  		var err error
    48  		// Now, all our listeners actually have File() functions so instead of
    49  		// individually casting we just use a hacky interface
    50  		files[i], err = l.(filer).File()
    51  		if err != nil {
    52  			return 0, err
    53  		}
    54  
    55  		if unixListener, ok := l.(*net.UnixListener); ok {
    56  			unixListener.SetUnlinkOnClose(false)
    57  		}
    58  		// Remember to close these at the end.
    59  		defer func(i int) {
    60  			_ = files[i].Close()
    61  		}(i)
    62  	}
    63  
    64  	// Use the original binary location. This works with symlinks such that if
    65  	// the file it points to has been changed we will use the updated symlink.
    66  	argv0, err := exec.LookPath(os.Args[0])
    67  	if err != nil {
    68  		return 0, err
    69  	}
    70  
    71  	// Pass on the environment and replace the old count key with the new one.
    72  	var env []string
    73  	for _, v := range os.Environ() {
    74  		if !strings.HasPrefix(v, listenFDsEnv+"=") {
    75  			env = append(env, v)
    76  		}
    77  	}
    78  	env = append(env, fmt.Sprintf("%s=%d", listenFDsEnv, len(listeners)))
    79  
    80  	if notifySocketAddr != "" {
    81  		env = append(env, fmt.Sprintf("%s=%s", notifySocketEnv, notifySocketAddr))
    82  	}
    83  
    84  	if watchdogTimeout != 0 {
    85  		watchdogStr := strconv.FormatInt(int64(watchdogTimeout/time.Millisecond), 10)
    86  		env = append(env, fmt.Sprintf("%s=%s", watchdogTimeoutEnv, watchdogStr))
    87  	}
    88  
    89  	sb := &strings.Builder{}
    90  	for i, unlink := range getActiveListenersToUnlink() {
    91  		if !unlink {
    92  			continue
    93  		}
    94  		_, _ = sb.WriteString(strconv.Itoa(i))
    95  		_, _ = sb.WriteString(",")
    96  	}
    97  	unlinkStr := sb.String()
    98  	if len(unlinkStr) > 0 {
    99  		unlinkStr = unlinkStr[:len(unlinkStr)-1]
   100  		env = append(env, fmt.Sprintf("%s=%s", unlinkFDsEnv, unlinkStr))
   101  	}
   102  
   103  	allFiles := append([]*os.File{os.Stdin, os.Stdout, os.Stderr}, files...)
   104  	process, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{
   105  		Dir:   originalWD,
   106  		Env:   env,
   107  		Files: allFiles,
   108  	})
   109  	if err != nil {
   110  		return 0, err
   111  	}
   112  	processPid := process.Pid
   113  	_ = process.Release() // no wait, so release
   114  	return processPid, nil
   115  }