News for hackers. Stuff that matters.
Re:Writing my own init with Go by chubot on Tuesday February 09, 2016 @12:54PM (#11067182)
NOTE: You can't write an init without threads in Go, as you can in C. (I tried awhile back)

At least you can't with its portable interfaces such as exec.Command() and cmd.Wait():

http://www.mustafaak.in/2016/02/09/forking-process-in-myinit...

You end burning a thread per process, which is pretty lame for an init. The wait() call blocks a goroutine AND OS thread, so the go runtime will have to start a new thread for the next wait().

Whereas in C you can do the entire thing from a simple thread (I call it "async processes"). You set SIGCHLD handler and get async exit notifications, and then process those in a single-threaded loop. I'm pretty sure there is a problem doing this in Go (besides resorting to the syscall module)... I think it has to do with the fact that Go turns signals into messages on a channel, but I don't remember right now...

EDIT: Now I remember. The problem with using the syscall module is that Go does not export Fork and Exec. It exports ForkExec, because it needs to ensure they run on the same thread.

https://golang.org/pkg/syscall/#ForkExec

This severely limits the usefulness of your init program, because in Unix there are all sorts of important things that happen in between. It's how you set the child process state. You basically can't do anything with file descriptors or containers at all with this model.

Re:Writing my own init with Go by pcwalton on Tuesday February 09, 2016 @12:58PM (#11067232)
Signal handlers are pretty much impossible to write directly in a managed language (at least without heavy contortions from the runtime), because you could get a signal during a non-GC-safe point and then deadlock or corrupt your program. For example, malloc often takes locks internally; if you get a signal during malloc with that lock held and then try to malloc in your signal handler you can deadlock. Managed languages like Go treat allocation as an implementation detail rather than something the programmer has to explicitly invoke, making them basically incompatible with signal handlers.

(BTW, this is mostly a problem with signal handlers as a concept, not with Go.)

> The problem with using the syscall module is that Go does not export Fork and Exec. It exports ForkExec, because it needs to ensure they run on the same thread.

And because you can't safely allocate after a fork in managed languages, because fork drops your other threads, leading to the same problems above. :)

Re:Writing my own init with Go by chubot on Tuesday February 09, 2016 @01:12PM (#11067354)
So the way Python does it is that it doesn't call back into the interpreter loop from the signal handler. That would be a really bad idea as you note, because the interpreter can call malloc and do all sorts of other stuff.

It queues your Python signal handling function, returns from the signal handler, and then runs it on the main loop at the next tick.

As far as I know this works fine. I wrote the same program in Python and it seems to work just fine, and have more functionality than the Go version. I guess one problem is that if you do a long blocking syscall on a tick in the main loop, your signal handler can be delayed indefinitely. There might be some hacks around this in the Python interpreter, but I don't recall offhand.

Re:Writing my own init with Go by pcwalton on Tuesday February 09, 2016 @01:38PM (#11067582)
Yeah, that's basically having the runtime advance to the next GC safe point before running the handler. It's not direct control over signal handlers, but it's fairly low overhead. I guess Go could do this too if it wanted to. I don't think it works for fork though.
Re:Writing my own init with Go by chubot on Tuesday February 09, 2016 @02:41PM (#11068144)
Yeah the problem with Go and "async processes" doesn't have to do with GC -- it has to do with M:N threading. I'm glad Rust abandoned M:N threading.

Though it is definitely a killer feature of Go to write in a blocking style rather than in a state machine style... but I feel that there might have been a way to do it without baking it so hard into the runtime (?). Maybe like coroutines in Python, which run on a single thread and are optional.

M:N threading causes a lot of other complications too, like calling from C to Go, etc.

Re:Writing my own init with Go by d0mine on Tuesday February 09, 2016 @02:07PM (#11067811)
A signal interrupts a blocking syscall. You can call PyErr_CheckSignals() that may execute Python signal handler before you restart the syscal on EINTR in C code. Look at how os.waitpid() is implemented: https://github.com/python/cpython/blob/master/Modules/posixm...
Re:Writing my own init with Go by crzwdjk on Tuesday February 09, 2016 @03:23PM (#11068467)
Long blocking syscalls will return with EINTR when a signal is received, at which point the interpreter should be checking the signal flag. The real problem with this scheme is if you call into some long-running C extension code, in which case the interpreter doesn't get a chance to check the signal flag.
Re:Writing my own init with Go by chubot on Tuesday February 09, 2016 @06:48PM (#11069765)
Yeah doing something else on the main interpreter loop, BEFORE checking the signal flag, is what what I meant. The "something else" could be long-running C extension code, or a blocking syscall. (I realize the original signal will interrupt a syscall, but there could be another one after that.)

In either case, the signal handler doesn't get run for an arbitrary period of time. And another signal could occur.

I didn't trace through the code to see how this is handled... but it seems like it is hard to handle simply and correctly (you're kind of recapitulating the problems of signals to begin with, in a higher level language).

Any docs / clarity on this would be interesting to me, as I will probably implement this in my own language soon.

Re:Writing my own init with Go by crzwdjk on Tuesday February 09, 2016 @07:21PM (#11069937)
This is really tricky to get right: it's kind of like the problems you get with threads, except with the added bonus that you can't even wait on a lock, because the lock might be held by the thread that got interrupted. The easiest thing to do is just ignore the problem and not let users install signal handlers written in your language. The next easiest thing is the Python model, and it's pretty much the only option if your runtime is not multithreaded (as Python's is not: thanks to the GIL only one thread can be running Python at a time). If your language runtime is multithreaded, you can try to set things up so all signals are routed to a special signal handling thread that spins in a loop waiting for a signal handler to set a flag, then runs the appropriate handler.
Re:Writing my own init with Go by pjmlp on Tuesday February 09, 2016 @05:23PM (#11069298)
Writing any code in a signal handler besides setting a flag is a very bad idea, specially when writing code across UNIX compliant OSes.

Unless it changed, POSIX gives no guarantees of stack size and function calls re-entrancy guarantees depend a lot on the OS and compiler versions.

Hence the best approach in managed system languages is just to mark a signal as active and re-trigger it outside the handler when control gets back to user space.

Re:Writing my own init with Go by sebcat on Tuesday February 09, 2016 @01:19PM (#11067410)
I don't understand. How is using ForkExec, Wait4 forcing you to use threads? What do you mean with "You basically can't do anything with file descriptors or containers at all with this model."? It is possible to pass a ProcAddr containing a SysProcAddr to ForkExec with fds, chroot, namespaces, &c.

Attached simple demo code of what I mean with ForkExec, Wait4.

EDIT: to clarify, the demo code is not intended to work like init, just to spawn a couple of processes and wait for them to terminate.

        package main

        import (
            "log"
            "syscall"
        )

        func main() {
            cmds := [][]string{
                {"/usr/bin/sleep", "4"},
                {"/usr/bin/sleep", "5"},
                {"/usr/bin/sleep", "1"},
                {"/usr/bin/sleep", "2"},
            }

            for _, cmd := range cmds {
                if len(cmd) > 0 {
                    if _, err := syscall.ForkExec(cmd[0], cmd, nil); err != nil {
                        log.Fatal(err)
                    }
                }
            }

            for nwaited := 0; nwaited < len(cmds); nwaited++ {
                var w syscall.WaitStatus
                pid, err := syscall.Wait4(-1, &w, 0, nil)
                if err != nil {
                    log.Println(err)
                } else {
                    log.Println("pid", pid, "exited", w.Exited(), "exit status", w.ExitStatus())
                }
            }
        }
Re:Writing my own init with Go by chubot on Tuesday February 09, 2016 @01:30PM (#11067513)
OK, I see that there is a lot of stuff like chroot and UIDs in SysProcAttr [1]. I think they have added a bunch since I last tried this.

But the point still stands... you get a subset of the OS API. Everything has to be "canned" into that struct and you can't execute arbitrary code between fork and exec.

Especially with containers, this is a never-ending target... They apparently have user namespaces, but what about network namespaces or IPC namespaces?

[1] https://golang.org/src/syscall/exec_linux.go

Now, it might be good enough for your application, and it probably IS good enough for everything except a full-fledged production quality init system. For that case I would be worried about running into limitations.

Re:Writing my own init with Go by sebcat on Tuesday February 09, 2016 @02:05PM (#11067799)
> Especially with containers, this is a never-ending target... They apparently have user namespaces, but what about network namespaces or IPC namespaces?

There's no reason calls to e.g. unshare(2) and setns(2) can't be added to forkAndExecInChild in exec_linux.go like they have been added for the other syscalls needed to set up a child. I guess if anyone is reading this thread and wants that they can follow the guidelines for contributions and look at https://github.com/golang/go/issues/5968

EDIT: It's also possible to supply CloneFlags though the SysProcAttr struct, e.g., CLONE_NEWIPC, CLONE_NEWNET.

Re:Writing my own init with Go by heavenlyhash on Tuesday February 09, 2016 @02:46PM (#11068185)
You're free to argue that, but it's still fair for everyone to concede that there's a difference in friction between "I can do what I need, now" and "I have to wait for the language core to add features trailing discovering of these niche needs".

Also, the subject's been raised before, and regrettably (IMO) the proposal rejected. As a result, you'll find a nontrivial amount of C appearing in e.g. the runc project. (Which is a crying shame, because it invoking cgo means runc ceased to be trivially reproducibly buildable, last time I checked...)

Re:Writing my own init with Go by falcolas on Tuesday February 09, 2016 @02:02PM (#11067772)
So, don't do process management inside init. Spawn a child and check on it after every "wait" to handle the process management. Less opportunity for the kernel panic, less for init to handle (fewer bugs), greater flexibility.
[deleted]
Re:Writing my own init with Go by chubot on Tuesday February 09, 2016 @01:05PM (#11067289)
Yes, but if you see my edit, even with GOMAXPROCS=1, you are still constrained to the syscall.ForkExec API, which severely limits the usefulness of an init program. Its entire purpose is to start processes in different states (i.e. with different UID, etc.)

Not sure what you mean by 2). If you're suggesting polling, then that sucks, because one important feature of an init system is to restart processes immediately.

All in all my point is that Go is a very poor language for this... worse than Python (which I also tried and ended up using). I think it's great that the author is experimenting though -- I think that's the only way to make these issues clear.

Re:Writing my own init with Go by ominous_prime on Tuesday February 09, 2016 @01:33PM (#11067538)
#1 is not equivalent to being single threaded (every go program is multithreaded), it just means there only 1 thread for user goroutines. Every call to wait will block and spawn a new thread. If you force it to stay on the same thread, you block the runtime, and your entire program.

An init system has to call wait, that's one of its primary tasks, especially on orphaned processes it inherits.

Re:Writing my own init with Go by noselasd on Tuesday February 09, 2016 @01:04PM (#11067274)
Option 1) is not feasible though, as you'll block your one thread.

Though I wonder if you can work around all this by using signalfd (http://man7.org/linux/man-pages/man2/signalfd.2.html) and have SIGCHLD delivered through normal file descriptors - I don't know the low levels of Go enough to know if you can integrate a raw file descriptor with its runtime, or whether messing with SIGCHLD causes other issues, but it might be worth pursuing.

Re:Writing my own init with Go by nteon on Tuesday February 09, 2016 @02:04PM (#11067794)
You could easily do a syscall to get a signalfd file descriptor, then call os.NewFile to turn that numerical FD into a Go *os.File (https://golang.org/pkg/os/#NewFile). It seems like that should work fine!
Re:Writing my own init with Go by vetrom on Tuesday February 09, 2016 @06:01PM (#11069528)
wait() doesn't need to block at all actually, you can use WNOHANG.
Re:Writing my own init with Go by chubot on Tuesday February 09, 2016 @07:57PM (#11070065)
Yeah but then you're polling, which isn't good as mentioned elsewhere).