append slice concurrently without lock,after that panic occurred when iterate the slice

1,225 views
Skip to first unread message

Yuu LongXue

unread,
Mar 9, 2020, 11:17:10 AM3/9/20
to golang-nuts
Hi all,

Slice is not thread safe for write operation if without lock, this is true and I know this feature. but recently, I got a panic which caused by writing a slice concurrently with out lock occasionally and I can't figure out why.

### the process as below:
1. define a slice: var x []string
2. append to x concurrently without lock in multi goroutines
3. wait all goroutines to be done
4. iterate the x slice, panic occurred when read some element with index <strong>i (i > 0 && i < len(x)-1)</strong> in the x

### the question is:
In this case, the element with index 0 and index len(x) - 1 is ok to read, but it exists a <strong>x[i](i > 0 && i < len(x)-1)</strong> that can't be read and it will lead to panic[invalid memory address or nil pointer dereference];

I know that some data by the write operation missed, but how could the panic occur? I have tried my best to know why, but I still can't figure out;
<strong>so could anyone tell me why the element in x has a invalid address?
maybe it was collected by gc or not initialized yet and why?<strong>

### code
```
func TestConcurrentWriteSlice(t *testing.T) {
// panic do not occurred for each running, so I repeat until it occurred
for i := 0; i < 100; i++ {
testConcurrentWriteSlice()
}
}

func testConcurrentWriteSlice() {
var strs []string

// write concurrently here
var wg sync.WaitGroup
for x := 0; x < 1000; x++ {
wg.Add(1)
go func() {
defer func() { wg.Done() }()
time.Sleep(2 * time.Second)
for i := 65; i < 91; i++ {
strs = append(strs, string(i))
}
}()
}
wg.Wait()

l := len(strs)
log.Printf("strs len: %v f=%v l=%v", l, strs[0], strs[l-1])
for i := 0; i < l; i++ {
log.Printf("strs len=%v i=%v first=%v last=%v &current[i]=%v", l, i, strs[0], strs[l-1], &strs[i])

// panic here
log.Printf("strs len=%v i=%v first=%v last=%v current=%v", l, i, strs[0], strs[l-1], strs[i])
}
}
```

### output log
```
2020/03/09 14:39:08 strs len=2607 i=519 first=A last=Z &current[i]=0xc000606070
2020/03/09 14:39:08 strs len=2607 i=519 first=A last=Z current=
2020/03/09 14:39:08 strs len=2607 i=520 first=A last=Z &current[i]=0xc000606080
2020/03/09 14:39:08 strs len=2607 i=520 first=A last=Z current=
2020/03/09 14:39:08 strs len=2607 i=521 first=A last=Z &current[i]=0xc000606090
2020/03/09 14:39:08 strs len=2607 i=521 first=A last=Z current=
2020/03/09 14:39:08 strs len=2607 i=522 first=A last=Z &current[i]=0xc0006060a0
2020/03/09 14:39:08 strs len=2607 i=522 first=A last=Z current=
2020/03/09 14:39:08 strs len=2607 i=523 first=A last=Z &current[i]=0xc0006060b0
--- FAIL: TestConcurrentWriteSlice (19.72s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x105aa93]

goroutine 33 [running]:
testing.tRunner.func1(0xc0000d8100)
/usr/local/go/src/testing/testing.go:874 +0x3a3
panic(0x1127f20, 0x12494b0)
/usr/local/go/src/runtime/panic.go:679 +0x1b2
fmt.(*buffer).writeString(...)
/usr/local/go/src/fmt/print.go:82
fmt.(*fmt).padString(0xc000082040, 0x0, 0x115ced0)
/usr/local/go/src/fmt/format.go:110 +0x8c
fmt.(*fmt).fmtS(0xc000082040, 0x0, 0x115ced0)
/usr/local/go/src/fmt/format.go:359 +0x61
fmt.(*pp).fmtString(0xc000082000, 0x0, 0x115ced0, 0x76)
/usr/local/go/src/fmt/print.go:447 +0x131
fmt.(*pp).printArg(0xc000082000, 0x111c3a0, 0xc00011ed80, 0x76)
/usr/local/go/src/fmt/print.go:698 +0x877
fmt.(*pp).doPrintf(0xc000082000, 0x115b38e, 0x2c, 0xc0000c9ef8, 0x5, 0x5)
/usr/local/go/src/fmt/print.go:1030 +0x15b
fmt.Sprintf(0x115b38e, 0x2c, 0xc000840ef8, 0x5, 0x5, 0x1, 0xc00011ed80)
/usr/local/go/src/fmt/print.go:219 +0x66
log.Printf(0x115b38e, 0x2c, 0xc000840ef8, 0x5, 0x5)
/usr/local/go/src/log/log.go:307 +0x53
github.com/test/project/util.testConcurrentWriteSlice()
/Users/zhangqinxue/work/github/demo/go/project/util/slice_test.go:36 +0x323
github.com/test/project/util.TestConcurrentWriteSlice(0xc0000d8100)
/Users/zhangqinxue/work/github/demo/go/project/util/slice_test.go:12 +0x2a
testing.tRunner(0xc0000d8100, 0x115caf0)
/usr/local/go/src/testing/testing.go:909 +0xc9
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:960 +0x350
```

Thanks

YUU

burak serdar

unread,
Mar 9, 2020, 11:28:04 AM3/9/20
to Yuu LongXue, golang-nuts
On Mon, Mar 9, 2020 at 9:16 AM Yuu LongXue <longx...@gmail.com> wrote:
>
> Hi all,
>
> Slice is not thread safe for write operation if without lock, this is true and I know this feature. but recently, I got a panic which caused by writing a slice concurrently with out lock occasionally and I can't figure out why.
>
> ### the process as below:
> 1. define a slice: var x []string
> 2. append to x concurrently without lock in multi goroutines
> 3. wait all goroutines to be done
> 4. iterate the x slice, panic occurred when read some element with index <strong>i (i > 0 && i < len(x)-1)</strong> in the x
>
> ### the question is:
> In this case, the element with index 0 and index len(x) - 1 is ok to read, but it exists a <strong>x[i](i > 0 && i < len(x)-1)</strong> that can't be read and it will lead to panic[invalid memory address or nil pointer dereference];

You have a race, so the behavior is undefined. One can only guess what
went wrong. It is possible that you have a corrupt string in the
slice, one with len>0 but with a nil backing array, because you have
many goroutines writing to the same element of the slice, and you
don't know which one wrote what in what order.
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/122CE4C2-A82A-4819-BFBD-E6E98A71BF7F%40gmail.com.

Jake Montgomery

unread,
Mar 9, 2020, 12:01:56 PM3/9/20
to golang-nuts
It seems like you start your post by acknowledging that you have a race. But you are wondering why that race is causing a panic? Is that a correct assessment of you post?

A race condition can cause all sorts of bad behavior. Some may behave "normally" 99.99999% of the time, then cause an incorrect result. Or they may panic all the time, or some of the time. Or worse, they can cause a panic, or some other bad behavior, in a seemingly unrelated part of the code, long after the race condition occurred. While it may be interesting to try to understand how a particular race caused a particular behavior, it is not generally useful to do so. There is no such thing as a "safe" race in Go. You should always take great care to strictly follow the "rules" to avoid races. You should always fix a race condition as soon as you find it.

Jan Mercl

unread,
Mar 9, 2020, 12:33:57 PM3/9/20
to Yuu LongXue, golang-nuts
On Mon, Mar 9, 2020 at 4:16 PM Yuu LongXue <longx...@gmail.com> wrote:
>
> Hi all,
>
> Slice is not thread safe for write operation if without lock, this is true and I know this feature. but recently, I got a panic which caused by writing a slice concurrently with out lock occasionally and I can't figure out why.
>
> ### the process as below:
> 1. define a slice: var x []string
> 2. append to x concurrently without lock in multi goroutines
> 3. wait all goroutines to be done
> 4. iterate the x slice, panic occurred when read some element with index <strong>i (i > 0 && i < len(x)-1)</strong> in the x
>
> ### the question is:
> In this case, the element with index 0 and index len(x) - 1 is ok to read, but it exists a <strong>x[i](i > 0 && i < len(x)-1)</strong> that can't be read and it will lead to panic[invalid memory address or nil pointer dereference];

Slice is a multi word value. So writing it concurrently is not atomic
and it may eventually produce a value that's a mix of the concurrently
written values. After append it may happen that the slice's pointer to
the backing array points incorrectly to the old value but the len and
capacity are for the new value. Then it may happen that one can index
only to a certain extent, lower than would be using a non corrupted
slice value. Exact failure modes can be only guessed without more
info.

Yuu LongXue

unread,
Mar 10, 2020, 9:36:42 AM3/10/20
to Jake Montgomery, golang-nuts
Yes, you are right, I am just wondering why that race is causing a panic. 
Thanks for your response and recommendation.

YUU

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Yuu LongXue

unread,
Mar 10, 2020, 9:46:05 AM3/10/20
to Leszek Kubik, burak serdar, Jan Mercl, golang-nuts
Yeah, source code and dumping memory seems to be the only and right way to know exactly why. 


Thanks for your responses

YUU


On Mar 9, 2020, at 11:39 PM, Leszek Kubik <leszek...@gmail.com> wrote:

To understand (make a best guess) a race condition case you need to dump the physical memory and analyze it against the internal code.

But what’s the value of doing that for you?

I’ve done numerous race condition analysis. It’s almost never an obvious answer as data gets overwritten in memory so the chronological order is always lost, you can extract only scraps of it and from that you switch to the source/compiled code to figure out where is the race condition or undefined behavior. In your case it’s obvious so don’t waste time on it.



--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages