ยง32.1โ€“32.2What Types Of Bugs Exist? โ€ฆ Non-Deadlock Bugs

Part II OSTEP pp. 385โ€“388 ยท ~5 min read

  • atomicity violation
  • order violation

Before diving into deadlock, it helps to know the patterns real concurrency bugs take โ€” the first step to writing robust code.

The Crux: How To Handle Common Concurrency Bugs

Concurrency bugs come in a few common patterns. Knowing which to look out for is the first step to writing more robust, correct concurrent code.

32.1 What Types Of Bugs Exist?

Lu et al. studied concurrency bugs found and fixed in four mature open-source applications โ€” a rare quantitative look at what actually goes wrong:

Figure 32.1: the Lu et al. study โ€” 105 concurrency bugs in four mature open-source apps. Most (74) are NOT deadlock.
what it doesnon-deadlockdeadlock
MySQLdatabase server149
Apacheweb server134
Mozillaweb browser4116
OpenOfficeoffice suite62
Totalโ€”7431
Dotted-underlined cells have explanations โ€” click one.

The headline: non-deadlock bugs are the majority (74 of 105). We look at those first, then deadlock.

32.2 Non-Deadlock Bugs

Lu found two dominant patterns โ€” together ~97% of non-deadlock bugs.

Atomicity-violation bugs

A sequence of accesses the programmer assumed was atomic gets interrupted. The MySQL example (Fig 32.2):

// Thread 1
if (thd->proc_info) {
    fputs(thd->proc_info, ...);
}

// Thread 2
thd->proc_info = NULL;

Thread 1 checks the pointer, then uses it โ€” assuming nothing changes in between. But it can:

Figure 32.2 atomicity violation (MySQL): the check and the use of proc_info were assumed atomic โ€” a switch in between crashes.
t =
Thread 1
Thread 2
shared state
proc_info: valid
tick 1 / 4 ยท one scheduling step per tickrunningreadyCRASH

Thread 1 checks: proc_info is non-NULL, so it enters the if โ€” about to fputs() it.

Formally (Lu): โ€œa code region is intended to be atomic, but the atomicity is not enforced during execution.โ€ This is an atomicity violation . The fix is usually simple โ€” hold a lock around every access to the shared field (Fig 32.3):

pthread_mutex_t proc_info_lock = PTHREAD_MUTEX_INITIALIZER;

// Thread 1
pthread_mutex_lock(&proc_info_lock);
if (thd->proc_info) {
    fputs(thd->proc_info, ...);
}
pthread_mutex_unlock(&proc_info_lock);

// Thread 2
pthread_mutex_lock(&proc_info_lock);
thd->proc_info = NULL;
pthread_mutex_unlock(&proc_info_lock);

Order-violation bugs

The desired order between two accesses is flipped โ€” A should run before B, but nothing enforces it. The Mozilla example (Fig 32.4):

// Thread 1
void init() {
    mThread = PR_CreateThread(mMain, ...);
}

// Thread 2
void mMain(...) {
    mState = mThread->State;
}

Thread 2 assumes mThread is already set. If it runs first, it isnโ€™t:

Figure 32.4 order violation (Mozilla): Thread 2 assumes Thread 1 already initialized mThread โ€” but the order was never enforced.
t =
Thread 1
Thread 2
shared state
mThread: NULL
tick 1 / 3 ยท one scheduling step per tickrunningreadyCRASH

Thread 1's init() calls PR_CreateThread, which creates Thread 2 (mMain). But mThread hasn't been assigned the return value yet โ€” it's still NULL.

Formally: โ€œA should always be executed before B, but the order is not enforced.โ€ This is an order violation . The fix is to enforce order โ€” a condition variable (with a state variable) is the robust way (Fig 32.5):

pthread_mutex_t mtLock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  mtCond = PTHREAD_COND_INITIALIZER;
int mtInit = 0;

// Thread 1 โ€” after creating the thread:
pthread_mutex_lock(&mtLock);
mtInit = 1;
pthread_cond_signal(&mtCond);
pthread_mutex_unlock(&mtLock);

// Thread 2 โ€” before using mThread:
pthread_mutex_lock(&mtLock);
while (mtInit == 0)
    pthread_cond_wait(&mtCond, &mtLock);
pthread_mutex_unlock(&mtLock);
mState = mThread->State; // now safe

If Thread 2 runs early it waits for the signal; if it runs late it sees mtInit == 1 and proceeds. When order matters between threads, condition variables (or semaphores) come to the rescue.

Because atomicity and order violations together cover the vast majority of non-deadlock bugs, learning to spot these two patterns โ€” and reaching for a lock or a condition variable โ€” prevents most of them. Next: the harder, older problem of deadlock.

Check yourself: non-deadlock bugs

1.According to Lu et al.'s study, which two patterns cover ~97% of non-deadlock concurrency bugs?

2.In the MySQL example, Thread 1 does `if (proc_info) { fputs(proc_info); }` and Thread 2 does `proc_info = NULL`. What's the bug?

3.How do you fix an atomicity violation like the proc_info bug?

4.In the Mozilla example, Thread 1's init() sets mThread while Thread 2's mMain() reads mThread->State. What's the bug?

5.What's the robust fix for an order violation?

6.Of the 105 bugs Lu et al. studied, how did non-deadlock and deadlock bugs split?

6 questions