Navroz Charania
Navroz Charania

Reputation: 9

Why does a mutex lock end up with threads are sleeping error?

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    manager := NewPlatformManager()
    pTrain := &PassengerTrain{mediator:manager}
    mTrain := &MailTrain{mediator:manager}
    pTrain.Arrive()
    mTrain.Arrive()
    pTrain.Depart()
    //pTrain.Arrive()
    //mTrain.Depart()
    //time.Sleep(20000 * time.Millisecond)
}

/*
Mediator pattern
*/
type Train interface {
    Arrive()
    Depart()
    PermitArrival()
}

type Mediator interface {
    allowLanding(Train) bool
    notifyFree()
}

type PassengerTrain struct {
    mediator Mediator
}

func (t *PassengerTrain) Arrive() {
    if !t.mediator.allowLanding(t) {
        fmt.Println("Station Busy, passenger train")
    } else {
        fmt.Println("Landing on station, passenger train")
        time.Sleep(3000 * time.Millisecond)
    }
}

func (t *PassengerTrain) Depart() {
    t.mediator.notifyFree()
    fmt.Println("leaving station, passenger train")
}

func (t *PassengerTrain) PermitArrival() {
    fmt.Println("Allowed to arrive, passenger train")
    t.Arrive()
}

type MailTrain struct {
    mediator Mediator
}

func (t *MailTrain) Arrive() {
    if !t.mediator.allowLanding(t) {
        fmt.Println("Station Busy, mail train")
    } else {
        fmt.Println("Landing on station, mail train")
        time.Sleep(5000 * time.Millisecond)
    }
}

func (t *MailTrain) Depart() {
    t.mediator.notifyFree()
    fmt.Println("leaving station, mail train")
}

func (t *MailTrain) PermitArrival() {
    fmt.Println("Allowed to arrive, mail train")
    t.Arrive()
}

type PlatformManager struct {
    queue     []Train
    signal    *sync.Mutex
    isBlocked bool
}

func NewPlatformManager() Mediator {
    instance := &PlatformManager{
        queue:     make([]Train,0),
        signal:    &sync.Mutex{},
        isBlocked: false,
    }
    return instance
}

func (pm *PlatformManager) allowLanding(train Train) bool {
    pm.signal.Lock()
    defer pm.signal.Unlock()
    if !pm.isBlocked {
        pm.isBlocked = true
        return true
    }
    pm.queue = append(pm.queue, train)
    return false
}

func (pm *PlatformManager) notifyFree() {
    pm.signal.Lock()
    defer pm.signal.Unlock()

    if pm.isBlocked {
        pm.isBlocked = false
    }
    if len(pm.queue) > 0 {
        nextTrain := pm.queue[0]
        pm.queue = pm.queue[1:]
        nextTrain.PermitArrival()
    }
}

I get the error as below, I was expecting the code to go in a single flow son haven't defined any go routine either. the expected result should be passengerTrain arrives (waits 3 seconds)-> mailTrain is wanting to arrive (is asked to wait)-> passengerTrain leaves -> mailTrain is notified and it arrives and waits (5 seconds)

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_SemacquireMutex(0xc00002c00c, 0x486700, 0x1)
    /usr/local/go-faketime/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0xc00002c008)
    /usr/local/go-faketime/src/sync/mutex.go:138 +0xfc
sync.(*Mutex).Lock(...)
    /usr/local/go-faketime/src/sync/mutex.go:81
main.(*PlatformManager).allowLanding(0xc000060150, 0x4dd220, 0xc000010210, 0x0)
    /tmp/sandbox325139098/prog.go:97 +0x17d
main.(*MailTrain).Arrive(0xc000010210)
    /tmp/sandbox325139098/prog.go:63 +0x48
main.(*MailTrain).PermitArrival(0xc000010210)
    /tmp/sandbox325139098/prog.go:78 +0x83
main.(*PlatformManager).notifyFree(0xc000060150)
    /tmp/sandbox325139098/prog.go:117 +0xbb
main.(*PassengerTrain).Depart(0xc000010200)
    /tmp/sandbox325139098/prog.go:49 +0x37
main.main()
    /tmp/sandbox325139098/prog.go:15 +0x13b

I ran it in go playground https://play.golang.org/p/JO2mEQSk_kI

Upvotes: 0

Views: 994

Answers (1)

poWar
poWar

Reputation: 823

You are getting this error because func (pm *PlatformManager) notifyFree() creates a lock on pm.signal and then calls nextTrain.PermitArrival() which in turn tries to get a lock on pm.signal. Because of this layout of code, nextTrain.PermitArrival() will never get to lock pm.signal and hence the error on line no 97.

You can use go routines to implement this more elegantly. You can refer to this ping pong example to start with.

Upvotes: 4

Related Questions