Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

atomic.AddInt64 Causes invalid memory address or nil pointer dereference

Calling atomic.AddInt64 on field of a struct panics invalid memory address or nil pointer dereference, but not when we re-arrange fields order; why?

Using this type:

type CountHandler struct {
    c     *RequestContext
    count int64
}

And calling atomic.AddInt64(&countHandler.count, 1) (field c is nil at this point) panics. But not when we rewrite it as:

type CountHandler struct {
    count int64
    c     *RequestContext
}

Error goes away.

I guess it should be so, because Go keeps data in memory in a sequential manner and reaching a nil value breaks this sequence (of bytes); yet I wonder why is that so again because a pointer should have a fixed size nil or other value.

This is Go x86 1.4.2 on Windows & complete error message is:

2015/02/23 12:56:44 http: panic serving [::1]:51886: runtime error: invalid memory address or nil pointer dereference
goroutine 5 [running]:
net/http.func·011()
        c:/go/src/net/http/server.go:1130 +0xa8
sync/atomic.AddUint64(0x731144, 0x1, 0x0, 0x0, 0x263168)
        c:/go/src/sync/atomic/asm_386.s:118 +0xc
main.(*CountHandler).ServeHTTP(0x731140, 0x263180, 0x122f6380, 0x122f62a0)
        C:/Workshop/Devox/Workshop-Go/src/geoho/web/app/app.go:62 +0x42
github.com/julienschmidt/httprouter.func·001(0x263180, 0x122f6380, 0x122f62a0, 0x0, 0x0, 0x0)
        C:/Workshop/Devox/Workshop-Go/src/github.com/julienschmidt/httprouter/router.go:232 +0x4c
github.com/julienschmidt/httprouter.(*Router).ServeHTTP(0x122d5d20, 0x263180, 0x122f6380, 0x122f62a0)
        C:/Workshop/Devox/Workshop-Go/src/github.com/julienschmidt/httprouter/router.go:298 +0x141
net/http.serverHandler.ServeHTTP(0x122d2280, 0x263180, 0x122f6380, 0x122f62a0)
        c:/go/src/net/http/server.go:1703 +0x145
net/http.(*conn).serve(0x122e01e0)
        c:/go/src/net/http/server.go:1204 +0x9d8
created by net/http.(*Server).Serve
        c:/go/src/net/http/server.go:1751 +0x2ce

Whole source code is (this code is wrong, I was just about studying alice):

package main

import (
    "fmt"
    "github.com/julienschmidt/httprouter"
    "github.com/justinas/alice"
    "net/http"

    "os"
    "sync/atomic"
)

// play with alice
func main() {
    c1 := alice.New(Counter, Texter).Then(nil)

    router := httprouter.New()
    router.Handler("GET", "/", c1)
    router.GET("/kill", kill)

    http.ListenAndServe(":27007", router)
}

func kill(rw http.ResponseWriter, rq *http.Request, pl httprouter.Params) {
    os.Exit(0)
}

var ch CountHandler

// constructors:

func Counter(h http.Handler) http.Handler {
    return &ch
}

func Texter(h http.Handler) http.Handler {
    var t TextHandler
    switch x := h.(type) {
    case *CountHandler:
        t.c = x.c
        t.text = fmt.Sprintf("called so far %d", atomic.LoadInt64(&x.count))
    }
    return &t
}

// handlers:

type RequestContext struct {
    val int
}

type CountHandler struct {
    c     *RequestContext
    count int64
}

func (c *CountHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    atomic.AddInt64(&c.count, 1)
}

type TextHandler struct {
    c    *RequestContext
    text string
}

func (t *TextHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    rw.Write([]byte(t.text))
}
like image 320
Kaveh Shahbazian Avatar asked Feb 23 '15 09:02

Kaveh Shahbazian


2 Answers

The fault is caused in the first case by the atomically updated field not being properly aligned.

On both ARM and x86-32, it is the caller's responsibility to arrange for 64-bit alignment of 64-bit words accessed atomically. The first word in a global variable or in an allocated struct or slice can be relied upon to be 64-bit aligned.

like image 79
user4596275 Avatar answered Nov 02 '22 13:11

user4596275


Here are a few tricks to solve the problem in case you stumble upon this error:

The easiest way, as explained in OP, is to place all your 64-bit atomic values at the top of the struct:

c := struct {
    val   int64 // pos 0
    val2  int64 // pos 8
    valid bool  // pos 16
}{val2: 1}
fmt.Println(atomic.AddInt64(&c.val2, 1))

If you, for whatever reason, don't want to place this field at the top, you can always place a _ [4]byte above the 64-bit field to ensure it is correctly padded.

c := struct {
    val   int64   // pos 0
    valid bool    // pos 8
    _     [4]byte // pos 9; compiler adds additional [3]byte at pos 13 for alignment
    val2  int64   // pos 16, correctly aligned
}{val2: 1}
fmt.Println(atomic.AddInt64(&c.val2, 1)) // => 2

Note that this will not work in case the field was already aligned; on the contrary, if it didn't panic before, it will panic now.

c := struct {
    val   int64   // pos 0
    _     [4]byte // pos 8; compiler adds no padding
    val2  int64   // pos 12, not a multiple of 8!
}{val2: 1}
fmt.Println(atomic.AddInt64(&c.val2, 1)) // => runtime error: invalid memory address [...]

You can also rely on the behaviour that the first element in slices of 64-bit elements will be correctly aligned:

c := struct {
    val   int64
    valid bool
    val2  []int64
}{val2: []int64{1}}
fmt.Println(atomic.AddInt64(&c.val2[0], 1))

Note that this won't work for arrays, as their values are stored in the struct directly and not on the heap as it is the case for slice data.

The last trick you can pull off is that of declaring the field in the struct as a pointer to an int64; if the int64 it points to is aligned, then it will run smoothly.

c := struct {
    val   int64
    valid bool
    val2  *int64
}{val2: new(int64)}
fmt.Println(atomic.AddInt64(c.val2, 1))

If you don't want to get your hands dirty with sync/atomic, remember that sync.Mutex is a much cleaner and more understandable solution than dealing with atomics.

like image 33
morganbaz Avatar answered Nov 02 '22 14:11

morganbaz