don't corrupt files when mmap fails

In some cases, after a snapshot, if mmap fails, we could write
a duplicate of the bitmap to the file, creating cryptic "unknown
op type: 60" messages. This doesn't fix those files, but it stops
making them.
This commit is contained in:
Seebs 2020-02-03 15:48:16 -06:00 committed by Matt Jaffee
parent f9e7fee47d
commit 372389fd30
No known key found for this signature in database
GPG key ID: 08A3DFFF987B11BF
2 changed files with 97 additions and 1 deletions

View file

@ -291,7 +291,20 @@ func (f *fragment) importStorage(data []byte, file *os.File, newGen generation,
// to use a new storage as backing store.
func (f *fragment) applyStorage(data []byte, file *os.File, newGen generation, mapped bool) (bool, error) {
if len(data) == 0 {
return f.emptyStorage(file)
if file != nil {
fi, err := file.Stat()
if err == nil && fi != nil && fi.Size() == 0 {
return f.emptyStorage(file)
}
}
// if we can't be sure of that, we assume data is 0 because
// we couldn't mmap it, and since all we'd be doing is remapping
// our containers to use that storage *to take advantage of
// mmap*, we'll just make sure our containers aren't pointing to
// old storage and say "nope".
f.storage.RemapRoaringStorage(nil)
f.storage.SetSource(nil)
return false, nil
}
// Tell storage to prefer mapping if and only if we think the data
// is mmapped and valid.

83
mmap_test.go Normal file
View file

@ -0,0 +1,83 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pilosa
import (
"math/rand"
"sync/atomic"
"testing"
)
type cv struct {
cols []uint64
vals []int64
}
// This test should basically never fail, but it might if you were running
// out of available mmaps. Which you can fake up by adding '&& false' to the test
// in newGeneration in generation.go. So this is probably useless but it's
// a failure mode we've been bitten by once...
func TestMmapBehavior(t *testing.T) {
depth := uint(6)
var done int64
f := mustOpenBSIFragment("i", "f", viewStandard, 0)
defer f.Clean(t)
ch := make(chan struct{})
for i := 0; i < f.MaxOpN; i++ {
_, _ = f.setBit(0, uint64(i*32))
}
// force snapshot so we get a mmapped row...
_ = f.Snapshot()
row := f.row(0)
segment := row.Segments()[0]
bitmap := segment.data
// request information from the frozen bitmap we got back
go func() {
for atomic.LoadInt64(&done) == 0 {
for i := 0; i < f.MaxOpN; i++ {
_ = bitmap.Contains(uint64(i * 32))
}
}
close(ch)
}()
values := make([]cv, 1024)
for i := range values {
cols := make([]uint64, 512)
vals := make([]int64, 512)
for j := range cols {
cols[j] = uint64(rand.Int63n(ShardWidth))
vals[j] = int64(rand.Int63n(1 << depth))
}
values[i] = cv{cols, vals}
}
// modify the original bitmap, until it causes a snapshot, which
// then invalidates the other map...
for j := 0; j < 5; j++ {
for i := 0; i < f.MaxOpN/int(depth+1); i++ {
cv := values[i%len(values)]
err := f.importValue(cv.cols, cv.vals, depth, (i%3 == 1))
if err != nil {
t.Fatalf("importValue[%d][%d]: %v", j, i, err)
}
}
}
atomic.StoreInt64(&done, 1)
<-ch
}