software engineering
Software Deadlock problem, what it is? how do we tackle it? š°
date
slug
deadlock-problem-what-it-is
author
status
Public
tags
system architecture
coding
go
summary
Imagine your life is messed up right now, you cant turn back and you cant move forward, what will you do? Will you stay on this position? Same as it is. Software can encounter kind of this situation
type
Post
category
software engineering
updatedAt
Apr 22, 2023 04:52 AM
Like
Once upon a time, there were two drivers, Naufal and Djamhur, who were driving on a narrow road towards each other. They reached a point where the road was too narrow for both cars to pass each other, and they ended up stuck in a deadlock.
Naufal refused to back up his car, as he had already traveled a long distance, and wanted Djamhur to back up his car. However, Djamhur also refused to back up his car, as he believed he had the right of way and that Naufal should back up instead.
As a result, they both remained stuck and unable to move. Other drivers on the road honked their horns and tried to intervene, but Naufal and Djamhur were both determined to hold their ground.
After a while, they realized that they needed to find a way to break the deadlock. They both got out of their cars and started talking. They came to a mutual agreement and decided to both back up their cars at the same time, allowing them to pass each other.
Thatās how deadlock in simple story-telling on our daily life as software can encounter this situation
So what is dead-lock actually?
Deadlock is a situation where a computer system meets two processes at the same time and is unable to continue executing on both of them because they are waiting for each other to continue. In other words with more scientific phrases, we can say it is a situation where two or more processes are stuck in a circular wait for resources that the other process holds.

Okay, I think this is too complex without simple Illustration of Cop and Criminal

So imagine you have been kidnapped by a criminal, and then a Cop comes to your house to save you. The cop successfully caught up with the 2nd criminal, but he realizes he doesnāt catch the other ones. Also, you have been kidnapped in the first place. Both sides got their hostage, and they wonāt move if their demands were not fulfilled. So, basically, dead-lock is a situation where no one is getting fulfilled, so no one will show the next move.
Ā
Deadlock Problem
The deadlock use case is the HARDEST part to debug because you canāt look up directly whatās wrong in your code. The hard part happened because youāre going to debug on your multi-threaded environment. Itās getting complex if you didnāt know how your languages compile, first of all, you need to place a breakpoint on your concurrency where deadlock might suspend in your background. Let's start from very-very simple deadlock problem in coding example, I will use Go as an example
package main
import "fmt"
func main() {
messages := make(chan string)
// go-routine read from main channel
// but there's no other go-routine send the channel
fmt.Println(<-messages)
}
Letās see what will happen to the messages channel
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
So we can assume that, IF we want to prevent the messages in deadlock conditions first thing first we need to have the sender (1)
package main
func main() {
messages := make(chan string)
// go-routine send messages
// but no go-routine read the messages
messages <- "Hello Naufal!"
}
Letās see what will happen to the messages channel
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
Of course, thereās no sender, how could possibly not be stuck in a deadlock? (2)
Thatās some kind of simple dead-lock problem, letās go ahead into a deep explanation of how Concurrency works in Go before we head into Concurrency code
Go runtime creates a number of threads based on the number of available CPUs on the system.
Each thread is then responsible for executing a number of goroutines concurrently. The Go runtime manages the creation, scheduling, and execution of these goroutines across multiple threads, ensuring that they are executed efficiently and without contention.
Go already had controlled access to shared resources and coordinated the execution of concurrent operations.
In Go, dead-locking can happen IF we are locking more than once in SAME PROCESS OR THREAD, itās because Go Mutex is not Reentrant, not like C++ or Python that Reentrant
Simple code shown like this
package main
import "sync"
func main() {
var mutex sync.Mutex
mutex.Lock()
// deadlock will happend here, since we lock twice on same thread
mutex.Lock()
}
But sometimes you might questioning if we improve that kind of code, we want to unlock the mutex but somehow we got the deadlock? Letās see the code here
package main
import (
"fmt"
"sync"
)
var number = make(chan int)
var mutex = &sync.Mutex{}
func worker(wg *sync.WaitGroup, id int) {
defer wg.Done()
mutex.Lock()
number <- id + <-number
mutex.Unlock()
}
func main() {
var wg sync.WaitGroup
number <- 0
for i := 0; i < 5; i++ {
wg.Add(1)
go worker(&wg, i)
}
wg.Wait()
fmt.Println(<-number)
But we see here was
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
We were expecting a number that calculated as a result for 10
Whereās the problem? Letās breakdown it
var number = make(chan int)
We can see that an unbuffered channel will block until there is a receiver to receive the message. Main go routine should add a number of channel in go-routine, ADDING ZERO TO THE CHANNEL, will not take the place until thereās something to receive. So it blocks before even it reach the Mutex.
Supplying size to a channel is a must, we need to add the code into
var number = make(chan int 5)
Letās stop re-iterating with the simple problem such as Channelling, Concurrency just in code. How about we head into real world-life problem?
Real Case Problem Discussion
We had an app written by Golang, with PostgreSQL Databases, and we had a transaction. Imagine our app is a Payment Service, our Payment Services needs a UNIQUE Payment Code to process the request, shown in the diagram below

Since we use Golang, thereās transaction support on it.
Supposed the Payment Code was expected as Unique Code. But somehow we violent the constraint, as seems we see that our Payment Code is generated as duplicated when we tried to hit the services at the same time. Of course, we can assume that we had a race condition in our concurrency system. Of course, this happened on our DB side, since we already handle our native transactions in our Golang apps.
The solution can be up on the code or the database, which will be your first investigation? Adding wait on the concurrent while itās generated PaymentCode to the DB? or we can add Isolation Level on the Postgres? Let's see your solution in the comment below!
Ā
TL;DR Thankyou for reading my post! š
Ā
Ā