...
Noncompliant Code Example
Based on The following code has behavior which is dependend on the runtime environment and the scheduler on the operating system, the following code will have different behaviorsplatform's scheduler. However, with proper timing, the main()
function will deadlock when running thr1
and thr2
in which thr1
tries to lock ba2
's mutex while thr2
tries to lock on ba1
's mutex in the deposit()
function and the program will not progress.
Code Block | ||
---|---|---|
| ||
#include <stdio.h> #include <pthread.h> #include <stdlib.h> typedef struct { int balance; pthread_mutex_t balance_mutex; } bank_account; typedef struct { bank_account *from; bank_account *to; int amount; } deposit_thr_args; /* return negative on error */ int void create_bank_account(bank_account **ba, int initial_amount) { int retresult; bank_account *nba = malloc(sizeof(bank_account)); if (nba == NULL) { /* return -1;Handle Error */ } nba->balance = initial_amount; retresult = pthread_mutex_init(&nba->balance_mutex, NULL); if (retresult) { /* Handle Error */ exit(ret);} *ba = nba; return 0; } void *deposit(void *ptr) { int result; deposit_thr_args *args = (deposit_thr_args *)ptr; if ((result = pthread_mutex_lock(&(args->from->balance_mutex))); != 0) { /* Handle Error */ } /* not enough balance to transfer */ if (args->from->balance < args->amount) { if ((result = pthread_mutex_unlock(&(args->from->balance_mutex));) != 0) { /* Handle Error */ } return NULL; } if ((result = pthread_mutex_lock(&(args->to->balance_mutex));)) != 0) { /* Handle Error */ } args->from->balance -= args->amount; args->to->balance += args->amount; if ((result = pthread_mutex_unlock(&(args->from->balance_mutex))); != 0) { /* Handle Error */ } if ((result = pthread_mutex_unlock(&(args->to->balance_mutex))); != 0) { /* Handle Error */ } free(ptr); return NULL; } int main(void) { pthread_t thr1, thr2; int errresult; bank_account *ba1,; bank_account *ba2; err = create_bank_account(&ba1, 1000); if (err < 0) exit(err); err = create_bank_account(&ba2, 1000); if (err < 0) exit(err); deposit_thr_args *arg1 = malloc(sizeof(deposit_thr_args)); if (arg1 == NULL) { /* exit(-1); Handle Error */ } deposit_thr_args *arg2 = malloc(sizeof(deposit_thr_args)); if (arg2 == NULL) { exit(-1);/* Handle Error */ } arg1->from = ba1; arg1->to = ba2; arg1->amount = 100; arg2->from = ba2; arg2->to = ba1; arg2->amount = 100; /* perform the depositdeposits */ if err((result = pthread_create(&thr1, NULL, deposit, (void *)arg1);) != 0) { if (err) /* Handle exit(err); Error */ } errif ((result = pthread_create(&thr2, NULL, deposit, (void *)arg2);)) != 0) { if (err) exit(err); /* Handle Error */ } pthread_exit(NULL); return 0; } |
Compliant Solution
The solution to the deadlock problem is to lock in use a predefined order for the locks in the deposit()
function. In the following examplecompliant solution, each thread will lock based on the id of bank_account
's id defined in the struct initialization. This way prevents the circular wait problem is avoided and when one thread requires a lock will guarantee it will require the next lock.
Code Block | ||
---|---|---|
| ||
typedef struct { int balance; pthread_mutex_t balance_mutex; unsigned int id; /* read only and should never be changed after initialized */ } bank_account; unsigned int global_id = 1; /* return negative on error */ int void create_bank_account(bank_account **ba, int initial_amount) { int retresult; bank_account *nba = malloc(sizeof(bank_account)); if (nba == NULL) { /* Handle return -1;Error */ } nba->balance = initial_amount; retresult = pthread_mutex_init(&nba->balance_mutex, NULL); if (ret) result != 0) { /* exit(ret);Handle Error */ } nba->id = global_id++; *ba = nba; return 0; } void *deposit(void *ptr) { deposit_thr_args *args = (deposit_thr_args *)ptr; int result; if (args->from->id == args->to->id) return NULL; /* ensure proper ordering for locking */ if (args->from->id < args->to->id) { if ((result = pthread_mutex_lock(&(args->from->balance_mutex));) != 0) { /* Handle Error */ } if ((result = pthread_mutex_lock(&(args->to->balance_mutex));)) != 0) { /* Handle Error */ } } else { if ((result = pthread_mutex_lock(&(args->to->balance_mutex)))); != 0) { /* Handle Error */ } if ((result = pthread_mutex_lock(&(args->from->balance_mutex));) != 0) { /* Handle Error */ } } /* not enough balance to transfer */ if (args->from->balance < args->amount) { if ((result = pthread_mutex_unlock(&(args->from->balance_mutex)))); != 0) { /* Handle Error */ } if ((result = pthread_mutex_unlock(&(args->to->balance_mutex));))) != 0) { /* Handle Error */ } return NULL; } args->from->balance -= args->amount; args->to->balance += args->amount; if ((result = pthread_mutex_unlock(&(args->from->balance_mutex)); )) != 0) { /* Handle Error */ } if ((result = pthread_mutex_unlock(&(args->to->balance_mutex));)) != 0) { /* Handle Error */ } free(ptr); return NULL; } |
Risk Assessment
Deadlock causes multiple threads to become unable to progress and thus halts the executing program. This is a potential denial-of-service attack because the attacker can force deadlock situations. It is likely for deadlock to occur in multi-threaded programs that manage multiple shared resources.
...