However, Go developers seems to really want us NOT to do this. So, forking a Go process is not as simple as a C/C++ one. Here I'm going to explain how I did it anyway :P Also setting up some communication between the two processes: Go usual communication do not encompass this use case.
Edit: Well, after more usage, it appears this is not usable. The reason is we are probably conflicting with how Go manages thread and how that does not work very well with fork. Don't think there a good way to "properly" do that without diving into how the Go runtime is working. And at this point, I'm just better off just compiling another binary and calling it.
TL;DR: Fork using cgo and communication using pipes. Note that you can use any usual inter-process communication. Just not the usual Go ones.
Complete code: https://github.com/Jiliac/go-clone/blob/master/fork.go
I - Forking
First make a wrapper around libc fork call. This could be done without libc/cgo, but I went for the simple way :-)
//#include
//
//int cFork() {
// pid_t pid;
// pid = fork();
// return ((int)pid);
//}
import "C"
You can test it works correctly with:
func forkTest()
pid := int(C.cFork())
if pid == 0 {
fmt.Println("Child")
} else {
fmt.Println("Parent")
}
}
So not very complicated in the end.
II - Communication
Any IPC can be used here. Chose to use pipes because that's I'm comfortable with. Can use networking anything as well...
Problem using pipes is that the ones from
os.Pipe
are set with FD_CLOEXEC
flag. (see https://golang.org/src/os/pipe_linux.go?s=319:360#L1).So we have to open these pipes by hand; and then 'convert' them into
os.File
(names are not important) so we can conveniently use them:
func pipe() (r, w *os.File, err error) {
var p [2]int
err = syscall.Pipe(p[0:])
if err != nil {
return r, w, err
}
r = os.NewFile(uintptr(p[0]), "|0")
w = os.NewFile(uintptr(p[1]), "|1")
return r, w, err
}
I have a pipeTest
function on github to check if these are working correctly.
Putting it all together we have:
func forkComTest() {
r, w, err := pipe()
if err != nil {
log.Printf("Failed to create pipes: %v.\n", err)
return
}
pid := int(C.cFork())
if pid == 0 { // Child
fmt.Printf("(from child)\tHello.\n")
err = r.Close()
if err != nil {
log.Printf("Child could not close reading pipe: %v.\n", err)
return
}
_, err := w.Write([]byte(msg))
if err != nil {
log.Printf("Could not write in pipe: %v.\n", err)
}
} else { // Parent
fmt.Printf("(from parent)\tChild pid = %d.\n", pid)
err = w.Close()
if err != nil {
log.Printf("Parent could not close writing pipe: %v\n.", err)
return
}
buf := make([]byte, bufSize)
_, err := r.Read(buf)
if err != nil {
log.Printf("Could not reat from pipe: %v.\n", err)
}
msg := string(buf)
fmt.Printf("(from parent)\tMessage: '%s'\n", msg)
}
}
Which should output something like:
(from parent) Child pid = 47158.
(from child) Hello.
(from parent) Message: 'Hello dad, it's your son :-).'
And here we go.