Merge remote-tracking branch 'origin/develop' into jae/literefactor4

This commit is contained in:
Ethan Buchman
2018-08-02 19:12:22 -04:00
111 changed files with 5359 additions and 3140 deletions

View File

@@ -1,6 +1,7 @@
package autofile
import (
"io/ioutil"
"os"
"sync/atomic"
"syscall"
@@ -13,10 +14,14 @@ import (
func TestSIGHUP(t *testing.T) {
// First, create an AutoFile writing to a tempfile dir
file, name := cmn.Tempfile("sighup_test")
if err := file.Close(); err != nil {
file, err := ioutil.TempFile("", "sighup_test")
if err != nil {
t.Fatalf("Error creating tempfile: %v", err)
}
if err := file.Close(); err != nil {
t.Fatalf("Error closing tempfile: %v", err)
}
name := file.Name()
// Here is the actual AutoFile
af, err := OpenAutoFile(name)
if err != nil {

View File

@@ -147,14 +147,13 @@ func TestSearch(t *testing.T) {
// Now search for each number
for i := 0; i < 100; i++ {
t.Log("Testing for i", i)
gr, match, err := g.Search("INFO", makeSearchFunc(i))
require.NoError(t, err, "Failed to search for line")
assert.True(t, match, "Expected Search to return exact match")
require.NoError(t, err, "Failed to search for line, tc #%d", i)
assert.True(t, match, "Expected Search to return exact match, tc #%d", i)
line, err := gr.ReadLine()
require.NoError(t, err, "Failed to read line after search")
require.NoError(t, err, "Failed to read line after search, tc #%d", i)
if !strings.HasPrefix(line, fmt.Sprintf("INFO %v ", i)) {
t.Fatal("Failed to get correct line")
t.Fatalf("Failed to get correct line, tc #%d", i)
}
// Make sure we can continue to read from there.
cur := i + 1
@@ -165,16 +164,16 @@ func TestSearch(t *testing.T) {
// OK!
break
} else {
t.Fatal("Got EOF after the wrong INFO #")
t.Fatalf("Got EOF after the wrong INFO #, tc #%d", i)
}
} else if err != nil {
t.Fatal("Error reading line", err)
t.Fatalf("Error reading line, tc #%d, err:\n%s", i, err)
}
if !strings.HasPrefix(line, "INFO ") {
continue
}
if !strings.HasPrefix(line, fmt.Sprintf("INFO %v ", cur)) {
t.Fatalf("Unexpected INFO #. Expected %v got:\n%v", cur, line)
t.Fatalf("Unexpected INFO #. Expected %v got:\n%v, tc #%d", cur, line, i)
}
cur++
}

View File

@@ -8,13 +8,15 @@ import (
"sync"
)
// BitArray is a thread-safe implementation of a bit array.
type BitArray struct {
mtx sync.Mutex
Bits int `json:"bits"` // NOTE: persisted via reflect, must be exported
Elems []uint64 `json:"elems"` // NOTE: persisted via reflect, must be exported
}
// There is no BitArray whose Size is 0. Use nil instead.
// NewBitArray returns a new bit array.
// It returns nil if the number of bits is zero.
func NewBitArray(bits int) *BitArray {
if bits <= 0 {
return nil
@@ -25,6 +27,7 @@ func NewBitArray(bits int) *BitArray {
}
}
// Size returns the number of bits in the bitarray
func (bA *BitArray) Size() int {
if bA == nil {
return 0
@@ -32,7 +35,8 @@ func (bA *BitArray) Size() int {
return bA.Bits
}
// NOTE: behavior is undefined if i >= bA.Bits
// GetIndex returns the bit at index i within the bit array.
// The behavior is undefined if i >= bA.Bits
func (bA *BitArray) GetIndex(i int) bool {
if bA == nil {
return false
@@ -49,7 +53,8 @@ func (bA *BitArray) getIndex(i int) bool {
return bA.Elems[i/64]&(uint64(1)<<uint(i%64)) > 0
}
// NOTE: behavior is undefined if i >= bA.Bits
// SetIndex sets the bit at index i within the bit array.
// The behavior is undefined if i >= bA.Bits
func (bA *BitArray) SetIndex(i int, v bool) bool {
if bA == nil {
return false
@@ -71,6 +76,7 @@ func (bA *BitArray) setIndex(i int, v bool) bool {
return true
}
// Copy returns a copy of the provided bit array.
func (bA *BitArray) Copy() *BitArray {
if bA == nil {
return nil
@@ -98,7 +104,9 @@ func (bA *BitArray) copyBits(bits int) *BitArray {
}
}
// Returns a BitArray of larger bits size.
// Or returns a bit array resulting from a bitwise OR of the two bit arrays.
// If the two bit-arrys have different lengths, Or right-pads the smaller of the two bit-arrays with zeroes.
// Thus the size of the return value is the maximum of the two provided bit arrays.
func (bA *BitArray) Or(o *BitArray) *BitArray {
if bA == nil && o == nil {
return nil
@@ -110,7 +118,11 @@ func (bA *BitArray) Or(o *BitArray) *BitArray {
return bA.Copy()
}
bA.mtx.Lock()
defer bA.mtx.Unlock()
o.mtx.Lock()
defer func() {
bA.mtx.Unlock()
o.mtx.Unlock()
}()
c := bA.copyBits(MaxInt(bA.Bits, o.Bits))
for i := 0; i < len(c.Elems); i++ {
c.Elems[i] |= o.Elems[i]
@@ -118,13 +130,19 @@ func (bA *BitArray) Or(o *BitArray) *BitArray {
return c
}
// Returns a BitArray of smaller bit size.
// And returns a bit array resulting from a bitwise AND of the two bit arrays.
// If the two bit-arrys have different lengths, this truncates the larger of the two bit-arrays from the right.
// Thus the size of the return value is the minimum of the two provided bit arrays.
func (bA *BitArray) And(o *BitArray) *BitArray {
if bA == nil || o == nil {
return nil
}
bA.mtx.Lock()
defer bA.mtx.Unlock()
o.mtx.Lock()
defer func() {
bA.mtx.Unlock()
o.mtx.Unlock()
}()
return bA.and(o)
}
@@ -136,12 +154,17 @@ func (bA *BitArray) and(o *BitArray) *BitArray {
return c
}
// Not returns a bit array resulting from a bitwise Not of the provided bit array.
func (bA *BitArray) Not() *BitArray {
if bA == nil {
return nil // Degenerate
}
bA.mtx.Lock()
defer bA.mtx.Unlock()
return bA.not()
}
func (bA *BitArray) not() *BitArray {
c := bA.copy()
for i := 0; i < len(c.Elems); i++ {
c.Elems[i] = ^c.Elems[i]
@@ -149,13 +172,20 @@ func (bA *BitArray) Not() *BitArray {
return c
}
// Sub subtracts the two bit-arrays bitwise, without carrying the bits.
// This is essentially bA.And(o.Not()).
// If bA is longer than o, o is right padded with zeroes.
func (bA *BitArray) Sub(o *BitArray) *BitArray {
if bA == nil || o == nil {
// TODO: Decide if we should do 1's complement here?
return nil
}
bA.mtx.Lock()
defer bA.mtx.Unlock()
o.mtx.Lock()
defer func() {
bA.mtx.Unlock()
o.mtx.Unlock()
}()
if bA.Bits > o.Bits {
c := bA.copy()
for i := 0; i < len(o.Elems)-1; i++ {
@@ -164,15 +194,15 @@ func (bA *BitArray) Sub(o *BitArray) *BitArray {
i := len(o.Elems) - 1
if i >= 0 {
for idx := i * 64; idx < o.Bits; idx++ {
// NOTE: each individual GetIndex() call to o is safe.
c.setIndex(idx, c.getIndex(idx) && !o.GetIndex(idx))
c.setIndex(idx, c.getIndex(idx) && !o.getIndex(idx))
}
}
return c
}
return bA.and(o.Not()) // Note degenerate case where o == nil
return bA.and(o.not()) // Note degenerate case where o == nil
}
// IsEmpty returns true iff all bits in the bit array are 0
func (bA *BitArray) IsEmpty() bool {
if bA == nil {
return true // should this be opposite?
@@ -187,6 +217,7 @@ func (bA *BitArray) IsEmpty() bool {
return true
}
// IsFull returns true iff all bits in the bit array are 1.
func (bA *BitArray) IsFull() bool {
if bA == nil {
return true
@@ -207,6 +238,8 @@ func (bA *BitArray) IsFull() bool {
return (lastElem+1)&((uint64(1)<<uint(lastElemBits))-1) == 0
}
// PickRandom returns a random index in the bit array, and its value.
// It uses the global randomness in `random.go` to get this index.
func (bA *BitArray) PickRandom() (int, bool) {
if bA == nil {
return 0, false
@@ -260,6 +293,8 @@ func (bA *BitArray) String() string {
return bA.StringIndented("")
}
// StringIndented returns the same thing as String(), but applies the indent
// at every 10th bit, and twice at every 50th bit.
func (bA *BitArray) StringIndented(indent string) string {
if bA == nil {
return "nil-BitArray"
@@ -295,6 +330,7 @@ func (bA *BitArray) stringIndented(indent string) string {
return fmt.Sprintf("BA{%v:%v}", bA.Bits, strings.Join(lines, indent))
}
// Bytes returns the byte representation of the bits within the bitarray.
func (bA *BitArray) Bytes() []byte {
bA.mtx.Lock()
defer bA.mtx.Unlock()
@@ -309,15 +345,18 @@ func (bA *BitArray) Bytes() []byte {
return bytes
}
// NOTE: other bitarray o is not locked when reading,
// so if necessary, caller must copy or lock o prior to calling Update.
// If bA is nil, does nothing.
// Update sets the bA's bits to be that of the other bit array.
// The copying begins from the begin of both bit arrays.
func (bA *BitArray) Update(o *BitArray) {
if bA == nil || o == nil {
return
}
bA.mtx.Lock()
defer bA.mtx.Unlock()
o.mtx.Lock()
defer func() {
bA.mtx.Unlock()
o.mtx.Unlock()
}()
copy(bA.Elems, o.Elems)
}

View File

@@ -8,7 +8,6 @@ import (
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"syscall"
)
@@ -124,60 +123,6 @@ func MustWriteFile(filePath string, contents []byte, mode os.FileMode) {
}
}
// WriteFileAtomic creates a temporary file with data and the perm given and
// swaps it atomically with filename if successful.
func WriteFileAtomic(filename string, data []byte, perm os.FileMode) error {
var (
dir = filepath.Dir(filename)
tempFile = filepath.Join(dir, "write-file-atomic-"+RandStr(32))
// Override in case it does exist, create in case it doesn't and force kernel
// flush, which still leaves the potential of lingering disk cache.
flag = os.O_WRONLY | os.O_CREATE | os.O_SYNC | os.O_TRUNC
)
f, err := os.OpenFile(tempFile, flag, perm)
if err != nil {
return err
}
// Clean up in any case. Defer stacking order is last-in-first-out.
defer os.Remove(f.Name())
defer f.Close()
if n, err := f.Write(data); err != nil {
return err
} else if n < len(data) {
return io.ErrShortWrite
}
// Close the file before renaming it, otherwise it will cause "The process
// cannot access the file because it is being used by another process." on windows.
f.Close()
return os.Rename(f.Name(), filename)
}
//--------------------------------------------------------------------------------
func Tempfile(prefix string) (*os.File, string) {
file, err := ioutil.TempFile("", prefix)
if err != nil {
PanicCrisis(err)
}
return file, file.Name()
}
func Tempdir(prefix string) (*os.File, string) {
tempDir := os.TempDir() + "/" + prefix + RandStr(12)
err := EnsureDir(tempDir, 0700)
if err != nil {
panic(Fmt("Error creating temp dir: %v", err))
}
dir, err := os.Open(tempDir)
if err != nil {
panic(Fmt("Error opening temp dir: %v", err))
}
return dir, tempDir
}
//--------------------------------------------------------------------------------
func Prompt(prompt string, defaultValue string) (string, error) {

View File

@@ -1,52 +1,10 @@
package common
import (
"bytes"
"io/ioutil"
"os"
"testing"
)
func TestWriteFileAtomic(t *testing.T) {
var (
data = []byte(RandStr(RandIntn(2048)))
old = RandBytes(RandIntn(2048))
perm os.FileMode = 0600
)
f, err := ioutil.TempFile("/tmp", "write-atomic-test-")
if err != nil {
t.Fatal(err)
}
defer os.Remove(f.Name())
if err = ioutil.WriteFile(f.Name(), old, 0664); err != nil {
t.Fatal(err)
}
if err = WriteFileAtomic(f.Name(), data, perm); err != nil {
t.Fatal(err)
}
rData, err := ioutil.ReadFile(f.Name())
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(data, rData) {
t.Fatalf("data mismatch: %v != %v", data, rData)
}
stat, err := os.Stat(f.Name())
if err != nil {
t.Fatal(err)
}
if have, want := stat.Mode().Perm(), perm; have != want {
t.Errorf("have %v, want %v", have, want)
}
}
func TestGoPath(t *testing.T) {
// restore original gopath upon exit
path := os.Getenv("GOPATH")

View File

@@ -109,6 +109,10 @@ func RandInt63n(n int64) int64 {
return grand.Int63n(n)
}
func RandBool() bool {
return grand.Bool()
}
func RandFloat32() float32 {
return grand.Float32()
}
@@ -274,6 +278,13 @@ func (r *Rand) Intn(n int) int {
return i
}
// Bool returns a uniformly random boolean
func (r *Rand) Bool() bool {
// See https://github.com/golang/go/issues/23804#issuecomment-365370418
// for reasoning behind computing like this
return r.Int63()%2 == 0
}
// Perm returns a pseudo-random permutation of n integers in [0, n).
func (r *Rand) Perm(n int) []int {
r.Lock()

128
libs/common/tempfile.go Normal file
View File

@@ -0,0 +1,128 @@
package common
import (
fmt "fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
)
const (
atomicWriteFilePrefix = "write-file-atomic-"
// Maximum number of atomic write file conflicts before we start reseeding
// (reduced from golang's default 10 due to using an increased randomness space)
atomicWriteFileMaxNumConflicts = 5
// Maximum number of attempts to make at writing the write file before giving up
// (reduced from golang's default 10000 due to using an increased randomness space)
atomicWriteFileMaxNumWriteAttempts = 1000
// LCG constants from Donald Knuth MMIX
// This LCG's has a period equal to 2**64
lcgA = 6364136223846793005
lcgC = 1442695040888963407
// Create in case it doesn't exist and force kernel
// flush, which still leaves the potential of lingering disk cache.
// Never overwrites files
atomicWriteFileFlag = os.O_WRONLY | os.O_CREATE | os.O_SYNC | os.O_TRUNC | os.O_EXCL
)
var (
atomicWriteFileRand uint64
atomicWriteFileRandMu sync.Mutex
)
func writeFileRandReseed() uint64 {
// Scale the PID, to minimize the chance that two processes seeded at similar times
// don't get the same seed. Note that PID typically ranges in [0, 2**15), but can be
// up to 2**22 under certain configurations. We left bit-shift the PID by 20, so that
// a PID difference of one corresponds to a time difference of 2048 seconds.
// The important thing here is that now for a seed conflict, they would both have to be on
// the correct nanosecond offset, and second-based offset, which is much less likely than
// just a conflict with the correct nanosecond offset.
return uint64(time.Now().UnixNano() + int64(os.Getpid()<<20))
}
// Use a fast thread safe LCG for atomic write file names.
// Returns a string corresponding to a 64 bit int.
// If it was a negative int, the leading number is a 0.
func randWriteFileSuffix() string {
atomicWriteFileRandMu.Lock()
r := atomicWriteFileRand
if r == 0 {
r = writeFileRandReseed()
}
// Update randomness according to lcg
r = r*lcgA + lcgC
atomicWriteFileRand = r
atomicWriteFileRandMu.Unlock()
// Can have a negative name, replace this in the following
suffix := strconv.Itoa(int(r))
if string(suffix[0]) == "-" {
// Replace first "-" with "0". This is purely for UI clarity,
// as otherwhise there would be two `-` in a row.
suffix = strings.Replace(suffix, "-", "0", 1)
}
return suffix
}
// WriteFileAtomic creates a temporary file with data and provided perm and
// swaps it atomically with filename if successful.
func WriteFileAtomic(filename string, data []byte, perm os.FileMode) (err error) {
// This implementation is inspired by the golang stdlibs method of creating
// tempfiles. Notable differences are that we use different flags, a 64 bit LCG
// and handle negatives differently.
// The core reason we can't use golang's TempFile is that we must write
// to the file synchronously, as we need this to persist to disk.
// We also open it in write-only mode, to avoid concerns that arise with read.
var (
dir = filepath.Dir(filename)
f *os.File
)
nconflict := 0
// Limit the number of attempts to create a file. Something is seriously
// wrong if it didn't get created after 1000 attempts, and we don't want
// an infinite loop
i := 0
for ; i < atomicWriteFileMaxNumWriteAttempts; i++ {
name := filepath.Join(dir, atomicWriteFilePrefix+randWriteFileSuffix())
f, err = os.OpenFile(name, atomicWriteFileFlag, perm)
// If the file already exists, try a new file
if os.IsExist(err) {
// If the files exists too many times, start reseeding as we've
// likely hit another instances seed.
if nconflict++; nconflict > atomicWriteFileMaxNumConflicts {
atomicWriteFileRandMu.Lock()
atomicWriteFileRand = writeFileRandReseed()
atomicWriteFileRandMu.Unlock()
}
continue
} else if err != nil {
return err
}
break
}
if i == atomicWriteFileMaxNumWriteAttempts {
return fmt.Errorf("Could not create atomic write file after %d attempts", i)
}
// Clean up in any case. Defer stacking order is last-in-first-out.
defer os.Remove(f.Name())
defer f.Close()
if n, err := f.Write(data); err != nil {
return err
} else if n < len(data) {
return io.ErrShortWrite
}
// Close the file before renaming it, otherwise it will cause "The process
// cannot access the file because it is being used by another process." on windows.
f.Close()
return os.Rename(f.Name(), filename)
}

View File

@@ -0,0 +1,138 @@
package common
// Need access to internal variables, so can't use _test package
import (
"bytes"
fmt "fmt"
"io/ioutil"
"os"
testing "testing"
"github.com/stretchr/testify/require"
)
func TestWriteFileAtomic(t *testing.T) {
var (
data = []byte(RandStr(RandIntn(2048)))
old = RandBytes(RandIntn(2048))
perm os.FileMode = 0600
)
f, err := ioutil.TempFile("/tmp", "write-atomic-test-")
if err != nil {
t.Fatal(err)
}
defer os.Remove(f.Name())
if err = ioutil.WriteFile(f.Name(), old, 0664); err != nil {
t.Fatal(err)
}
if err = WriteFileAtomic(f.Name(), data, perm); err != nil {
t.Fatal(err)
}
rData, err := ioutil.ReadFile(f.Name())
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(data, rData) {
t.Fatalf("data mismatch: %v != %v", data, rData)
}
stat, err := os.Stat(f.Name())
if err != nil {
t.Fatal(err)
}
if have, want := stat.Mode().Perm(), perm; have != want {
t.Errorf("have %v, want %v", have, want)
}
}
// This tests atomic write file when there is a single duplicate file.
// Expected behavior is for a new file to be created, and the original write file to be unaltered.
func TestWriteFileAtomicDuplicateFile(t *testing.T) {
var (
defaultSeed uint64 = 1
testString = "This is a glorious test string"
expectedString = "Did the test file's string appear here?"
fileToWrite = "/tmp/TestWriteFileAtomicDuplicateFile-test.txt"
)
// Create a file at the seed, and reset the seed.
atomicWriteFileRand = defaultSeed
firstFileRand := randWriteFileSuffix()
atomicWriteFileRand = defaultSeed
fname := "/tmp/" + atomicWriteFilePrefix + firstFileRand
f, err := os.OpenFile(fname, atomicWriteFileFlag, 0777)
defer os.Remove(fname)
// Defer here, in case there is a panic in WriteFileAtomic.
defer os.Remove(fileToWrite)
require.Nil(t, err)
f.WriteString(testString)
WriteFileAtomic(fileToWrite, []byte(expectedString), 0777)
// Check that the first atomic file was untouched
firstAtomicFileBytes, err := ioutil.ReadFile(fname)
require.Nil(t, err, "Error reading first atomic file")
require.Equal(t, []byte(testString), firstAtomicFileBytes, "First atomic file was overwritten")
// Check that the resultant file is correct
resultantFileBytes, err := ioutil.ReadFile(fileToWrite)
require.Nil(t, err, "Error reading resultant file")
require.Equal(t, []byte(expectedString), resultantFileBytes, "Written file had incorrect bytes")
// Check that the intermediate write file was deleted
// Get the second write files' randomness
atomicWriteFileRand = defaultSeed
_ = randWriteFileSuffix()
secondFileRand := randWriteFileSuffix()
_, err = os.Stat("/tmp/" + atomicWriteFilePrefix + secondFileRand)
require.True(t, os.IsNotExist(err), "Intermittent atomic write file not deleted")
}
// This tests atomic write file when there are many duplicate files.
// Expected behavior is for a new file to be created under a completely new seed,
// and the original write files to be unaltered.
func TestWriteFileAtomicManyDuplicates(t *testing.T) {
var (
defaultSeed uint64 = 2
testString = "This is a glorious test string, from file %d"
expectedString = "Did any of the test file's string appear here?"
fileToWrite = "/tmp/TestWriteFileAtomicDuplicateFile-test.txt"
)
// Initialize all of the atomic write files
atomicWriteFileRand = defaultSeed
for i := 0; i < atomicWriteFileMaxNumConflicts+2; i++ {
fileRand := randWriteFileSuffix()
fname := "/tmp/" + atomicWriteFilePrefix + fileRand
f, err := os.OpenFile(fname, atomicWriteFileFlag, 0777)
require.Nil(t, err)
f.WriteString(fmt.Sprintf(testString, i))
defer os.Remove(fname)
}
atomicWriteFileRand = defaultSeed
// Defer here, in case there is a panic in WriteFileAtomic.
defer os.Remove(fileToWrite)
WriteFileAtomic(fileToWrite, []byte(expectedString), 0777)
// Check that all intermittent atomic file were untouched
atomicWriteFileRand = defaultSeed
for i := 0; i < atomicWriteFileMaxNumConflicts+2; i++ {
fileRand := randWriteFileSuffix()
fname := "/tmp/" + atomicWriteFilePrefix + fileRand
firstAtomicFileBytes, err := ioutil.ReadFile(fname)
require.Nil(t, err, "Error reading first atomic file")
require.Equal(t, []byte(fmt.Sprintf(testString, i)), firstAtomicFileBytes,
"atomic write file %d was overwritten", i)
}
// Check that the resultant file is correct
resultantFileBytes, err := ioutil.ReadFile(fileToWrite)
require.Nil(t, err, "Error reading resultant file")
require.Equal(t, []byte(expectedString), resultantFileBytes, "Written file had incorrect bytes")
}

View File

@@ -2,6 +2,7 @@ package db
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
@@ -17,8 +18,8 @@ func cleanupDBDir(dir, name string) {
func testBackendGetSetDelete(t *testing.T, backend DBBackendType) {
// Default
dir, dirname := cmn.Tempdir(fmt.Sprintf("test_backend_%s_", backend))
defer dir.Close()
dirname, err := ioutil.TempDir("", fmt.Sprintf("test_backend_%s_", backend))
require.Nil(t, err)
db := NewDB("testdb", backend, dirname)
// A nonexistent key should return nil, even if the key is empty

View File

@@ -2,12 +2,12 @@ package db
import (
"fmt"
"io/ioutil"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
cmn "github.com/tendermint/tendermint/libs/common"
)
//----------------------------------------
@@ -61,9 +61,9 @@ func checkValuePanics(t *testing.T, itr Iterator) {
}
func newTempDB(t *testing.T, backend DBBackendType) (db DB) {
dir, dirname := cmn.Tempdir("db_common_test")
dirname, err := ioutil.TempDir("", "db_common_test")
require.Nil(t, err)
db = NewDB("testdb", backend, dirname)
dir.Close()
return db
}