Skip to content

Commit 18d8aa6

Browse files
committedDec 9, 2019
vendor zrepl's circlog to break circular module dependency
1 parent f34099f commit 18d8aa6

File tree

7 files changed

+369
-251
lines changed

7 files changed

+369
-251
lines changed
 

‎dial.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"syscall"
1212
"time"
1313

14-
"github.com/zrepl/zrepl/util/circlog"
14+
"github.com/problame/go-netssh/internal/circlog"
1515
)
1616

1717
type Endpoint struct {

‎go.mod

+2-17
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,10 @@ go 1.11
44

55
require (
66
github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff
7+
github.com/inconshreveable/mousetrap v1.0.0 // indirect
78
github.com/spf13/cobra v0.0.2
9+
github.com/spf13/pflag v1.0.5 // indirect
810
github.com/stretchr/testify v1.4.0
911
github.com/theckman/goconstraint v1.11.0
10-
github.com/zrepl/zrepl v0.2.0
1112
golang.org/x/sys v0.0.0-20191010194322-b09406accb47
1213
)
13-
14-
// copied from zrepl 0.2.0
15-
// invalid dates in transitive dependencies (first validated in Go 1.13, didn't fail in earlier Go versions)
16-
replace (
17-
github.com/go-critic/go-critic v0.0.0-20181204210945-1df300866540 => github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540
18-
github.com/go-critic/go-critic v0.0.0-20181204210945-ee9bf5809ead => github.com/go-critic/go-critic v0.3.5-0.20190210220443-ee9bf5809ead
19-
github.com/golangci/errcheck v0.0.0-20181003203344-ef45e06d44b6 => github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6
20-
github.com/golangci/go-tools v0.0.0-20180109140146-35a9f45a5db0 => github.com/golangci/go-tools v0.0.0-20190124090046-35a9f45a5db0
21-
github.com/golangci/go-tools v0.0.0-20180109140146-af6baa5dc196 => github.com/golangci/go-tools v0.0.0-20190318060251-af6baa5dc196
22-
github.com/golangci/gofmt v0.0.0-20181105071733-0b8337e80d98 => github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98
23-
github.com/golangci/gosec v0.0.0-20180901114220-66fb7fc33547 => github.com/golangci/gosec v0.0.0-20190211064107-66fb7fc33547
24-
github.com/golangci/ineffassign v0.0.0-20180808204949-42439a7714cc => github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc
25-
github.com/golangci/lint-1 v0.0.0-20180610141402-ee948d087217 => github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217
26-
golang.org/x/tools v0.0.0-20190125232054-379209517ffe => golang.org/x/tools v0.0.0-20190205201329-379209517ffe
27-
mvdan.cc/unparam v0.0.0-20190124213536-fbb59629db34 => mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34
28-
)

‎go.sum

+4-233
Large diffs are not rendered by default.

‎internal/circlog/COPYRIGHT

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
The contents of this directory have been copied from the zrepl project:
2+
3+
zrepl/util/circlog was authored by
4+
5+
Ross Williams
6+
7+
as part of zrepl commit 729c83ee726d0fa42ccdc0b5d17a099245b35ef9
8+
9+
============================================================================
10+
REPRODUCTION OF THE ZREPL LICENSE
11+
https://github.com/zrepl/zrepl.git@c24c327151a0894329fafe5b8b881927945d5c00
12+
============================================================================
13+
14+
MIT License
15+
16+
Copyright (c) 2017-2018 Christian Schwarz
17+
18+
Permission is hereby granted, free of charge, to any person obtaining a copy
19+
of this software and associated documentation files (the "Software"), to deal
20+
in the Software without restriction, including without limitation the rights
21+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22+
copies of the Software, and to permit persons to whom the Software is
23+
furnished to do so, subject to the following conditions:
24+
25+
The above copyright notice and this permission notice shall be included in all
26+
copies or substantial portions of the Software.
27+
28+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
34+
SOFTWARE.
35+

‎internal/circlog/circlog.go

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package circlog
2+
3+
import (
4+
"fmt"
5+
"math/bits"
6+
"sync"
7+
)
8+
9+
const CIRCULARLOG_INIT_SIZE int = 32 << 10
10+
11+
type CircularLog struct {
12+
buf []byte
13+
size int
14+
max int
15+
written int
16+
writeCursor int
17+
/*
18+
Mutex prevents:
19+
concurrent writes:
20+
buf, size, written, writeCursor in Write([]byte)
21+
buf, writeCursor in Reset()
22+
data races vs concurrent Write([]byte) calls:
23+
size in Size()
24+
size, writeCursor in Len()
25+
buf, size, written, writeCursor in Bytes()
26+
*/
27+
mtx sync.Mutex
28+
}
29+
30+
func NewCircularLog(max int) (*CircularLog, error) {
31+
if max <= 0 {
32+
return nil, fmt.Errorf("max must be positive")
33+
}
34+
35+
return &CircularLog{
36+
size: CIRCULARLOG_INIT_SIZE,
37+
buf: make([]byte, CIRCULARLOG_INIT_SIZE),
38+
max: max,
39+
}, nil
40+
}
41+
42+
func nextPow2Int(n int) int {
43+
if n < 1 {
44+
panic("can only round up positive integers")
45+
}
46+
r := uint(n)
47+
// Credit: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
48+
r--
49+
// Assume at least a 32 bit integer
50+
r |= r >> 1
51+
r |= r >> 2
52+
r |= r >> 4
53+
r |= r >> 8
54+
r |= r >> 16
55+
if bits.UintSize == 64 {
56+
r |= r >> 32
57+
}
58+
r++
59+
// Can't exceed max positive int value
60+
if r > ^uint(0)>>1 {
61+
panic("rounded to larger than int()")
62+
}
63+
return int(r)
64+
}
65+
66+
func (cl *CircularLog) Write(data []byte) (int, error) {
67+
cl.mtx.Lock()
68+
defer cl.mtx.Unlock()
69+
n := len(data)
70+
71+
// Keep growing the buffer by doubling until
72+
// hitting cl.max
73+
bufAvail := cl.size - cl.writeCursor
74+
// If cl.writeCursor wrapped on the last write,
75+
// then the buffer is full, not empty.
76+
if cl.writeCursor == 0 && cl.written > 0 {
77+
bufAvail = 0
78+
}
79+
if n > bufAvail && cl.size < cl.max {
80+
// Add to size, not writeCursor, so as
81+
// to not resize multiple times if this
82+
// Write() immediately fills up the buffer
83+
newSize := nextPow2Int(cl.size + n)
84+
if newSize > cl.max {
85+
newSize = cl.max
86+
}
87+
newBuf := make([]byte, newSize)
88+
// Reset write cursor to old size if wrapped
89+
if cl.writeCursor == 0 && cl.written > 0 {
90+
cl.writeCursor = cl.size
91+
}
92+
copy(newBuf, cl.buf[:cl.writeCursor])
93+
cl.buf = newBuf
94+
cl.size = newSize
95+
}
96+
97+
// If data to be written is larger than the max size,
98+
// discard all but the last cl.size bytes
99+
if n > cl.size {
100+
data = data[n-cl.size:]
101+
// Overwrite the beginning of data
102+
// with a string indicating truncation
103+
copy(data, []byte("(...)"))
104+
}
105+
// First copy data to the end of buf. If that wasn't
106+
// all of data, then copy the rest to the beginning
107+
// of buf.
108+
copied := copy(cl.buf[cl.writeCursor:], data)
109+
if copied < n {
110+
copied += copy(cl.buf, data[copied:])
111+
}
112+
113+
cl.writeCursor = ((cl.writeCursor + copied) % cl.size)
114+
cl.written += copied
115+
return copied, nil
116+
}
117+
118+
func (cl *CircularLog) Size() int {
119+
cl.mtx.Lock()
120+
defer cl.mtx.Unlock()
121+
return cl.size
122+
}
123+
124+
func (cl *CircularLog) Len() int {
125+
cl.mtx.Lock()
126+
defer cl.mtx.Unlock()
127+
if cl.written >= cl.size {
128+
return cl.size
129+
} else {
130+
return cl.writeCursor
131+
}
132+
}
133+
134+
func (cl *CircularLog) TotalWritten() int {
135+
cl.mtx.Lock()
136+
defer cl.mtx.Unlock()
137+
138+
return cl.written
139+
}
140+
141+
func (cl *CircularLog) Reset() {
142+
cl.mtx.Lock()
143+
defer cl.mtx.Unlock()
144+
cl.writeCursor = 0
145+
cl.buf = make([]byte, CIRCULARLOG_INIT_SIZE)
146+
cl.size = CIRCULARLOG_INIT_SIZE
147+
}
148+
149+
func (cl *CircularLog) Bytes() []byte {
150+
cl.mtx.Lock()
151+
defer cl.mtx.Unlock()
152+
switch {
153+
case cl.written >= cl.size && cl.writeCursor == 0:
154+
return cl.buf
155+
case cl.written > cl.size:
156+
ret := make([]byte, cl.size)
157+
copy(ret, cl.buf[cl.writeCursor:])
158+
copy(ret[cl.size-cl.writeCursor:], cl.buf[:cl.writeCursor])
159+
return ret
160+
default:
161+
return cl.buf[:cl.writeCursor]
162+
}
163+
}
164+
165+
func (cl *CircularLog) String() string {
166+
return string(cl.Bytes())
167+
}

‎internal/circlog/circlog_test.go

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package circlog_test
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/problame/go-netssh/internal/circlog"
11+
)
12+
13+
func TestCircularLog(t *testing.T) {
14+
var maxCircularLogSize int = 1 << 20
15+
16+
writeFixedSize := func(w io.Writer, size int) (int, error) {
17+
pattern := []byte{0xDE, 0xAD, 0xBE, 0xEF}
18+
writeBytes := bytes.Repeat(pattern, size/4)
19+
if len(writeBytes) < size {
20+
writeBytes = append(writeBytes, pattern[:size-len(writeBytes)]...)
21+
}
22+
n, err := w.Write(writeBytes)
23+
if err != nil {
24+
return n, err
25+
}
26+
return n, nil
27+
}
28+
29+
t.Run("negative-size-error", func(t *testing.T) {
30+
_, err := circlog.NewCircularLog(-1)
31+
require.EqualError(t, err, "max must be positive")
32+
})
33+
34+
t.Run("no-resize", func(t *testing.T) {
35+
log, err := circlog.NewCircularLog(maxCircularLogSize)
36+
require.NoError(t, err)
37+
38+
initSize := log.Size()
39+
writeSize := circlog.CIRCULARLOG_INIT_SIZE - 1
40+
_, err = writeFixedSize(log, writeSize)
41+
require.NoError(t, err)
42+
finalSize := log.Size()
43+
require.Equal(t, initSize, finalSize)
44+
require.Equal(t, writeSize, log.Len())
45+
})
46+
t.Run("one-resize", func(t *testing.T) {
47+
log, err := circlog.NewCircularLog(maxCircularLogSize)
48+
require.NoError(t, err)
49+
50+
initSize := log.Size()
51+
writeSize := circlog.CIRCULARLOG_INIT_SIZE + 1
52+
_, err = writeFixedSize(log, writeSize)
53+
require.NoError(t, err)
54+
finalSize := log.Size()
55+
require.Greater(t, finalSize, initSize)
56+
require.LessOrEqual(t, finalSize, maxCircularLogSize)
57+
require.Equal(t, writeSize, log.Len())
58+
})
59+
t.Run("reset", func(t *testing.T) {
60+
log, err := circlog.NewCircularLog(maxCircularLogSize)
61+
require.NoError(t, err)
62+
63+
initSize := log.Size()
64+
_, err = writeFixedSize(log, maxCircularLogSize)
65+
require.NoError(t, err)
66+
log.Reset()
67+
finalSize := log.Size()
68+
require.Equal(t, initSize, finalSize)
69+
})
70+
t.Run("wrap-exactly-maximum", func(t *testing.T) {
71+
log, err := circlog.NewCircularLog(maxCircularLogSize)
72+
require.NoError(t, err)
73+
74+
initSize := log.Size()
75+
writeSize := maxCircularLogSize / 4
76+
for written := 0; written < maxCircularLogSize; {
77+
n, err := writeFixedSize(log, writeSize)
78+
written += n
79+
require.NoError(t, err)
80+
}
81+
finalSize := log.Size()
82+
require.Greater(t, finalSize, initSize)
83+
require.Equal(t, finalSize, maxCircularLogSize)
84+
require.Equal(t, finalSize, log.Len())
85+
})
86+
t.Run("wrap-partial", func(t *testing.T) {
87+
log, err := circlog.NewCircularLog(maxCircularLogSize)
88+
require.NoError(t, err)
89+
90+
initSize := log.Size()
91+
92+
startSentinel := []byte{0x00, 0x00, 0x00, 0x00}
93+
_, err = log.Write(startSentinel)
94+
require.NoError(t, err)
95+
96+
nWritesToFill := 4
97+
writeSize := maxCircularLogSize / nWritesToFill
98+
for i := 0; i < (nWritesToFill + 1); i++ {
99+
_, err := writeFixedSize(log, writeSize)
100+
require.NoError(t, err)
101+
}
102+
103+
endSentinel := []byte{0xFF, 0xFF, 0xFF, 0xFF}
104+
_, err = log.Write(endSentinel)
105+
require.NoError(t, err)
106+
107+
finalSize := log.Size()
108+
require.Greater(t, finalSize, initSize)
109+
require.Greater(t, log.TotalWritten(), finalSize)
110+
require.Equal(t, finalSize, maxCircularLogSize)
111+
require.Equal(t, finalSize, log.Len())
112+
113+
logBytes := log.Bytes()
114+
115+
require.Equal(t, endSentinel, logBytes[len(logBytes)-len(endSentinel):])
116+
require.NotEqual(t, startSentinel, logBytes[:len(startSentinel)])
117+
})
118+
t.Run("overflow-write", func(t *testing.T) {
119+
log, err := circlog.NewCircularLog(maxCircularLogSize)
120+
require.NoError(t, err)
121+
122+
initSize := log.Size()
123+
writeSize := maxCircularLogSize + 1
124+
n, err := writeFixedSize(log, writeSize)
125+
require.NoError(t, err)
126+
finalSize := log.Size()
127+
require.Less(t, n, writeSize)
128+
require.Greater(t, finalSize, initSize)
129+
require.Equal(t, finalSize, maxCircularLogSize)
130+
logBytes := log.Bytes()
131+
require.Equal(t, []byte("(...)"), logBytes[:5])
132+
})
133+
t.Run("stringify", func(t *testing.T) {
134+
log, err := circlog.NewCircularLog(maxCircularLogSize)
135+
require.NoError(t, err)
136+
137+
writtenString := "A harmful truth is better than a useful lie."
138+
n, err := log.Write([]byte(writtenString))
139+
require.NoError(t, err)
140+
require.Equal(t, len(writtenString), n)
141+
loggedString := log.String()
142+
require.Equal(t, writtenString, loggedString)
143+
})
144+
}

‎internal/circlog/nextpow2int_test.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package circlog
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestNextPow2Int(t *testing.T) {
10+
require.Equal(t, 512, nextPow2Int(512), "a power of 2 should round to itself")
11+
require.Equal(t, 1024, nextPow2Int(513), "should round up to the next power of 2")
12+
require.PanicsWithValue(t, "can only round up positive integers", func() { nextPow2Int(0) }, "unimplemented: zero is not positive; corner case")
13+
require.PanicsWithValue(t, "can only round up positive integers", func() { nextPow2Int(-1) }, "unimplemented: cannot round up negative numbers")
14+
maxInt := int((^uint(0)) >> 1)
15+
require.PanicsWithValue(t, "rounded to larger than int()", func() { nextPow2Int(maxInt - 1) }, "cannot round to a number bigger than the int type")
16+
}

0 commit comments

Comments
 (0)
Please sign in to comment.