vibetunnel/linux/pkg/session/eventloop_darwin.go
2025-06-21 02:49:38 +02:00

280 lines
5.1 KiB
Go

//go:build darwin || freebsd || openbsd || netbsd
// +build darwin freebsd openbsd netbsd
package session
import (
"fmt"
"sync"
"syscall"
"time"
"golang.org/x/sys/unix"
)
// kqueueEventLoop implements EventLoop using kqueue (macOS/BSD)
type kqueueEventLoop struct {
kq int
mu sync.Mutex
running bool
stopChan chan struct{}
fdData map[int]interface{}
}
func newPlatformEventLoop() (EventLoop, error) {
kq, err := unix.Kqueue()
if err != nil {
return nil, fmt.Errorf("failed to create kqueue: %w", err)
}
return &kqueueEventLoop{
kq: kq,
stopChan: make(chan struct{}),
fdData: make(map[int]interface{}),
}, nil
}
func (e *kqueueEventLoop) Add(fd int, events EventType, data interface{}) error {
e.mu.Lock()
defer e.mu.Unlock()
e.fdData[fd] = data
var kevents []unix.Kevent_t
if events&EventRead != 0 {
kevents = append(kevents, unix.Kevent_t{
Ident: uint64(fd),
Filter: unix.EVFILT_READ,
Flags: unix.EV_ADD | unix.EV_ENABLE,
})
}
if events&EventWrite != 0 {
kevents = append(kevents, unix.Kevent_t{
Ident: uint64(fd),
Filter: unix.EVFILT_WRITE,
Flags: unix.EV_ADD | unix.EV_ENABLE,
})
}
if len(kevents) > 0 {
_, err := unix.Kevent(e.kq, kevents, nil, nil)
if err != nil {
delete(e.fdData, fd)
return fmt.Errorf("failed to add fd %d to kqueue: %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 *kqueueEventLoop) Remove(fd int) error {
e.mu.Lock()
defer e.mu.Unlock()
delete(e.fdData, fd)
// Remove both read and write filters
kevents := []unix.Kevent_t{
{
Ident: uint64(fd),
Filter: unix.EVFILT_READ,
Flags: unix.EV_DELETE,
},
{
Ident: uint64(fd),
Filter: unix.EVFILT_WRITE,
Flags: unix.EV_DELETE,
},
}
_, err := unix.Kevent(e.kq, kevents, nil, nil)
if err != nil && err != syscall.ENOENT {
return fmt.Errorf("failed to remove fd %d from kqueue: %w", fd, err)
}
return nil
}
func (e *kqueueEventLoop) Modify(fd int, events EventType) error {
// For kqueue, we need to remove and re-add
if err := e.Remove(fd); err != nil {
return err
}
e.mu.Lock()
data := e.fdData[fd]
e.mu.Unlock()
return e.Add(fd, events, data)
}
func (e *kqueueEventLoop) 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.Kevent_t, 128)
for {
select {
case <-e.stopChan:
return nil
default:
}
// Wait for events with 100ms timeout to check for stop
n, err := unix.Kevent(e.kq, nil, events, &unix.Timespec{
Sec: 0,
Nsec: 100 * 1000 * 1000, // 100ms
})
if err != nil {
if err == unix.EINTR {
continue
}
return fmt.Errorf("kevent wait failed: %w", err)
}
// Process events
for i := 0; i < n; i++ {
event := &events[i]
fd := int(event.Ident)
e.mu.Lock()
data := e.fdData[fd]
e.mu.Unlock()
var eventType EventType
// Convert kqueue events to our EventType
if event.Filter == unix.EVFILT_READ {
eventType |= EventRead
}
if event.Filter == unix.EVFILT_WRITE {
eventType |= EventWrite
}
if event.Flags&unix.EV_EOF != 0 {
eventType |= EventHup
}
if event.Flags&unix.EV_ERROR != 0 {
eventType |= EventError
}
handler(Event{
FD: fd,
Events: eventType,
Data: data,
})
}
}
}
func (e *kqueueEventLoop) RunOnce(handler EventHandler, timeoutMs int) error {
events := make([]unix.Kevent_t, 128)
var timeout *unix.Timespec
if timeoutMs >= 0 {
timeout = &unix.Timespec{
Sec: int64(timeoutMs / 1000),
Nsec: int64((timeoutMs % 1000) * 1000 * 1000),
}
}
n, err := unix.Kevent(e.kq, nil, events, timeout)
if err != nil {
if err == unix.EINTR {
return nil
}
return fmt.Errorf("kevent wait failed: %w", err)
}
// Process events
for i := 0; i < n; i++ {
event := &events[i]
fd := int(event.Ident)
e.mu.Lock()
data := e.fdData[fd]
e.mu.Unlock()
var eventType EventType
if event.Filter == unix.EVFILT_READ {
eventType |= EventRead
}
if event.Filter == unix.EVFILT_WRITE {
eventType |= EventWrite
}
if event.Flags&unix.EV_EOF != 0 {
eventType |= EventHup
}
if event.Flags&unix.EV_ERROR != 0 {
eventType |= EventError
}
handler(Event{
FD: fd,
Events: eventType,
Data: data,
})
}
return nil
}
func (e *kqueueEventLoop) 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 *kqueueEventLoop) 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.kq >= 0 {
err := unix.Close(e.kq)
e.kq = -1
// Recreate stop channel for potential reuse
e.stopChan = make(chan struct{})
return err
}
return nil
}