Skip to content

Instantly share code, notes, and snippets.

@tevino
Last active January 20, 2024 22:50
Show Gist options
  • Save tevino/3a4f4ec4ea9d0ca66d4f to your computer and use it in GitHub Desktop.
Save tevino/3a4f4ec4ea9d0ca66d4f to your computer and use it in GitHub Desktop.
An example of using epoll in Go
package main
import (
"fmt"
"net"
"os"
"syscall"
)
const (
EPOLLET = 1 << 31
MaxEpollEvents = 32
)
func echo(fd int) {
defer syscall.Close(fd)
var buf [32 * 1024]byte
for {
nbytes, e := syscall.Read(fd, buf[:])
if nbytes > 0 {
fmt.Printf(">>> %s", buf)
syscall.Write(fd, buf[:nbytes])
fmt.Printf("<<< %s", buf)
}
if e != nil {
break
}
}
}
func main() {
var event syscall.EpollEvent
var events [MaxEpollEvents]syscall.EpollEvent
fd, err := syscall.Socket(syscall.AF_INET, syscall.O_NONBLOCK|syscall.SOCK_STREAM, 0)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer syscall.Close(fd)
if err = syscall.SetNonblock(fd, true); err != nil {
fmt.Println("setnonblock1: ", err)
os.Exit(1)
}
addr := syscall.SockaddrInet4{Port: 2000}
copy(addr.Addr[:], net.ParseIP("0.0.0.0").To4())
syscall.Bind(fd, &addr)
syscall.Listen(fd, 10)
epfd, e := syscall.EpollCreate1(0)
if e != nil {
fmt.Println("epoll_create1: ", e)
os.Exit(1)
}
defer syscall.Close(epfd)
event.Events = syscall.EPOLLIN
event.Fd = int32(fd)
if e = syscall.EpollCtl(epfd, syscall.EPOLL_CTL_ADD, fd, &event); e != nil {
fmt.Println("epoll_ctl: ", e)
os.Exit(1)
}
for {
nevents, e := syscall.EpollWait(epfd, events[:], -1)
if e != nil {
fmt.Println("epoll_wait: ", e)
break
}
for ev := 0; ev < nevents; ev++ {
if int(events[ev].Fd) == fd {
connFd, _, err := syscall.Accept(fd)
if err != nil {
fmt.Println("accept: ", err)
continue
}
syscall.SetNonblock(fd, true)
event.Events = syscall.EPOLLIN | EPOLLET
event.Fd = int32(connFd)
if err := syscall.EpollCtl(epfd, syscall.EPOLL_CTL_ADD, connFd, &event); err != nil {
fmt.Print("epoll_ctl: ", connFd, err)
os.Exit(1)
}
} else {
go echo(int(events[ev].Fd))
}
}
}
}
@Togou98
Copy link

Togou98 commented Jun 26, 2020

package syscall define the EPOLLET = -0x8000000 ,but epoll_event field "Events" need uint32 I'm confused 🤣

@lasselj
Copy link

lasselj commented Jun 26, 2020

package syscall define the EPOLLET = -0x8000000 ,but epoll_event field "Events" need uint32 I'm confused 🤣

There's more than one way to skin a cat. Try something like: const EPOLLET uint32 = 1<<31

@cgyim
Copy link

cgyim commented Sep 30, 2020

I guess the fd created for new connection needs to be removed from epollfd before go echo(int(events[ev].Fd)) , and then all data of the detached connection could be handled independently by a goroutine.

@yarbelk
Copy link

yarbelk commented Aug 9, 2021

If the compiler is a C compiler or a GO compiler what difference does it make? It is not the case that the binary produced by one is inherently "slower" than the other.

Yes, of course there's a huge difference.
GO compilers compiles your code to run inside a runtime, which is essentially an abstraction layer doing all the dirty work that C programmers manually do (memory management, process scheduling, and all of that).

But then again, this is the most important reason in my opinion:
epoll() is normally used because it provides a performance orders of magnitude bigger than a normal poll() or a select().
When you require very high performance, you don't want to ruin in because the garbage collector kicks in.

Or you want to do edge detection and sleep to save battery. in this case; you may be triggering code that itself doesn't need optimization and can run happily in a go with GC; but you want to sleep as much as possible until the triggering event.

@lasselj
Copy link

lasselj commented Aug 9, 2021

If the compiler is a C compiler or a GO compiler what difference does it make? It is not the case that the binary produced by one is inherently "slower" than the other.

Yes, of course there's a huge difference.
GO compilers compiles your code to run inside a runtime, which is essentially an abstraction layer doing all the dirty work that C programmers manually do (memory management, process scheduling, and all of that).

But then again, this is the most important reason in my opinion:
epoll() is normally used because it provides a performance orders of magnitude bigger than a normal poll() or a select().
When you require very high performance, you don't want to ruin in because the garbage collector kicks in.

Go is not Java. Go code does not get compiled into platform independent byte code which is then "interpreted" at runtime "inside" anything. Narrowly as it relates to epoll you are wrong in your assessment that cgo is faster at performing a syscall operation, it just isn't. More broadly as it relates to the runtime libraries used by different languages you are also wrong. For fun, you could look up how in C main() gets called by the c runtime library or in C++ you could investigate what happens when someone calls new or free..... In any event, the fact that a language and its runtime library has memory allocation and garbage collection functionality does not infer that it is by definition "slower" than a language that doesn't. Similarly, a language that doesn't, does not have determinism built-in to its memory allocation allocation/deallocation system, which is - believe it or not - a part of its runtime library functions. Lastly, you are not wrong that there are instances where you could write something in C/C++ that would perform better than the equivalent written in Go. But as it relates to using cgo for epoll in this case you are wrong to assume that syscall() used in cgo is by definition faster than syscall.Syscall in go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment