Im trying to make a simple mutext with a specific behavior. But the specific behavior is imposible because golang doesnt work out of the blue
simple test code returns an error: all goroutines are asleep - deadlock!
A first block with for works perfectly without an error
Second block failures on m.Unlock()
What's wrong? What's happened? I am only human. I write code like human. And there is human logic:
- I opened the channel with buffer
- I sent the value to the channel What go-routines should I think about?
Result:
=== RUN TestNiceMutex_Lock
=== RUN TestNiceMutex_Lock/test
nicemutex_test.go:36: main: lock
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
testing.(*T).Run(0xc00007b380, {0xcee048?, 0xc1f6cd?}, 0xcf8388)
C:/Program Files/Go/src/testing/testing.go:1649 +0x3c8
testing.runTests.func1(0xdecd00?)
C:/Program Files/Go/src/testing/testing.go:2054 +0x3e
testing.tRunner(0xc00007b380, 0xc0000b7c48)
C:/Program Files/Go/src/testing/testing.go:1595 +0xff
testing.runTests(0xc00009a140?, {0xde0d90, 0x1, 0x1}, {0xbc3345?, 0xc0000b7d08?, 0x0?})
C:/Program Files/Go/src/testing/testing.go:2052 +0x445
testing.(*M).Run(0xc00009a140)
C:/Program Files/Go/src/testing/testing.go:1925 +0x636
main.main()
_testmain.go:47 +0x19c
goroutine 6 [chan receive]:
testing.(*T).Run(0xc00007b520, {0xceae57?, 0xc78420?}, 0xc00023c000)
C:/Program Files/Go/src/testing/testing.go:1649 +0x3c8
simply/nicemutex.TestNiceMutex_Lock(0x0?)
C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:26 +0xba
testing.tRunner(0xc00007b520, 0xcf8388)
C:/Program Files/Go/src/testing/testing.go:1595 +0xff
created by testing.(*T).Run in goroutine 1
C:/Program Files/Go/src/testing/testing.go:1648 +0x3ad
(content is repeated)
goroutine 97 [chan send]:
simply/nicemutex.lock()
C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex.go:6
simply/nicemutex.TestNiceMutex_Lock.func1()
C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:12 +0x25
created by simply/nicemutex.TestNiceMutex_Lock in goroutine 6
C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:11 +0x2c
goroutine 117 [chan send]:
simply/nicemutex.(*NiceMutex).Lock(0xc000250008)
C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex.go:25 +0x99
simply/nicemutex.TestNiceMutex_Lock.func2.1()
C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:32 +0x5f
created by simply/nicemutex.TestNiceMutex_Lock.func2 in goroutine 116
C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:30 +0xa5
Process finished with the exit code 1
package nicemutex
import (
"sync"
"testing"
"time"
)
func TestNiceMutex_Lock(t *testing.T) {
for i := 0; i < 100; i++ {
go func() {
lock()
unlock()
}()
lock()
unlock()
}
tests := []struct {
name string
}{
{"test"},
}
wg := sync.WaitGroup{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var m NiceMutex
m.Lock()
wg.Add(1)
go func() {
defer m.Unlock()
m.Lock()
t.Log("go: lock")
wg.Done()
}()
t.Log("main: lock")
time.Sleep(time.Second * 1)
m.Unlock() // <- error
wg.Wait()
})
}
}
package nicemutex
var internalLock = make(chan struct{}, 1)
func lock() {
internalLock <- struct{}{} // <- error
}
func unlock() {
<-internalLock
}
type NiceMutex struct {
ch chan struct{}
}
func (m *NiceMutex) Lock() {
defer unlock()
lock()
if m.ch == nil {
m.ch = make(chan struct{}, 1)
}
m.ch <- struct{}{}
}
func (m *NiceMutex) Unlock() {
defer unlock()
lock() // <- error
if m.ch == nil {
return
}
<-m.ch
}
Updated: New version - same result
package nicemutex
import (
"log"
"time"
)
var (
internalLock2 = make(chan struct{}, 1)
TraceEnabled2 = false
)
type NiceMutex2 struct {
ch chan struct{}
lockedOn int64
}
func (m *NiceMutex2) Lock() {
internalLock2 <- struct{}{}
defer func() { <-internalLock2 }()
m.lockedOn = time.Now().UnixNano()
if m.ch == nil {
m.ch = make(chan struct{}, 1)
}
m.ch <- struct{}{}
trace2("locked")
}
func (m *NiceMutex2) LockForSec() {
internalLock2 <- struct{}{}
defer func() { <-internalLock2 }()
on := time.Now().UnixNano()
m.lockedOn = on
if m.ch == nil {
m.ch = make(chan struct{}, 1)
}
m.ch <- struct{}{}
go func(lockedOn int64) {
<-time.After(time.Second)
internalLock2 <- struct{}{}
defer func() { <-internalLock2 }()
if m.lockedOn == lockedOn {
select {
case <-m.ch:
trace2("timeout used", true)
default:
trace2("timeout ignore")
}
}
}(on)
trace2("locked")
}
func (m *NiceMutex2) Unlock() {
internalLock2 <- struct{}{}
defer func() { <-internalLock2 }()
m.lockedOn = time.Now().UnixNano()
if m.ch == nil {
return
}
select {
case <-m.ch:
default:
}
trace2("unlocked")
}
func trace2(msg string, ignoreEnabling ...bool) {
if TraceEnabled2 || (len(ignoreEnabling) > 0 && ignoreEnabling[0]) {
log.Println(msg)
}
}
Welcome to SO!
Firstly, using channels like this as a mutex isn't exactly "simple". Reasoning about concurrency state is complex.
The reason for the deadlock is because the channel buffers are full.
m.Lock()fillsinternalLockm.Lock()fillsm.chm.Lock()drainsinternalLockm.Lock()fillsinternalLockm.Lock()blocks asm.chbuffer is fullm.Lock()never drainsinternalLockm.Unlock()blocks asinternalLockbuffer is fullm.Unlock()never drainsm.chBoth the goroutine and main paths are blocked forever. The goroutine will never drain
internalLockuntil it can write tom.ch. And the opposite is true for main.