-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathgcnotifier.go
103 lines (92 loc) · 3.54 KB
/
gcnotifier.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// Package gcnotifier provides a way to receive notifications after every
// garbage collection (GC) cycle. This can be useful, in long-running programs,
// to instruct your code to free additional memory resources that you may be
// using.
//
// A common use case for this is when you have custom data structures (e.g.
// buffers, caches, rings, trees, pools, ...): instead of setting a maximum size
// to your data structure you can leave it unbounded and then drop all (or some)
// of the allocated-but-unused slots after every GC run (e.g. sync.Pool drops
// all allocated-but-unused objects in the pool during GC).
//
// To minimize the load on the GC the code that runs after receiving the
// notification should try to avoid allocations as much as possible, or at the
// very least make sure that the amount of new memory allocated is significantly
// smaller than the amount of memory that has been "freed" in response to the
// notification.
//
// GCNotifier guarantees to send a notification after every GC cycle completes.
// Note that the Go runtime does not guarantee that the GC will run:
// specifically there is no guarantee that a GC will run before the program
// terminates.
package gcnotifier
import "runtime"
// GCNotifier allows your code to control and receive notifications every time
// the garbage collector runs.
type GCNotifier struct {
n *gcnotifier
}
type gcnotifier struct {
doneCh chan struct{}
gcCh chan struct{}
}
type sentinel gcnotifier
// AfterGC returns the channel that will receive a notification after every GC
// run. No further notifications will be sent until the previous notification
// has been consumed. To stop notifications immediately call the Close() method.
// Otherwise notifications will continue until the GCNotifier object itself is
// garbage collected. Note that the channel returned by AfterGC will be closed
// only when GCNotifier is garbage collected.
// The channel is unique to a single GCNotifier object: use dedicated
// GCNotifiers if you need to listen for GC notifications in multiple receivers
// at the same time.
func (n *GCNotifier) AfterGC() <-chan struct{} {
return n.n.gcCh
}
// Close will stop and release all resources associated with the GCNotifier. It
// is not required to call Close explicitly: when the GCNotifier object is
// garbage collected Close is called implicitly.
// If you don't call Close explicitly make sure not to accidently maintain the
// GCNotifier object alive.
func (n *GCNotifier) Close() {
autoclose(n.n)
}
// autoclose is both called explicitely via Close or when the GCNotifier is
// garbage collected
func autoclose(n *gcnotifier) {
select {
case n.doneCh <- struct{}{}:
default:
}
}
// New creates and arms a new GCNotifier.
func New() *GCNotifier {
n := &gcnotifier{
gcCh: make(chan struct{}, 1),
doneCh: make(chan struct{}, 1),
}
// sentinel is dead immediately after the call to SetFinalizer
runtime.SetFinalizer(&sentinel{gcCh: n.gcCh, doneCh: n.doneCh}, finalizer)
// n will be dead when the GCNotifier that wraps it (see the return below) is dead
runtime.SetFinalizer(n, autoclose)
// we wrap the internal gcnotifier object in a GCNotifier so that we can
// safely call autoclose when the GCNotifier becomes unreachable
return &GCNotifier{n: n}
}
func finalizer(s *sentinel) {
// check if we have to shutdown
select {
case <-s.doneCh:
close(s.gcCh)
return
default:
}
// send the notification
select {
case s.gcCh <- struct{}{}:
default:
// drop it if there's already an unread notification in gcCh
}
// rearm the finalizer
runtime.SetFinalizer(s, finalizer)
}