diff --git a/common/winpowrprof/event_test.go b/common/winpowrprof/event_test.go index 1113fe3c8..dfed6d1b4 100644 --- a/common/winpowrprof/event_test.go +++ b/common/winpowrprof/event_test.go @@ -4,6 +4,8 @@ import ( "runtime" "testing" + F "github.com/sagernet/sing/common/format" + "github.com/stretchr/testify/require" ) @@ -11,9 +13,19 @@ func TestPowerEvents(t *testing.T) { if runtime.GOOS != "windows" { t.SkipNow() } + t.Parallel() listener, err := NewEventListener(func(event int) {}) require.NoError(t, err) require.NotNil(t, listener) require.NoError(t, listener.Start()) require.NoError(t, listener.Close()) } + +func TestBatchPowerEvents(t *testing.T) { + if runtime.GOOS != "windows" { + t.SkipNow() + } + for i := 0; i < 100; i++ { + t.Run(F.ToString(i), TestPowerEvents) + } +} diff --git a/common/winpowrprof/event_windows.go b/common/winpowrprof/event_windows.go index 26c776d06..f34404f87 100644 --- a/common/winpowrprof/event_windows.go +++ b/common/winpowrprof/event_windows.go @@ -7,6 +7,7 @@ import ( "unsafe" "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" "golang.org/x/sys/windows" ) @@ -42,6 +43,7 @@ var suspendResumeNotificationCallback = common.OnceValue(func() uintptr { }) type powerEventListener struct { + pinner myPinner callback EventCallback handle uintptr } @@ -61,6 +63,7 @@ func NewEventListener(callback EventCallback) (EventListener, error) { } func (l *powerEventListener) Start() error { + l.pinner.Pin(l.callback) type DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS struct { callback uintptr context unsafe.Pointer @@ -77,15 +80,25 @@ func (l *powerEventListener) Start() error { uintptr(unsafe.Pointer(&l.handle)), ) if errno != 0 { + l.pinner.Unpin() return errno } + if l.handle == 0 { + l.pinner.Unpin() + return E.New("PowerRegisterSuspendResumeNotification returned success but handle is zero") + } return nil } func (l *powerEventListener) Close() error { - _, _, errno := syscall.SyscallN(procPowerUnregisterSuspendResumeNotification.Addr(), uintptr(unsafe.Pointer(&l.handle))) - if errno != 0 { - return errno + if l.handle == 0 { + return nil + } + defer l.pinner.Unpin() + r0, _, _ := syscall.SyscallN(procPowerUnregisterSuspendResumeNotification.Addr(), l.handle) + if r0 != windows.NO_ERROR { + return syscall.Errno(r0) } + l.handle = 0 return nil } diff --git a/common/winpowrprof/pinner.go b/common/winpowrprof/pinner.go new file mode 100644 index 000000000..f3cf1daa5 --- /dev/null +++ b/common/winpowrprof/pinner.go @@ -0,0 +1,7 @@ +//go:build go1.21 + +package winpowrprof + +import "runtime" + +type myPinner = runtime.Pinner diff --git a/common/winpowrprof/pinner_compat.go b/common/winpowrprof/pinner_compat.go new file mode 100644 index 000000000..c52fd9fcf --- /dev/null +++ b/common/winpowrprof/pinner_compat.go @@ -0,0 +1,11 @@ +//go:build !go1.21 + +package winpowrprof + +type myPinner struct{} + +func (p *myPinner) Pin(pointer any) { +} + +func (p *myPinner) Unpin() { +}