-
Notifications
You must be signed in to change notification settings - Fork 6
/
gcnotifier_test.go
127 lines (116 loc) · 2.55 KB
/
gcnotifier_test.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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package gcnotifier
import (
"io/ioutil"
"runtime"
"testing"
"time"
)
func TestAfterGC(t *testing.T) {
doneCh := make(chan struct{})
go func() {
M := &runtime.MemStats{}
NumGC := uint32(0)
gcn := New()
for range gcn.AfterGC() {
runtime.ReadMemStats(M)
NumGC += 1
if NumGC != M.NumGC {
t.Fatal("Skipped a GC notification")
}
if NumGC >= 500 {
gcn.Close()
}
}
doneCh <- struct{}{}
}()
for {
select {
case <-time.After(1 * time.Millisecond):
b := make([]byte, 1<<20)
b[0] = 1
case <-doneCh:
return
}
}
}
func TestDoubleClose(t *testing.T) {
gcn := New()
gcn.Close()
gcn.Close() // no-op
}
func TestAutoclose(t *testing.T) {
count := 10000
done := make(chan struct{})
go func() {
for i := 0; i < count; i++ {
gcn := New().AfterGC()
go func() {
for range gcn {
}
// to reach here autoclose() must have been called
done <- struct{}{}
}()
}
}()
for i := 0; i < count; {
select {
case <-done:
i++
default:
runtime.GC() // required to quickly trigger autoclose()
}
}
}
// Example implements a simple time-based buffering io.Writer: data sent over
// dataCh is buffered for up to 100ms, then flushed out in a single call to
// out.Write and the buffer is reused. If GC runs, the buffer is flushed and
// then discarded so that it can be collected during the next GC run. The
// example is necessarily simplistic, a real implementation would be more
// refined (e.g. on GC flush or resize the buffer based on a threshold,
// perform asynchronous flushes, properly signal completions and propagate
// errors, adaptively preallocate the buffer based on the previous capacity,
// etc.)
func Example() {
dataCh := make(chan []byte)
doneCh := make(chan struct{})
out := ioutil.Discard
go func() {
var buf []byte
var tick <-chan time.Time
gcn := New()
for {
select {
case data := <-dataCh:
if tick == nil {
tick = time.After(100 * time.Millisecond)
}
// received data to write to the buffer
buf = append(buf, data...)
case <-tick:
// time to flush the buffer (but reuse it for the next writes)
if len(buf) > 0 {
out.Write(buf)
buf = buf[:0]
}
tick = nil
case <-gcn.AfterGC():
// GC just ran: flush and then drop the buffer
if len(buf) > 0 {
out.Write(buf)
}
buf = nil
tick = nil
case <-doneCh:
// close the writer: flush the buffer and return
if len(buf) > 0 {
out.Write(buf)
}
return
}
}
}()
for i := 0; i < 1<<20; i++ {
dataCh <- make([]byte, 1024)
}
doneCh <- struct{}{}
}