Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

storage/backend: set InitialMmapSize to 2GB on 32-bit to prevent overflow #4715

Closed
wants to merge 1 commit into from

Conversation

luxas
Copy link
Contributor

@luxas luxas commented Mar 8, 2016

@heyitsanthony
Copy link
Contributor

arm mmap accepts a 10GB mapping length?

@@ -30,5 +30,5 @@ import (
// silently ignore this flag. Please update your kernel to prevent this.
var boltOpenOptions = &bolt.Options{
MmapFlags: syscall.MAP_POPULATE,
InitialMmapSize: InitialMmapSize,
InitialMmapSize: int(InitialMmapSize),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this still overflow 32bit? /cc @hongchaodeng

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @luxas https://play.golang.org/p/HDHHzHbxkO

Probably we should convert to max.int32

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, probably. See here: https://github.com/boltdb/bolt/blob/master/bolt_arm.go#L4.
I would prefer moving this to a specific ARM file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 * 1024 * 1024 * 1024 == math.MaxInt32 + 1, so math.MaxInt32 sgtm

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

boltdb maxMapSize equals math.MaxInt32 too

@luxas
Copy link
Contributor Author

luxas commented Mar 8, 2016

@heyitsanthony I don't believe that. I'm perfectly fine to move InitialMmapSize to boltoption_unix and boltoption_arm respectively, and set the ARM value to something like 1 or 2 GB

@luxas
Copy link
Contributor Author

luxas commented Mar 8, 2016

This was just the first way of getting it compiled. Appreciating discussion.

@xiang90
Copy link
Contributor

xiang90 commented Mar 8, 2016

@luxas

This was just the first way of getting it compiled. Appreciating discussion.

What will be the next steps? We do worry about delivering unreliable software that can subtly break at runtime. (And we were considering about prevent 32bit from building actually)

@heyitsanthony
Copy link
Contributor

It'll build, but I am confident it won't work very well:

--- FAIL: TestConcurrentApplyAndSnapshotV3 (0.02s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
    panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x4 pc=0xe8c28]

goroutine 124 [running]:
testing.func·006()
    /home/chz/go1.4/go/src/testing/testing.go:441 +0x188
sync/atomic.storeUint64(0x10d1057c, 0x4000, 0x0)
    /home/chz/go1.4/go/src/sync/atomic/64bit_arm.go:20 +0x48
github.com/coreos/etcd/storage/backend.(*batchTx).commit(0x10e156e0, 0x10e15600)
    /home/chz/golib/src/github.com/coreos/etcd/storage/backend/batch_tx.go:159 +0x394
github.com/coreos/etcd/storage/backend.(*batchTx).Commit(0x10e156e0)
    /home/chz/golib/src/github.com/coreos/etcd/storage/backend/batch_tx.go:114 +0x80
github.com/coreos/etcd/storage/backend.newBatchTx(0x10d10550, 0x10e13540)
    /home/chz/golib/src/github.com/coreos/etcd/storage/backend/batch_tx.go:46 +0x80
github.com/coreos/etcd/storage/backend.newBackend(0x10e0b0b0, 0x28, 0x5f5e100, 0x0, 0x2710, 0x1f)
    /home/chz/golib/src/github.com/coreos/etcd/storage/backend/backend.go:103 +0x288
github.com/coreos/etcd/storage/backend.NewTmpBackend(0x5f5e100, 0x0, 0x2710, 0x773b00, 0x0, 0x0)
    /home/chz/golib/src/github.com/coreos/etcd/storage/backend/backend.go:304 +0x1c0
github.com/coreos/etcd/storage/backend.NewDefaultTmpBackend(0x10e028ec, 0x0, 0x0)
    /home/chz/golib/src/github.com/coreos/etcd/storage/backend/backend.go:308 +0x50
github.com/coreos/etcd/etcdserver.TestConcurrentApplyAndSnapshotV3(0x10df68a0)
    /home/chz/golib/src/github.com/coreos/etcd/etcdserver/server_test.go:936 +0x9e8
testing.tRunner(0x10df68a0, 0xb3f5ac)
    /home/chz/go1.4/go/src/testing/testing.go:447 +0xf4
created by testing.RunTests
    /home/chz/go1.4/go/src/testing/testing.go:555 +0x7e0

@luxas luxas closed this Mar 8, 2016
@luxas luxas force-pushed the fix_arm_backend branch from 92e73be to b922818 Compare March 8, 2016 20:10
@luxas
Copy link
Contributor Author

luxas commented Mar 8, 2016

Sorry, git error while updating fork. Fixing...

@luxas luxas reopened this Mar 8, 2016
@luxas luxas force-pushed the fix_arm_backend branch from 10e7508 to daba39f Compare March 8, 2016 20:15
@luxas
Copy link
Contributor Author

luxas commented Mar 8, 2016

Well, now we have a much better alternative.
If linux,arm, set a lower (2GB) map size, 'cause it's 32-bit. If not, do as usual.
This won't mess with linux,amd64 code at all, so we don't have to be afraid of delivering unreliable software.

Thanks for your patience. Now we have a "real" solution.
The earlier commit was just for starting the discussion with something that compiled.
WDYT? @xiang90 @heyitsanthony @hongchaodeng

@xiang90
Copy link
Contributor

xiang90 commented Mar 8, 2016

@luxas This only fixes build. We worry more about the runtime issues mentioned here #358. We are not able to fix all issues in the repo since we have a lot of dependencies.

@luxas
Copy link
Contributor Author

luxas commented Mar 8, 2016

Yeah I know that, but can you merge this when ready and shift the blocker to e.g. 32-bit intel or something instead.
arm 32-bit is already considered hackish, and you have stated in the readme that you doesn't support 32-bit.

@luxas luxas force-pushed the fix_arm_backend branch 2 times, most recently from ef54510 to 845e3b2 Compare March 9, 2016 18:01
@luxas luxas changed the title Fix arm compilation of backend.go Fix 32-bit compilation of backend.go Mar 9, 2016
@luxas
Copy link
Contributor Author

luxas commented Mar 9, 2016

Okay, so here comes the 32-build fix. Let's merge ASAP.
This makes GOARCH=386 ./build and GOARCH=arm ./build possible.

@heyitsanthony
Copy link
Contributor

@luxas I just tried GOARCH=386 ./build and GOARCH=arm ./build using master; they both work fine. What does this fix?

@luxas
Copy link
Contributor Author

luxas commented Mar 9, 2016

Yeah, of course arm and 386 builds with #4721. They just won't work.
Try to bin/etcd your 32-bit binary and it says unsupported.
This fixes the real 32-bit compilation, 'cause certainly folks like me will continue to build (and improve) etcd for 32-bit.

@luxas
Copy link
Contributor Author

luxas commented Mar 9, 2016

@heyitsanthony The patch sets InitialMmapSize to math.MaxInt32, which is 2GB for 32-bit platforms

@luxas
Copy link
Contributor Author

luxas commented Mar 9, 2016

You may easily run 32-bit integration tests:

$ git diff
diff --git a/build b/build
index e08bcc4..ce56aa8 100755
--- a/build
+++ b/build
@@ -29,5 +29,5 @@ else
 fi

 # Static compilation is useful when etcd is run in a container
-CGO_ENABLED=0 go build $GO_BUILD_FLAGS -installsuffix cgo -ldflags "-s -X ${REPO_PATH}/version.GitSHA${LINK_OPERATOR}${GIT_SHA}" -o bin/etcd ${REPO_PATH}
-CGO_ENABLED=0 go build $GO_BUILD_FLAGS -installsuffix cgo -ldflags "-s" -o bin/etcdctl ${REPO_PATH}/etcdctl
+CGO_ENABLED=0 GOARCH=386 go build $GO_BUILD_FLAGS -installsuffix cgo -ldflags "-s -X ${REPO_PATH}/version.GitSHA${LINK_OPERATOR}${GIT_SHA}" -o bin/etcd ${REPO_PATH}
+CGO_ENABLED=0 GOARCH=386 go build $GO_BUILD_FLAGS -installsuffix cgo -ldflags "-s" -o bin/etcdctl ${REPO_PATH}/etcdctl
diff --git a/etcdmain/etcd.go b/etcdmain/etcd.go
index e6cdf64..3a7456a 100644
--- a/etcdmain/etcd.go
+++ b/etcdmain/etcd.go
@@ -13,7 +13,6 @@
 // limitations under the License.

 // TODO: support arm64
-// +build amd64

 package etcdmain

diff --git a/etcdmain/etcd_phony.go b/etcdmain/etcd_phony.go
index b918b42..67ef009 100644
--- a/etcdmain/etcd_phony.go
+++ b/etcdmain/etcd_phony.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.

-// +build !amd64
+// +build ignore

 package etcdmain

With those small changes, it's just INTEGRATION=y ./test, and it will test 32-bit.
In my tests 32-bit did score exactly as 64-bit. I will run perf tests also and fix some alignments I've found

@heyitsanthony
Copy link
Contributor

In my tests 32-bit did score exactly as 64-bit.

Interesting. I've tested it on two separate machines (arm, x86) using both linux and darwin; the tests crash in both cases without #4733 but work fine on amd64. This sort of thing is exactly why 32-bit is considered unstable and not ready for general release.

@luxas
Copy link
Contributor Author

luxas commented Mar 9, 2016

I've run etcd 2.2 on my Raspberry Pis for 2-3 weeks non-stop without any problems. Also, many folks have used my project https://github.com/luxas/kubernetes-on-arm with etcd without reporting any such issues.

@luxas luxas force-pushed the fix_arm_backend branch from 845e3b2 to c13fd3f Compare March 10, 2016 05:34
@luxas luxas changed the title Fix 32-bit compilation of backend.go storage/backend: set InitialMmapSize to 2GB on 32-bit to prevent overflow Mar 10, 2016
@luxas
Copy link
Contributor Author

luxas commented Mar 10, 2016

@heyitsanthony @xiang90 Updated commit message. ptal

@heyitsanthony
Copy link
Contributor

I'm not sure these specializations are necessary for the time being. I'm running tests on 386 and arm now to see if it matters.

@luxas
Copy link
Contributor Author

luxas commented Mar 10, 2016

I think they are.
You included the first commit of this PR in #4721 (InitialMmapSize first to int64, then back to int), but InitialMmapSize is still 10GB, which I think overflows int32 e.g. here https://github.com/boltdb/bolt/blob/master/bolt_386.go#L4

@luxas
Copy link
Contributor Author

luxas commented Mar 10, 2016

var test = int64(10 * 1024 * 1024 * 1024);
var test2 = int64((2 * 1024 * 1024 * 1024) - 1); // 2 GB, math.MaxInt32
println(int(test))
println(int(test2))

gives on 386

-2147483648 //10GB
2147483647 //2GB

@heyitsanthony
Copy link
Contributor

@luxas 2147483647 is not 2GB; it is not page-aligned. -2147483648 is 2GB. The 386 tests all passed; about to test arm.

I looked at the boltdb code to see what happens with 2GB-- turns out it amounts to a no-op. However, I also found that boltdb is clearly broken for files >= 2GB on 32-bit. If the db file grows >= 2GB, it won't be able to open the file again.

func (db *DB) mmap(minsz int) error {                                                        
        db.mmaplock.Lock()
        defer db.mmaplock.Unlock()                                                           

        info, err := db.file.Stat()                                                          
        if err != nil {
                return fmt.Errorf("mmap stat error: %s", err)                                
        } else if int(info.Size()) < db.pageSize*2 {
                return fmt.Errorf("file size too small")                                     
        }                                                                                    

        // Ensure the size is at least the minimum size.                                     
        var size = int(info.Size())                                                          
        if size < minsz {
                size = minsz                                                                 
        }
        size, err = db.mmapSize(size)                                                        
        if err != nil {
                return err                                                                   
        } 

@heyitsanthony
Copy link
Contributor

Crashes near 1GB of DB data on 386.

23:08:42 etcd1 | unexpected fault address 0xb744a040
23:08:42 etcd1 | fatal error: fault
23:08:42 etcd1 | [signal 
23:08:42 etcd1 | 0xb code=0x1 addr=0xb744a040 pc=0x8430066]
23:08:42 etcd1 | goroutine 64 [running]:
23:08:42 etcd1 | runtime.throw(0x8872fc0, 0x5)
23:08:42 etcd1 |        /usr/lib/go/src/runtime/panic.go:527 +0x7f fp=0x43f4be08 sp=0x43f4bdfc
23:08:42 etcd1 | runtime.sigpanic()
23:08:42 etcd1 |        /usr/lib/go/src/runtime/sigpanic_unix.go:27 +0x276 fp=0x43f4be34 sp=0x43f4be08
23:08:42 etcd1 | github.com/coreos/etcd/Godeps/_workspace/src/github.com/boltdb/bolt.(*Tx).rollback(0x1903f100)
23:08:42 etcd1 |        /home/anthony/src/etcd/gopath/src/github.com/coreos/etcd/Godeps/_workspace/src/github.com/boltdb/bolt/tx.go:245 +0x66 fp=0x43f4be54 sp=0x43f4be34
23:08:42 etcd1 | github.com/coreos/etcd/Godeps/_workspace/src/github.com/boltdb/bolt.(*Tx).Commit(0x1903f100, 0x0, 0x0)
23:08:42 etcd1 |        /home/anthony/src/etcd/gopath/src/github.com/coreos/etcd/Godeps/_workspace/src/github.com/boltdb/bolt/tx.go:163 +
23:08:42 etcd1 | 0x1f2 fp=0x43f4bf10 sp=0x43f4be54
23:08:42 etcd1 | github.com/coreos/etcd/storage/backend.(*batchTx).commit(0x190bea40, 0x8a18e00)
23:08:42 etcd1 |        /home/anthony/src/etcd/gopath/src/github.com/coreos/etcd/storage/backend/batch_tx.go:139 +0x63 fp=0x43f4bf54 sp=0x43f4bf10
23:08:42 etcd1 | github.com/coreos/etcd/storage/backend.(*batchTx).Commit(0x190bea40)
23:08:42 etcd1 |        /home/anthony/src/etcd/gopath/src/github.com/coreos/etcd/storage/backend/batch_tx.go:114 +0x5a fp=0x43f4bf64 sp=0x43f4bf54
23:08:42 etcd1 | github.com/coreos/etcd/storage/backend.(*backend).run(0x19084820)
23:08:42 etcd1 |        /home/anthony/src/etcd/gopath/src/github.com/coreos/etcd/storage/backend/backend.go:179 +0xcf fp=0x43f4bfd0 sp=0x43f4bf64
23:08:42 etcd1 | runtime.goexit()
23:08:42 etcd1 |        /usr/lib/go/src/runtime/asm_386.s:1662 +0x1 fp=0x43f4bfd4 sp=0x43f4bfd0
23:08:42 etcd1 | created by github.com/coreos/etcd/storage/backend.newBackend
23:08:42 etcd1 |        /home/anthony/src/etcd/gopath/src/github.com/coreos/etcd/storage/backend/backend.go:108 +0x267
23:08:42 etcd1 | goroutine 1 [chan receive, 1 minutes]:
23:08:42 etcd1 | github.com/coreos/etcd/etcdmain.Main()
23:08:42 etcd1 |        /home/anthony/src/etcd/gopath/src/github.com/coreos/etcd/etcdmain/etcd.go:194 +0x15ee
23:08:42 etcd1 | main.main()
23:08:42 etcd1 |        /home/anthony/src/etcd/gopath/src/github.com/coreos/etcd/main.go:29 +0x17

@luxas
Copy link
Contributor Author

luxas commented Mar 10, 2016

Crashes near 1GB of DB data on 386.

With or without this patch?

The 386 tests all passed;

Yeah, noticed the same

it is not page-aligned. -2147483648 is 2GB

What do you mean with that?

about to test arm.

I think it's pretty the same, but I'm glad you're testing

@heyitsanthony
Copy link
Contributor

With or without this patch?

It doesn't make a difference. 32-bit is very broken in this case.

What do you mean with that?

mmap rounds sizes up to the nearest page; 2GB is page aligned-- mmap will round (((1 << 31) - 1) up to 1 << 31. Likewise, -2147483648 = 0x80000000 = 2GB

@luxas
Copy link
Contributor Author

luxas commented Mar 10, 2016

@heyitsanthony Strange, boltdb has a check that should check so the mmap doesn't grow too big

// mmapSize determines the appropriate size for the mmap given the current size
// of the database. The minimum size is 32KB and doubles until it reaches 1GB.
// Returns an error if the new mmap size is greater than the max allowed.
func (db *DB) mmapSize(size int) (int, error) {
    // Double the size from 32KB until 1GB.
    for i := uint(15); i <= 30; i++ {
        if size <= 1<<i {
            return 1 << i, nil
        }
    }

    // Verify the requested size is not above the maximum allowed.
    if size > maxMapSize {
        return 0, fmt.Errorf("mmap too large")
    }

@luxas
Copy link
Contributor Author

luxas commented Mar 10, 2016

And maxMapSize is 0x7FFFFFFF == math.MaxInt32
BTW, with which test did you get the failure above?

@heyitsanthony
Copy link
Contributor

@luxas, (1 << 31) is a no-op because boltdb is using an int so it defaults to using the file size; (1 << 31) - 1 seems like it should immediately trigger the mmapSize failure, but I couldn't reproduce that case. I assume the crash I'm seeing is because mmapSize kicks in somewhere once the db grows to 1GB.

I'm building with:
GOARCH=386 ./build

I start a cluster with:
goreman start -f V3DemoProcfile

I generate a workload with:
./tools/benchmark/benchmark put --total=4096 --key-space-size=4096 --val-size=1048576

@luxas
Copy link
Contributor Author

luxas commented Mar 10, 2016

@heyitsanthony I agree that (1 << 31) isn't a good choice, as it will overflow int32 and default to file size. I also think 10 * 1024 * 1024 * 1024 (which we have at master now) will overflow int32 and default to the file size, so I think that it should be changed in some way.

This patch sets initialMmapSize to (1 << 31) - 1 (which doesn't overflow, but instead it'll set the size of the mmap to 2GB directly and maybe that consumes too much memory or something) and that could be a solution, but haven't tested that much yet.

@tgulacsi
Copy link

As size is an int, which is 32bit on 386, the "size >= maxMapSize" will never be true, if maxMapSize = (1<<31)-1 == math.MaxInt32 == 0x7FFFFFFF

Either we should use int64 for the size, or check for size < 0 or ?

@heyitsanthony
Copy link
Contributor

OK. I think this should wait until 32-bit is not crashing on >1GB data sets. Otherwise it is misleading and wastes a gigabyte of address space for nothing.

@luxas
Copy link
Contributor Author

luxas commented Mar 11, 2016

@heyitsanthony Do you think #4742 affected the 32-bit situation?

@heyitsanthony
Copy link
Contributor

@luxas, nope looks like a corruption fix and flock stuff.

boltdb would probably need a redesign to handle large data sets on 32-bit. The etcd fix can be as simple as putting a quota on the db size (which would be useful elsewhere too, cf. #2393, #4744).

@luxas
Copy link
Contributor Author

luxas commented Mar 11, 2016

Yeah, read your issue some minutes ago, and it seems resonable.
Should we set the limit somewhere at 1GB then?

And I assume this 32-bit boltdb issue doesn't affect arm64 and ppc64le at all, so the only thing there before you may support them is some testing and validation.

@luxas luxas closed this Aug 3, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants