2019-10-15 16:39:51 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2019-10-15 16:39:51 +03:00
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
2021-08-24 19:47:09 +03:00
//go:build !windows
2019-10-15 16:39:51 +03:00
package graceful
import (
"fmt"
2019-11-24 05:11:24 +03:00
"net"
2019-10-15 16:39:51 +03:00
"os"
"os/exec"
2022-08-14 00:31:33 +03:00
"strconv"
2019-10-15 16:39:51 +03:00
"strings"
2019-10-23 18:32:19 +03:00
"sync"
"syscall"
2019-10-15 16:39:51 +03:00
)
2019-10-23 18:32:19 +03:00
var killParent sync . Once
// KillParent sends the kill signal to the parent process if we are a child
func KillParent ( ) {
killParent . Do ( func ( ) {
2019-12-15 12:51:28 +03:00
if GetManager ( ) . IsChild ( ) {
2019-10-23 18:32:19 +03:00
ppid := syscall . Getppid ( )
if ppid > 1 {
_ = syscall . Kill ( ppid , syscall . SIGTERM )
}
}
} )
}
2019-10-15 16:39:51 +03:00
// RestartProcess starts a new process passing it the active listeners. It
// doesn't fork, but starts a new process using the same environment and
// arguments as when it was originally started. This allows for a newly
// deployed binary to be started. It returns the pid of the newly started
// process when successful.
func RestartProcess ( ) ( int , error ) {
listeners := getActiveListeners ( )
// Extract the fds from the listeners.
files := make ( [ ] * os . File , len ( listeners ) )
for i , l := range listeners {
var err error
// Now, all our listeners actually have File() functions so instead of
// individually casting we just use a hacky interface
files [ i ] , err = l . ( filer ) . File ( )
if err != nil {
return 0 , err
}
2019-11-24 05:11:24 +03:00
if unixListener , ok := l . ( * net . UnixListener ) ; ok {
unixListener . SetUnlinkOnClose ( false )
}
2019-10-15 16:39:51 +03:00
// Remember to close these at the end.
2021-10-17 22:47:12 +03:00
defer func ( i int ) {
_ = files [ i ] . Close ( )
} ( i )
2019-10-15 16:39:51 +03:00
}
// Use the original binary location. This works with symlinks such that if
// the file it points to has been changed we will use the updated symlink.
argv0 , err := exec . LookPath ( os . Args [ 0 ] )
if err != nil {
return 0 , err
}
// Pass on the environment and replace the old count key with the new one.
var env [ ] string
for _ , v := range os . Environ ( ) {
if ! strings . HasPrefix ( v , listenFDs + "=" ) {
env = append ( env , v )
}
}
env = append ( env , fmt . Sprintf ( "%s=%d" , listenFDs , len ( listeners ) ) )
2022-08-14 00:31:33 +03:00
sb := & strings . Builder { }
for i , unlink := range getActiveListenersToUnlink ( ) {
if ! unlink {
continue
}
_ , _ = sb . WriteString ( strconv . Itoa ( i ) )
_ , _ = sb . WriteString ( "," )
}
unlinkStr := sb . String ( )
if len ( unlinkStr ) > 0 {
unlinkStr = unlinkStr [ : len ( unlinkStr ) - 1 ]
env = append ( env , fmt . Sprintf ( "%s=%s" , unlinkFDs , unlinkStr ) )
}
2019-10-15 16:39:51 +03:00
allFiles := append ( [ ] * os . File { os . Stdin , os . Stdout , os . Stderr } , files ... )
process , err := os . StartProcess ( argv0 , os . Args , & os . ProcAttr {
Dir : originalWD ,
Env : env ,
Files : allFiles ,
} )
if err != nil {
return 0 , err
}
return process . Pid , nil
}