restart or shutdown golang apps programmatically - go

so i need to make a program than can run, then restart or shutdown programmatically. I need to do like this from terminal:
project> go run app.go
apps started, waiting for 5 sec...
project>
other scenario:
project> go run app.go runner.go
apps started from app.go
runner detecting app.go from runner.go
app restarted after 10 sec from app.go
The program will be something like below:
package main
import(
"something"
)
func main () {
something.KillPrograms({
doingWhatever() //my program here
}, 5000) //program will running for only 5 sec
}
any library or anything from golang can do like that? thanks.

You can try with time.Sleep
package main
import (
"fmt"
"time"
)
func main() {
//use goroutine to run your function separately
//if you want it's sync, you can remove `go`
go doingWhatever()
//after 5 seconds, the program will exit
//even though your function has not been done yet
time.Sleep(5 * time.Second)
os.Exit(0)
}

Many libraries can handle restart/watch of the program. check this answer or this one.
I just tried this simple code that will restart itself each 2 seconds and during that he will run a random check to shutdown.
package main
import (
"fmt"
"math/rand"
"os"
"os/exec"
"runtime"
"sync"
"syscall"
"time"
)
var wg sync.WaitGroup
func main() {
t1 := time.NewTimer(time.Second * 2)
wg.Add(1)
// you can handle to restart programmatically
go func() {
defer wg.Done()
<-t1.C
fmt.Println("Timer expired")
RestartSelf()
}()
fmt.Println(time.Now().Format("2006-Jan-02 ( 15:04:05)"))
rand.Seed(time.Now().UnixNano())
// you can handle the shut-down programmatically
if rand.Intn(3) == 1 {
fmt.Println("It is time to shut-down")
os.Exit(0)
}
wg.Wait()
}
func RestartSelf() error {
self, err := os.Executable()
if err != nil {
return err
}
args := os.Args
env := os.Environ()
// Windows does not support exec syscall.
if runtime.GOOS == "windows" {
cmd := exec.Command(self, args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Env = env
err := cmd.Run()
if err == nil {
os.Exit(0)
}
return err
}
return syscall.Exec(self, args, env)
}
output
2022-Mar-10 ( 17:59:53)
Timer expired
2022-Mar-10 ( 17:59:55)
Timer expired
2022-Mar-10 ( 17:59:57)
It is time to shut-down
Process finished with the exit code 0

Related

Permission denied while reading /proc/pid/smaps file (golang)

Using the os/exec package, I want to run an external command on a *nix OS with another user instead of root. (The main process runs under root user).
The external command runs by go app. But my app can not read /proc/pid/smaps file, following error:
panic: open /proc/2962/smaps: permission denied
goroutine 6 [running]:
main.memwatch(0xc000094000, 0xc00007a0c0)
/src/main.go:41 +0x298
created by main.main
/src/main.go:25 +0x18f
exit status 2
Here is my code:
// main.go
package main
import (
"fmt"
"os"
"os/exec"
"syscall"
"time"
)
func main() {
cmd := exec.Command("sleep", "3")
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
Credential: &syscall.Credential{Uid: 65534, Gid: 65534}, // External command expect run with `nobody` instead of `root` for security reason
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
done := make(chan struct{})
go memwatch(cmd, done)
if err != nil {
panic(err)
}
cmd.Wait()
close(done)
}
func memwatch(cmd *exec.Cmd, done <-chan struct{}) {
// Reuse the reader so we don't have to close and reopen it all the time
smaps, err := os.Open(fmt.Sprintf("/proc/%d/smaps", cmd.Process.Pid))
if err != nil {
panic(err)
}
defer smaps.Close()
for {
select {
case <-done:
return
default:
fmt.Println("running")
time.Sleep(10 * time.Millisecond)
}
}
}
I'm tired. Anyone here for help, please.
I test my code inside a docker container that doesn't have SYS_PTRACE capability. That's why the error shows. The error was gone when I added the SYS_PTRACE capability for that container.

go build doesn't recognise methods

I try to setup a small Golang Microservice for users with Gin and Mongodb.
package main
import (
"context"
"fmt"
"github.com/wzslr321/artiver/entity"
"github.com/wzslr321/artiver/settings"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"log"
"os"
"os/signal"
"syscall"
"time"
)
type application struct {
users *entity.UserCollection
}
var app *application
func init() {
initMongo()
}
func initMongo() {
oc := options.Client().ApplyURI(settings.MongodbSettings.Uri)
client, err := mongo.NewClient(oc)
if err != nil {
log.Fatalf("Error occured while initializing a new mongo client: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
err = client.Connect(ctx)
if err != nil {
log.Fatalf("Errorr occurred while connecting to a client: %v", err)
}
defer func() {
if err = client.Disconnect(ctx); err != nil {
panic(err)
}
}()
log.Println("Successfully connected to the database!")
app = &application{
users: &entity.UserCollection{
C: client.Database("artiver").Collection("users"),
},
}
}
func main() {
router := app.InitRouter()
It doesn't show any errors in my IDE ( GoLand ), but when I try to build it I get an error:
# command-line-arguments
users/cmd/app/main.go:67:15: app.InitRouter undefined (type *application has no field or method InitRouter)
It it easily visible on the image above, that I do have access to such a method. It is defined in the same package.
package main
import (
"github.com/gin-gonic/gin"
cors "github.com/rs/cors/wrapper/gin"
"net/http"
)
func (app *application) InitRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Recovery())
r.Use(cors.Default())
r.GET("/", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "Hello World")
})
user := r.Group("/api/user")
{
user.POST("/add", app.CreateUser)
}
return r
}
I have no idea how am I supposed to fix it and what is done wrong. I'd appreciate any hint about what isn't done correctly.
Answer based on #mkopriva help in comments.
The issue was related to not running all needed .go files.
In my case, the solution was to build it this way in my Makefile:
go build -o $(path)users cmd/app/*
In similar cases, go run . most likely will do the job.

GO attach stdin in test setup method

I am using the built-in testing module to run some functional tests I have in my GO project. In my project I have external dependencies, which I connect to in my TestMain method. I save these connections to variables which then I use in the tests themselves, and the connections can take a long time to establish properly (Kafka anyone?). I would like to run the tests on-demand, but after these variables have been setup.
So what I want is to listen to stdin in my TestMain function and either run or quit the tests. But I want it to be controlled by the user so I can have my test environment setup, and the tests will run on my command.
But sadly, it seems that when running go test ... that stdin is mapped directly to /dev/null. So when I try to read os.Stdin I get an EOF error. The minimum code for this is:
package tests
import (
"bufio"
"fmt"
"os"
"testing"
)
func TestMain(m *testing.M) {
reader := bufio.NewReader(os.Stdin)
if input, err := reader.ReadString('\n'); err != nil {
fmt.Println(err)
fmt.Println("-----------")
fmt.Println(input)
os.Exit(1)
}
fmt.Println("ESCAPED!")
os.Exit(m.Run())
}
I have read in circles how to mock this for unit tests and the sort, but my case is more of a functional test runner. Is there a way, or even some trickery, that will allow me to open or change the test processes stdin?
You can redirect os.Stdin, it depends on OS though:
package tests
import (
"fmt"
"os"
"testing"
"bufio"
"runtime"
)
func TestMain(m *testing.M) {
var ttyName string
if runtime.GOOS == "windows" {
ttyName = "con"
} else {
ttyName = "/dev/tty"
}
f, err := os.Open(ttyName)
if err != nil {
panic(err)
}
defer f.Close()
oldStdin := os.Stdin
defer func() { os.Stdin = oldStdin }()
os.Stdin = f
reader := bufio.NewReader(os.Stdin)
if input, err := reader.ReadString('\n'); err != nil {
fmt.Println("Error:", err)
fmt.Println("-----------")
fmt.Println(input)
os.Exit(1)
}
fmt.Println("ESCAPED!")
os.Exit(m.Run())
}
func TestHello(t *testing.T){
fmt.Println("Hello")
}

Continuously execute tshark from Go script

I am trying to execute tskarh from golang script using the example from
https://tutorialedge.net/golang/executing-system-commands-with-golang/
The script works fine, but i don't receive any kind of output
What i want to get is the following:
Continuously run the script,
capture some packets,
extract some fields values,
and assign to variables
Any help please ?
https://pastebin.com/PeAz7vh9
package main
import (
"fmt"
"os/exec"
"runtime"
)
func execute() {
// here we perform the pwd command.
// we can store the output of this in our out variable
// and catch any errors in err
out, err := exec.Command("tshark", "-i", "em1").CombinedOutput()
// if there is an error with our execution
// handle it here
if err != nil {
fmt.Printf("%s", err)
}
fmt.Println("Command Successfully Executed")
// as the out variable defined above is of type []byte we need to convert
// this to a string or else we will see garbage printed out in our console
// this is how we convert it to a string
output := string(out[:])
// once we have converted it to a string we can then output it.
fmt.Println(output)
}
func main() {
fmt.Println("Simple Shell")
fmt.Println("---------------------")
if runtime.GOOS == "windows" {
fmt.Println("Can't Execute this on a windows machine")
} else {
execute()
}
}
I have no idea of tshark, but here is a code that will work continously, you need os.Interrupt, and select.
package main
import (
"os"
"os/exec"
"os/signal"
)
func main() {
out := exec.Command("ping", "8.8.8.8")
f1, _ := os.OpenFile("./outfile.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755)
f2, _ := os.OpenFile("./errfile.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755)
out.Stdout = f1
out.Stderr = f2
defer func() {
f1.Close()
f2.Close()
}()
err := out.Run()
if err != nil {
panic(err)
}
var ctrlcInt chan os.Signal
ctrlcInt = make(chan os.Signal, 1)
signal.Notify(ctrlcInt, os.Interrupt)
for {
select {
case <-ctrlcInt:
break
default:
continue
}
}
return
}
this code pings 8.8.8.8 and writes out put to outfile.txt, it will exit when you press ctrl+c. If there is error it will write to errfile.txt. You can tail the files and see the output. Hope this helps.

os: process already finished when sending syscall.SIGCONT (possible bug?)

When executing a process and sending signals to it using: Process.Signal I notice that after sending the second signal syscall.SIGCONT I got a: os: process already finished but if using syscall.Kill everything works as expected.
For demonstrative purposes I have created this naive example:
package main
import (
"fmt"
"os"
"os/exec"
"syscall"
"time"
)
func main() {
exit := make(chan error, 1)
go run(exit)
for {
select {
case <-exit:
println("fin, restarting")
run(exit)
default:
time.Sleep(time.Second)
println("running...")
}
}
}
func run(ch chan<- error) {
cmd := exec.Command("sleep", "3")
if err := cmd.Start(); err != nil {
print(err.Error())
os.Exit(1)
}
fmt.Printf("Pid: %d\n", cmd.Process.Pid)
go func() {
ch <- cmd.Wait()
}()
time.Sleep(2 * time.Second)
fmt.Printf("%v\n", cmd.Process.Signal(syscall.SIGSTOP))
time.Sleep(2 * time.Second)
// Using this will return an os: process already finished
fmt.Printf("%v\n", cmd.Process.Signal(syscall.SIGCONT))
// This works as expected
//fmt.Printf("%v\n", syscall.Kill(cmd.Process.Pid, syscall.SIGCONT))
}
So basically if using:
cmd.Process.Signal(syscall.SIGCONT)
os: process already finished is returned
But when using:
syscall.Kill(cmd.Process.Pid, syscall.SIGCONT)
It works as expected.
Could this be a bug on os.exec or it the expected behavior?
UPDATE: seems to be happening only on mac os X
This issue seems to be only happening on Mac OS X, tested on "Sierra" and "El Capitan" https://go-review.googlesource.com/#/c/37610/
So, for now, to keep things cross-platform better user:
syscall.Kill(cmd.Process.Pid, syscall.SIGCONT))
Example code to test if your system has this issue:
package main
import (
"fmt"
"log"
"os/exec"
"syscall"
"unsafe"
)
func main() {
cmd := exec.Command("sleep", "10")
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
// signal when wait4 will return immediately
go func() {
var siginfo [128]byte
psig := &siginfo[0]
_, _, e := syscall.Syscall6(syscall.SYS_WAITID, 1, uintptr(cmd.Process.Pid), uintptr(unsafe.Pointer(psig)), syscall.WEXITED|syscall.WNOWAIT, 0, 0)
fmt.Println("WAITID RETURNED -- this shouldn't happen:", e)
}()
err := cmd.Process.Signal(syscall.SIGSTOP)
if err != nil {
log.Fatal(err)
}
cmd.Wait()
}

Resources