Skip to content

Commit 7beca62

Browse files
committed
Improve winpowrprof callback
1 parent e422e3d commit 7beca62

File tree

3 files changed

+165
-87
lines changed

3 files changed

+165
-87
lines changed

common/oncefunc.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//go:build go1.21
2+
3+
package common
4+
5+
import "sync"
6+
7+
func OnceFunc(f func()) func() {
8+
return sync.OnceFunc(f)
9+
}
10+
11+
func OnceValue[T any](f func() T) func() T {
12+
return sync.OnceValue(f)
13+
}
14+
15+
func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
16+
return sync.OnceValues(f)
17+
}

common/oncefunc_compat.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//go:build !go1.21
2+
3+
// Copyright 2022 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
// copied from go1.22.5
8+
9+
package common
10+
11+
import "sync"
12+
13+
// OnceFunc returns a function that invokes f only once. The returned function
14+
// may be called concurrently.
15+
//
16+
// If f panics, the returned function will panic with the same value on every call.
17+
func OnceFunc(f func()) func() {
18+
var (
19+
once sync.Once
20+
valid bool
21+
p any
22+
)
23+
// Construct the inner closure just once to reduce costs on the fast path.
24+
g := func() {
25+
defer func() {
26+
p = recover()
27+
if !valid {
28+
// Re-panic immediately so on the first call the user gets a
29+
// complete stack trace into f.
30+
panic(p)
31+
}
32+
}()
33+
f()
34+
f = nil // Do not keep f alive after invoking it.
35+
valid = true // Set only if f does not panic.
36+
}
37+
return func() {
38+
once.Do(g)
39+
if !valid {
40+
panic(p)
41+
}
42+
}
43+
}
44+
45+
// OnceValue returns a function that invokes f only once and returns the value
46+
// returned by f. The returned function may be called concurrently.
47+
//
48+
// If f panics, the returned function will panic with the same value on every call.
49+
func OnceValue[T any](f func() T) func() T {
50+
var (
51+
once sync.Once
52+
valid bool
53+
p any
54+
result T
55+
)
56+
g := func() {
57+
defer func() {
58+
p = recover()
59+
if !valid {
60+
panic(p)
61+
}
62+
}()
63+
result = f()
64+
f = nil
65+
valid = true
66+
}
67+
return func() T {
68+
once.Do(g)
69+
if !valid {
70+
panic(p)
71+
}
72+
return result
73+
}
74+
}
75+
76+
// OnceValues returns a function that invokes f only once and returns the values
77+
// returned by f. The returned function may be called concurrently.
78+
//
79+
// If f panics, the returned function will panic with the same value on every call.
80+
func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
81+
var (
82+
once sync.Once
83+
valid bool
84+
p any
85+
r1 T1
86+
r2 T2
87+
)
88+
g := func() {
89+
defer func() {
90+
p = recover()
91+
if !valid {
92+
panic(p)
93+
}
94+
}()
95+
r1, r2 = f()
96+
f = nil
97+
valid = true
98+
}
99+
return func() (T1, T2) {
100+
once.Do(g)
101+
if !valid {
102+
panic(p)
103+
}
104+
return r1, r2
105+
}
106+
}

common/winpowrprof/event_windows.go

Lines changed: 42 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -3,134 +3,89 @@ package winpowrprof
33
// modify from https://github.com/golang/go/blob/b634f6fdcbebee23b7da709a243f3db217b64776/src/runtime/os_windows.go#L257
44

55
import (
6-
"sync"
76
"syscall"
87
"unsafe"
98

10-
"github.com/sagernet/sing/common/x/list"
9+
"github.com/sagernet/sing/common"
1110

1211
"golang.org/x/sys/windows"
1312
)
1413

15-
type powerEventListener struct {
16-
element *list.Element[EventCallback]
17-
}
18-
19-
func NewEventListener(callback EventCallback) (EventListener, error) {
20-
err := initCallback()
21-
if err != nil {
22-
return nil, err
23-
}
24-
access.Lock()
25-
defer access.Unlock()
26-
return &powerEventListener{
27-
element: callbackList.PushBack(callback),
28-
}, nil
29-
}
30-
31-
func (l *powerEventListener) Start() error {
32-
access.Lock()
33-
defer access.Unlock()
34-
if handle != 0 {
35-
return nil
36-
}
37-
return startListener()
38-
}
39-
40-
func (l *powerEventListener) Close() error {
41-
access.Lock()
42-
defer access.Unlock()
43-
if l.element != nil {
44-
callbackList.Remove(l.element)
45-
}
46-
if callbackList.Len() > 0 {
47-
return nil
48-
}
49-
return closeListener()
50-
}
51-
5214
var (
5315
modpowerprof = windows.NewLazySystemDLL("powrprof.dll")
5416
procPowerRegisterSuspendResumeNotification = modpowerprof.NewProc("PowerRegisterSuspendResumeNotification")
5517
procPowerUnregisterSuspendResumeNotification = modpowerprof.NewProc("PowerUnregisterSuspendResumeNotification")
5618
)
5719

58-
var (
59-
access sync.Mutex
60-
callbackList list.List[EventCallback]
61-
initCallbackOnce sync.Once
62-
rawCallback uintptr
63-
handle uintptr
64-
)
20+
var suspendResumeNotificationCallback = common.OnceValue(func() uintptr {
21+
return windows.NewCallback(func(context *EventCallback, changeType uint32, setting uintptr) uintptr {
22+
callback := *context
23+
const (
24+
PBT_APMSUSPEND uint32 = 4
25+
PBT_APMRESUMESUSPEND uint32 = 7
26+
PBT_APMRESUMEAUTOMATIC uint32 = 18
27+
)
28+
var event int
29+
switch changeType {
30+
case PBT_APMSUSPEND:
31+
event = EVENT_SUSPEND
32+
case PBT_APMRESUMESUSPEND:
33+
event = EVENT_RESUME
34+
case PBT_APMRESUMEAUTOMATIC:
35+
event = EVENT_RESUME_AUTOMATIC
36+
default:
37+
return 0
38+
}
39+
callback(event)
40+
return 0
41+
})
42+
})
6543

66-
func initCallback() error {
44+
type powerEventListener struct {
45+
callback EventCallback
46+
handle uintptr
47+
}
48+
49+
func NewEventListener(callback EventCallback) (EventListener, error) {
6750
err := procPowerRegisterSuspendResumeNotification.Find()
6851
if err != nil {
69-
return err // Running on Windows 7, where we don't need it anyway.
52+
return nil, err // Running on Windows 7, where we don't need it anyway.
7053
}
7154
err = procPowerUnregisterSuspendResumeNotification.Find()
7255
if err != nil {
73-
return err // Running on Windows 7, where we don't need it anyway.
56+
return nil, err // Running on Windows 7, where we don't need it anyway.
7457
}
75-
initCallbackOnce.Do(func() {
76-
rawCallback = windows.NewCallback(func(context uintptr, changeType uint32, setting uintptr) uintptr {
77-
const (
78-
PBT_APMSUSPEND uint32 = 4
79-
PBT_APMRESUMESUSPEND uint32 = 7
80-
PBT_APMRESUMEAUTOMATIC uint32 = 18
81-
)
82-
var event int
83-
switch changeType {
84-
case PBT_APMSUSPEND:
85-
event = EVENT_SUSPEND
86-
case PBT_APMRESUMESUSPEND:
87-
event = EVENT_RESUME
88-
case PBT_APMRESUMEAUTOMATIC:
89-
event = EVENT_RESUME_AUTOMATIC
90-
default:
91-
return 0
92-
}
93-
access.Lock()
94-
callbacks := callbackList.Array()
95-
access.Unlock()
96-
for _, callback := range callbacks {
97-
callback(event)
98-
}
99-
return 0
100-
})
101-
})
102-
return nil
58+
return &powerEventListener{
59+
callback: callback,
60+
}, nil
10361
}
10462

105-
func startListener() error {
63+
func (l *powerEventListener) Start() error {
10664
type DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS struct {
10765
callback uintptr
108-
context uintptr
66+
context unsafe.Pointer
10967
}
11068
const DEVICE_NOTIFY_CALLBACK = 2
11169
params := DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS{
112-
callback: rawCallback,
70+
callback: suspendResumeNotificationCallback(),
71+
context: unsafe.Pointer(&l.callback),
11372
}
11473
_, _, errno := syscall.SyscallN(
11574
procPowerRegisterSuspendResumeNotification.Addr(),
11675
DEVICE_NOTIFY_CALLBACK,
11776
uintptr(unsafe.Pointer(&params)),
118-
uintptr(unsafe.Pointer(&handle)),
77+
uintptr(unsafe.Pointer(&l.handle)),
11978
)
12079
if errno != 0 {
12180
return errno
12281
}
12382
return nil
12483
}
12584

126-
func closeListener() error {
127-
if handle == 0 {
128-
return nil
129-
}
130-
_, _, errno := syscall.SyscallN(procPowerUnregisterSuspendResumeNotification.Addr(), uintptr(unsafe.Pointer(&handle)))
85+
func (l *powerEventListener) Close() error {
86+
_, _, errno := syscall.SyscallN(procPowerUnregisterSuspendResumeNotification.Addr(), uintptr(unsafe.Pointer(&l.handle)))
13187
if errno != 0 {
13288
return errno
13389
}
134-
handle = 0
13590
return nil
13691
}

0 commit comments

Comments
 (0)