Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
bgColor#ffcccc
langc
#include <stdlib.h><mutex>
#include <threads.h><thread>
  
typedef struct {
  int balance;
  mtx_tstd::mutex balance_mutex;
} bank_account;

typedef struct {
  bank_account *from;
  bank_account *to;
  int amount;
} transaction;

void create_bank_account(bank_account **ba,
                         int initial_amount) {
  bank_account *nba = (new bank_account *)malloc(
    sizeof(bank_account)
  );
  if (nba == NULL) {
    /* Handle error */
  }

  nba->balance = initial_amount;
  if (thrd_success
      != mtx_init(&nba->balance_mutex, mtx_plain)) {
    /* Handle error */
  }

  *ba = nba;
}
 
int deposit(voidbank_account *ptr) {
  transaction *args = (transaction *)ptr;

  if (thrd_success != mtx_lock(&args->from->balance_mutex)) {
    /* Handle error */
  }

from, bank_account *to, int amount) {
  std::lock_guard<std::mutex> from_guard(from->balance_mutex);
 
  /* Not enough balance to transfer */
  if (args->fromfrom->balance < args->amount) {
    if (thrd_success
        != mtx_unlock(&args->from->balance_mutex)amount) {
      /* Handle error */
    }
    return -1; /* Indicate error */
  }
  if (thrd_success != mtx_lock(&args->to->balance_mutex)) {
    /* Handle error */
  }

  args->from-std::lock_guard<std::mutex> to_guard(to->balance_mutex);
 
  from->balance -= args->amountamount;
  args->toto->balance += args->amount;

  if (thrd_success
      != mtx_unlock(&args->from->balance_mutex)) {
    /* Handle error */
  }

  if (thrd_success
      != mtx_unlock(&args->to->balance_mutex)) {
    /* Handle error */
  }

  free(ptr);amount;
 
  return 0;
}
 
int main(void) {
  thrd_t thr1, thr2;
  transaction *arg1;
  transaction *arg2;
  bank_account *ba1;
  bank_account *ba2;
 
  create_bank_account(&ba1, 1000);
  create_bank_account(&ba2, 1000);

  arg1 = (transaction *)malloc(sizeof(transaction));
  if (arg1 == NULL) {
    /* HandlePerform the errordeposits */
  }
  arg2 = (transaction *)malloc(sizeof(transaction));
  if (arg2 == NULL) {
    /* Handle error */
  }
  arg1->from = ba1;
  arg1->to = ba2;
  arg1->amount = 100;

  arg2->from = ba2;
  arg2->to = ba1;
  arg2->amount = 100;

  /* Perform the deposits */
  if (thrd_success
     != thrd_create(&thr1, deposit, (void *)arg1)) {
    /* Handle error */
  }
  if (thrd_success
      != thrd_create(&thr2, deposit, (void *)arg2)) {
    /* Handle error */
  }
  std::thread thr1(deposit, ba1, ba2, 100);
  std::thread thr2(deposit, ba2, ba1, 100);
  thr1.join();
  thr2.join();
  return 0;
} 

Compliant Solution

This compliant solution eliminates the circular wait condition by establishing a predefined order for locking in the deposit() function. Each thread will lock on the basis of the bank_account ID, which is set when the bank_account struct is initialized.

Code Block
bgColor#ccccff
langc
#include <stdlib.h><mutex>
#include <threads.h><thread>
  
typedef struct {
  int balance;
  mtx_tstd::mutex balance_mutex;
  
  /* Should not change after initialization */
  unsigned int id;
} bank_account;

typedef struct {
  bank_account *from;
  bank_account *to;
  int amount;
} transaction;

unsigned int global_id = 1;
 
void create_bank_account(bank_account **ba,
                         int initial_amount) {
  bank_account *nba = (new bank_account *)malloc(
    sizeof(bank_account)
  );
  if (nba == NULL) {
    /* Handle error */
  }

  nba->balance = initial_amount;
  if (thrd_success
      != mtx_init(&nba->balance_mutex, mtx_plain)) {
    /* Handle error */
  }

  nba->id = global_id++;
  *ba = nba;
}
 
int deposit(voidbank_account *ptr) {
  transaction *args = (transaction *)ptr;from, bank_account *to, int amount) {
  int result = -1;
  mtx_tstd::mutex *first;
  mtx_tstd::mutex *second;
 
  if (argsfrom->from->id == args->toto->id) {
    return -1; /* Indicate error */
  }
 
  /* Ensure proper ordering for locking */
  if (args->fromfrom->id < argsto->to->id) {
    first = &args->fromfrom->balance_mutex;
    second = &argsto->to->balance_mutex;
  } else {
    first = &argsto->to->balance_mutex;
    second = &argsfrom->from->balance_mutex;
  }
  if (thrd_success != mtx_lock(first)) {
    /* Handle error */
  }
  if (thrd_success != mtx_lock(second)) {
    /* Handle error */
  }
std::lock_guard<std::mutex> first_guard(*first);
  std::lock_guard<std::mutex> second_guard(*second);
 
  /* Not enough balance to transfer */
  if (argsfrom->from->balance >= args->amountamount) {
    args->fromfrom->balance -= args->amountamount;
    args->toto->balance += args->amountamount;
    result = 0;
  }

  if (thrd_success != mtx_unlock(second)) {
    /* Handle error */
  }
  if (thrd_success != mtx_unlock(first)) {
    /* Handle error */
  }
  free(ptr);
  return result;
} 

Risk Assessment

Deadlock prevents multiple threads from progressing, halting program execution. A denial-of-service attack is possible if the attacker can create the conditions for deadlock.

...