diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-19 20:29:31 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-19 20:29:31 +0300 |
| commit | 11a8642b7035ff558fb84d7761e93525c84e4908 (patch) | |
| tree | aa1f501fcf8f3a5474d26658731782e061cccc15 /cmd | |
| parent | c67b34fca467fc4e5e8aba7a1b8929d8aa55a833 (diff) | |
z6: add KindPoll wiring for poll/select ready counts
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/ioworkload/scenario_polling.go | 114 | ||||
| -rw-r--r-- | cmd/ioworkload/scenario_polling_test.go | 64 |
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 +} |
