summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-19 20:29:31 +0300
committerPaul Buetow <paul@buetow.org>2026-05-19 20:29:31 +0300
commit11a8642b7035ff558fb84d7761e93525c84e4908 (patch)
treeaa1f501fcf8f3a5474d26658731782e061cccc15 /cmd
parentc67b34fca467fc4e5e8aba7a1b8929d8aa55a833 (diff)
z6: add KindPoll wiring for poll/select ready counts
Diffstat (limited to 'cmd')
-rw-r--r--cmd/ioworkload/scenario_polling.go114
-rw-r--r--cmd/ioworkload/scenario_polling_test.go64
2 files changed, 178 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_polling.go b/cmd/ioworkload/scenario_polling.go
index 1531511..e41ec9a 100644
--- a/cmd/ioworkload/scenario_polling.go
+++ b/cmd/ioworkload/scenario_polling.go
@@ -50,11 +50,40 @@ func pollingEpoll() error {
pwait2Supported = false
}
}
+ if err := waitAndDrainReadiness(pipefd, callPoll); err != nil {
+ return err
+ }
+ if err := waitAndDrainReadiness(pipefd, callPpoll); err != nil {
+ return err
+ }
+ if err := waitAndDrainReadiness(pipefd, callSelect); err != nil {
+ return err
+ }
+ if err := waitAndDrainReadiness(pipefd, callPselect6); err != nil {
+ return err
+ }
+ time.Sleep(2 * time.Millisecond)
}
return nil
}
+type readinessWaitFn func(pipefd [2]int) (int, error)
+
+func waitAndDrainReadiness(pipefd [2]int, waitFn readinessWaitFn) error {
+ if _, err := syscall.Write(pipefd[1], []byte{1}); err != nil {
+ return fmt.Errorf("write wake byte: %w", err)
+ }
+ ready, err := waitFn(pipefd)
+ if err != nil {
+ return err
+ }
+ if ready < 1 {
+ return fmt.Errorf("polling wait returned %d ready events", ready)
+ }
+ return drainWakeByte(pipefd[0])
+}
+
func waitAndDrain(epfd int, pipefd [2]int, waitFn func(int, []unix.EpollEvent) (int, error)) error {
if _, err := syscall.Write(pipefd[1], []byte{1}); err != nil {
return fmt.Errorf("write wake byte: %w", err)
@@ -134,6 +163,91 @@ func callEpollPwait2(epfd int, events []unix.EpollEvent) (int, error) {
return int(r1), nil
}
+func callPoll(pipefd [2]int) (int, error) {
+ fds := []unix.PollFd{{Fd: int32(pipefd[0]), Events: unix.POLLIN}}
+ r1, _, errno := syscall.RawSyscall(
+ syscall.SYS_POLL,
+ uintptr(unsafe.Pointer(&fds[0])),
+ uintptr(len(fds)),
+ uintptr(100),
+ )
+ runtime.KeepAlive(fds)
+ if errno != 0 {
+ return 0, fmt.Errorf("poll: %w", errno)
+ }
+ return int(r1), nil
+}
+
+func callPpoll(pipefd [2]int) (int, error) {
+ fds := []unix.PollFd{{Fd: int32(pipefd[0]), Events: unix.POLLIN}}
+ timeout := unix.Timespec{Sec: 0, Nsec: 100 * 1_000_000}
+ r1, _, errno := syscall.RawSyscall6(
+ unix.SYS_PPOLL,
+ uintptr(unsafe.Pointer(&fds[0])),
+ uintptr(len(fds)),
+ uintptr(unsafe.Pointer(&timeout)),
+ 0,
+ 0,
+ 0,
+ )
+ runtime.KeepAlive(fds)
+ runtime.KeepAlive(timeout)
+ if errno != 0 {
+ return 0, fmt.Errorf("ppoll: %w", errno)
+ }
+ return int(r1), nil
+}
+
+type fdSet [16]uint64
+
+func (s *fdSet) set(fd int) {
+ idx := fd / 64
+ bit := uint(fd % 64)
+ s[idx] |= 1 << bit
+}
+
+func callSelect(pipefd [2]int) (int, error) {
+ var readSet fdSet
+ readSet.set(pipefd[0])
+ timeout := syscall.Timeval{Sec: 0, Usec: 100000}
+ r1, _, errno := syscall.RawSyscall6(
+ syscall.SYS_SELECT,
+ uintptr(pipefd[0]+1),
+ uintptr(unsafe.Pointer(&readSet)),
+ 0,
+ 0,
+ uintptr(unsafe.Pointer(&timeout)),
+ 0,
+ )
+ runtime.KeepAlive(readSet)
+ runtime.KeepAlive(timeout)
+ if errno != 0 {
+ return 0, fmt.Errorf("select: %w", errno)
+ }
+ return int(r1), nil
+}
+
+func callPselect6(pipefd [2]int) (int, error) {
+ var readSet fdSet
+ readSet.set(pipefd[0])
+ timeout := unix.Timespec{Sec: 0, Nsec: 100 * 1_000_000}
+ r1, _, errno := syscall.RawSyscall6(
+ unix.SYS_PSELECT6,
+ uintptr(pipefd[0]+1),
+ uintptr(unsafe.Pointer(&readSet)),
+ 0,
+ 0,
+ uintptr(unsafe.Pointer(&timeout)),
+ 0,
+ )
+ runtime.KeepAlive(readSet)
+ runtime.KeepAlive(timeout)
+ if errno != 0 {
+ return 0, fmt.Errorf("pselect6: %w", errno)
+ }
+ return int(r1), nil
+}
+
func isUnsupportedEpollPwait2Err(err error) bool {
if err == nil {
return false
diff --git a/cmd/ioworkload/scenario_polling_test.go b/cmd/ioworkload/scenario_polling_test.go
index 83e6d7c..304602c 100644
--- a/cmd/ioworkload/scenario_polling_test.go
+++ b/cmd/ioworkload/scenario_polling_test.go
@@ -1,6 +1,7 @@
package main
import (
+ "errors"
"fmt"
"syscall"
"testing"
@@ -31,3 +32,66 @@ func TestIsUnsupportedEpollPwait2Err(t *testing.T) {
})
}
}
+
+func TestFdSetSetMarksBit(t *testing.T) {
+ t.Parallel()
+
+ var set fdSet
+ set.set(65)
+ if set[1]&(1<<1) == 0 {
+ t.Fatalf("fd bit was not set: %#v", set)
+ }
+}
+
+func TestClassicPollingSyscallsReturnReadyCount(t *testing.T) {
+ var pipefd [2]int
+ if err := syscall.Pipe(pipefd[:]); err != nil {
+ t.Fatalf("pipe: %v", err)
+ }
+ defer syscall.Close(pipefd[0]) //nolint:errcheck
+ defer syscall.Close(pipefd[1]) //nolint:errcheck
+
+ waiters := []struct {
+ name string
+ fn readinessWaitFn
+ }{
+ {name: "poll", fn: callPoll},
+ {name: "ppoll", fn: callPpoll},
+ {name: "select", fn: callSelect},
+ {name: "pselect6", fn: callPselect6},
+ }
+
+ for _, waiter := range waiters {
+ t.Run(waiter.name, func(t *testing.T) {
+ if _, err := syscall.Write(pipefd[1], []byte{1}); err != nil {
+ t.Fatalf("write wake byte: %v", err)
+ }
+
+ ready, err := waiter.fn(pipefd)
+ if err != nil {
+ if isUnsupportedPollingErr(err) {
+ t.Skipf("%s unsupported on this kernel: %v", waiter.name, err)
+ }
+ t.Fatalf("%s: %v", waiter.name, err)
+ }
+ if ready < 1 {
+ t.Fatalf("%s ready count = %d, want >=1", waiter.name, ready)
+ }
+
+ if err := drainWakeByte(pipefd[0]); err != nil {
+ t.Fatalf("drain wake byte: %v", err)
+ }
+ })
+ }
+}
+
+func isUnsupportedPollingErr(err error) bool {
+ if err == nil {
+ return false
+ }
+ var errno syscall.Errno
+ if !errors.As(err, &errno) {
+ return false
+ }
+ return errno == syscall.ENOSYS || errno == syscall.ENOTSUP
+}