mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-03-26 09:35:52 +00:00
277 lines
5.2 KiB
Go
277 lines
5.2 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
package session
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// epollEventLoop implements EventLoop using epoll (Linux)
|
|
type epollEventLoop struct {
|
|
epfd int
|
|
mu sync.Mutex
|
|
running bool
|
|
stopChan chan struct{}
|
|
fdData map[int]interface{}
|
|
}
|
|
|
|
func newPlatformEventLoop() (EventLoop, error) {
|
|
epfd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create epoll: %w", err)
|
|
}
|
|
|
|
return &epollEventLoop{
|
|
epfd: epfd,
|
|
stopChan: make(chan struct{}),
|
|
fdData: make(map[int]interface{}),
|
|
}, nil
|
|
}
|
|
|
|
func (e *epollEventLoop) Add(fd int, events EventType, data interface{}) error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
e.fdData[fd] = data
|
|
|
|
var epollEvents uint32
|
|
if events&EventRead != 0 {
|
|
epollEvents |= unix.EPOLLIN | unix.EPOLLPRI
|
|
}
|
|
if events&EventWrite != 0 {
|
|
epollEvents |= unix.EPOLLOUT
|
|
}
|
|
if events&EventError != 0 {
|
|
epollEvents |= unix.EPOLLERR
|
|
}
|
|
if events&EventHup != 0 {
|
|
epollEvents |= unix.EPOLLHUP | unix.EPOLLRDHUP
|
|
}
|
|
|
|
// Use edge-triggered mode for better performance
|
|
epollEvents |= unix.EPOLLET
|
|
|
|
event := unix.EpollEvent{
|
|
Events: epollEvents,
|
|
Fd: int32(fd),
|
|
}
|
|
|
|
if err := unix.EpollCtl(e.epfd, unix.EPOLL_CTL_ADD, fd, &event); err != nil {
|
|
delete(e.fdData, fd)
|
|
return fmt.Errorf("failed to add fd %d to epoll: %w", fd, err)
|
|
}
|
|
|
|
// Set non-blocking mode
|
|
if err := unix.SetNonblock(fd, true); err != nil {
|
|
// Not fatal, but log it
|
|
debugLog("[WARN] Failed to set non-blocking mode on fd %d: %v", fd, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *epollEventLoop) Remove(fd int) error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
delete(e.fdData, fd)
|
|
|
|
if err := unix.EpollCtl(e.epfd, unix.EPOLL_CTL_DEL, fd, nil); err != nil {
|
|
if err != syscall.ENOENT {
|
|
return fmt.Errorf("failed to remove fd %d from epoll: %w", fd, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *epollEventLoop) Modify(fd int, events EventType) error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
var epollEvents uint32
|
|
if events&EventRead != 0 {
|
|
epollEvents |= unix.EPOLLIN | unix.EPOLLPRI
|
|
}
|
|
if events&EventWrite != 0 {
|
|
epollEvents |= unix.EPOLLOUT
|
|
}
|
|
if events&EventError != 0 {
|
|
epollEvents |= unix.EPOLLERR
|
|
}
|
|
if events&EventHup != 0 {
|
|
epollEvents |= unix.EPOLLHUP | unix.EPOLLRDHUP
|
|
}
|
|
|
|
// Use edge-triggered mode
|
|
epollEvents |= unix.EPOLLET
|
|
|
|
event := unix.EpollEvent{
|
|
Events: epollEvents,
|
|
Fd: int32(fd),
|
|
}
|
|
|
|
if err := unix.EpollCtl(e.epfd, unix.EPOLL_CTL_MOD, fd, &event); err != nil {
|
|
return fmt.Errorf("failed to modify fd %d in epoll: %w", fd, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *epollEventLoop) Run(handler EventHandler) error {
|
|
e.mu.Lock()
|
|
if e.running {
|
|
e.mu.Unlock()
|
|
return fmt.Errorf("event loop already running")
|
|
}
|
|
e.running = true
|
|
e.mu.Unlock()
|
|
|
|
defer func() {
|
|
e.mu.Lock()
|
|
e.running = false
|
|
e.mu.Unlock()
|
|
}()
|
|
|
|
events := make([]unix.EpollEvent, 128)
|
|
|
|
for {
|
|
select {
|
|
case <-e.stopChan:
|
|
return nil
|
|
default:
|
|
}
|
|
|
|
// Wait for events with 100ms timeout to check for stop
|
|
n, err := unix.EpollWait(e.epfd, events, 100)
|
|
|
|
if err != nil {
|
|
if err == unix.EINTR {
|
|
continue
|
|
}
|
|
return fmt.Errorf("epoll wait failed: %w", err)
|
|
}
|
|
|
|
// Process events
|
|
for i := 0; i < n; i++ {
|
|
event := &events[i]
|
|
fd := int(event.Fd)
|
|
|
|
e.mu.Lock()
|
|
data := e.fdData[fd]
|
|
e.mu.Unlock()
|
|
|
|
var eventType EventType
|
|
|
|
// Convert epoll events to our EventType
|
|
if event.Events&(unix.EPOLLIN|unix.EPOLLPRI) != 0 {
|
|
eventType |= EventRead
|
|
}
|
|
if event.Events&unix.EPOLLOUT != 0 {
|
|
eventType |= EventWrite
|
|
}
|
|
if event.Events&(unix.EPOLLHUP|unix.EPOLLRDHUP) != 0 {
|
|
eventType |= EventHup
|
|
}
|
|
if event.Events&unix.EPOLLERR != 0 {
|
|
eventType |= EventError
|
|
}
|
|
|
|
handler(Event{
|
|
FD: fd,
|
|
Events: eventType,
|
|
Data: data,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e *epollEventLoop) RunOnce(handler EventHandler, timeoutMs int) error {
|
|
events := make([]unix.EpollEvent, 128)
|
|
|
|
n, err := unix.EpollWait(e.epfd, events, timeoutMs)
|
|
if err != nil {
|
|
if err == unix.EINTR {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("epoll wait failed: %w", err)
|
|
}
|
|
|
|
// Process events
|
|
for i := 0; i < n; i++ {
|
|
event := &events[i]
|
|
fd := int(event.Fd)
|
|
|
|
e.mu.Lock()
|
|
data := e.fdData[fd]
|
|
e.mu.Unlock()
|
|
|
|
var eventType EventType
|
|
|
|
if event.Events&(unix.EPOLLIN|unix.EPOLLPRI) != 0 {
|
|
eventType |= EventRead
|
|
}
|
|
if event.Events&unix.EPOLLOUT != 0 {
|
|
eventType |= EventWrite
|
|
}
|
|
if event.Events&(unix.EPOLLHUP|unix.EPOLLRDHUP) != 0 {
|
|
eventType |= EventHup
|
|
}
|
|
if event.Events&unix.EPOLLERR != 0 {
|
|
eventType |= EventError
|
|
}
|
|
|
|
handler(Event{
|
|
FD: fd,
|
|
Events: eventType,
|
|
Data: data,
|
|
})
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *epollEventLoop) Stop() error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
// Only close if not already closed
|
|
select {
|
|
case <-e.stopChan:
|
|
// Already closed
|
|
default:
|
|
close(e.stopChan)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *epollEventLoop) Close() error {
|
|
// Stop the event loop first
|
|
if e.running {
|
|
e.Stop()
|
|
// Give it a moment to stop
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
if e.epfd >= 0 {
|
|
err := unix.Close(e.epfd)
|
|
e.epfd = -1
|
|
|
|
// Recreate stop channel for potential reuse
|
|
e.stopChan = make(chan struct{})
|
|
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|