187 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			187 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			C++
		
	
	
	
// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s
 | 
						|
 | 
						|
#include <pthread.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <time.h>
 | 
						|
#include <errno.h>
 | 
						|
#include <vector>
 | 
						|
#include <algorithm>
 | 
						|
 | 
						|
const int kThreads = 4;
 | 
						|
const int kMutexes = 16 << 10;
 | 
						|
const int kIters = 400 << 10;
 | 
						|
const int kMaxPerThread = 10;
 | 
						|
 | 
						|
const int kStateInited = 0;
 | 
						|
const int kStateNotInited = -1;
 | 
						|
const int kStateLocked = -2;
 | 
						|
 | 
						|
struct Mutex {
 | 
						|
  int state;
 | 
						|
  pthread_rwlock_t m;
 | 
						|
};
 | 
						|
 | 
						|
Mutex mtx[kMutexes];
 | 
						|
 | 
						|
void check(int res) {
 | 
						|
  if (res != 0) {
 | 
						|
    printf("SOMETHING HAS FAILED\n");
 | 
						|
    exit(1);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool cas(int *a, int oldval, int newval) {
 | 
						|
  return __atomic_compare_exchange_n(a, &oldval, newval, false,
 | 
						|
      __ATOMIC_ACQ_REL, __ATOMIC_RELAXED);
 | 
						|
}
 | 
						|
 | 
						|
void *Thread(void *seed) {
 | 
						|
  unsigned rnd = (unsigned)(unsigned long)seed;
 | 
						|
  int err;
 | 
						|
  std::vector<int> locked;
 | 
						|
  for (int i = 0; i < kIters; i++) {
 | 
						|
    int what = rand_r(&rnd) % 10;
 | 
						|
    if (what < 4 && locked.size() < kMaxPerThread) {
 | 
						|
      // lock
 | 
						|
      int max_locked = -1;
 | 
						|
      if (!locked.empty()) {
 | 
						|
        max_locked = *std::max_element(locked.begin(), locked.end());
 | 
						|
        if (max_locked == kMutexes - 1) {
 | 
						|
          i--;
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      int id = (rand_r(&rnd) % (kMutexes - max_locked - 1)) + max_locked + 1;
 | 
						|
      Mutex *m = &mtx[id];
 | 
						|
      // init the mutex if necessary or acquire a reference
 | 
						|
      for (;;) {
 | 
						|
        int old = __atomic_load_n(&m->state, __ATOMIC_RELAXED);
 | 
						|
        if (old == kStateLocked) {
 | 
						|
          pthread_yield();
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
        int newv = old + 1;
 | 
						|
        if (old == kStateNotInited)
 | 
						|
          newv = kStateLocked;
 | 
						|
        if (cas(&m->state, old, newv)) {
 | 
						|
          if (old == kStateNotInited) {
 | 
						|
            if ((err = pthread_rwlock_init(&m->m, 0))) {
 | 
						|
              fprintf(stderr, "pthread_rwlock_init failed with %d\n", err);
 | 
						|
              exit(1);
 | 
						|
            }
 | 
						|
            if (!cas(&m->state, kStateLocked, 1)) {
 | 
						|
              fprintf(stderr, "init commit failed\n");
 | 
						|
              exit(1);
 | 
						|
            }
 | 
						|
          }
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      // now we have an inited and referenced mutex, choose what to do
 | 
						|
      bool failed = false;
 | 
						|
      switch (rand_r(&rnd) % 4) {
 | 
						|
      case 0:
 | 
						|
        if ((err = pthread_rwlock_wrlock(&m->m))) {
 | 
						|
          fprintf(stderr, "pthread_rwlock_wrlock failed with %d\n", err);
 | 
						|
          exit(1);
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      case 1:
 | 
						|
        if ((err = pthread_rwlock_rdlock(&m->m))) {
 | 
						|
          fprintf(stderr, "pthread_rwlock_rdlock failed with %d\n", err);
 | 
						|
          exit(1);
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      case 2:
 | 
						|
        err = pthread_rwlock_trywrlock(&m->m);
 | 
						|
        if (err != 0 && err != EBUSY) {
 | 
						|
          fprintf(stderr, "pthread_rwlock_trywrlock failed with %d\n", err);
 | 
						|
          exit(1);
 | 
						|
        }
 | 
						|
        failed = err == EBUSY;
 | 
						|
        break;
 | 
						|
      case 3:
 | 
						|
        err = pthread_rwlock_tryrdlock(&m->m);
 | 
						|
        if (err != 0 && err != EBUSY) {
 | 
						|
          fprintf(stderr, "pthread_rwlock_tryrdlock failed with %d\n", err);
 | 
						|
          exit(1);
 | 
						|
        }
 | 
						|
        failed = err == EBUSY;
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      if (failed) {
 | 
						|
        if (__atomic_fetch_sub(&m->state, 1, __ATOMIC_ACQ_REL) <= 0) {
 | 
						|
          fprintf(stderr, "failed to unref after failed trylock\n");
 | 
						|
          exit(1);
 | 
						|
        }
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      locked.push_back(id);
 | 
						|
    } else if (what < 9 && !locked.empty()) {
 | 
						|
      // unlock
 | 
						|
      int pos = rand_r(&rnd) % locked.size();
 | 
						|
      int id = locked[pos];
 | 
						|
      locked[pos] = locked[locked.size() - 1];
 | 
						|
      locked.pop_back();
 | 
						|
      Mutex *m = &mtx[id];
 | 
						|
      if ((err = pthread_rwlock_unlock(&m->m))) {
 | 
						|
        fprintf(stderr, "pthread_rwlock_unlock failed with %d\n", err);
 | 
						|
        exit(1);
 | 
						|
      }
 | 
						|
      if (__atomic_fetch_sub(&m->state, 1, __ATOMIC_ACQ_REL) <= 0) {
 | 
						|
        fprintf(stderr, "failed to unref after unlock\n");
 | 
						|
        exit(1);
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      // Destroy a random mutex.
 | 
						|
      int id = rand_r(&rnd) % kMutexes;
 | 
						|
      Mutex *m = &mtx[id];
 | 
						|
      if (!cas(&m->state, kStateInited, kStateLocked)) {
 | 
						|
        i--;
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      if ((err = pthread_rwlock_destroy(&m->m))) {
 | 
						|
        fprintf(stderr, "pthread_rwlock_destroy failed with %d\n", err);
 | 
						|
        exit(1);
 | 
						|
      }
 | 
						|
      if (!cas(&m->state, kStateLocked, kStateNotInited)) {
 | 
						|
        fprintf(stderr, "destroy commit failed\n");
 | 
						|
        exit(1);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  // Unlock all previously locked mutexes, otherwise other threads can deadlock.
 | 
						|
  for (int i = 0; i < locked.size(); i++) {
 | 
						|
    int id = locked[i];
 | 
						|
    Mutex *m = &mtx[id];
 | 
						|
    if ((err = pthread_rwlock_unlock(&m->m))) {
 | 
						|
      fprintf(stderr, "pthread_rwlock_unlock failed with %d\n", err);
 | 
						|
      exit(1);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
int main() {
 | 
						|
  timespec ts;
 | 
						|
  clock_gettime(CLOCK_MONOTONIC, &ts);
 | 
						|
  unsigned s = (unsigned)ts.tv_nsec;
 | 
						|
  fprintf(stderr, "seed %d\n", s);
 | 
						|
  srand(s);
 | 
						|
  for (int i = 0; i < kMutexes; i++)
 | 
						|
    mtx[i].state = kStateNotInited;
 | 
						|
  pthread_t t[kThreads];
 | 
						|
  for (int i = 0; i < kThreads; i++)
 | 
						|
    pthread_create(&t[i], 0, Thread, (void*)(unsigned long)rand());
 | 
						|
  for (int i = 0; i < kThreads; i++)
 | 
						|
    pthread_join(t[i], 0);
 | 
						|
  fprintf(stderr, "DONE\n");
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
// CHECK-NOT: WARNING: ThreadSanitizer
 | 
						|
// CHECK: DONE
 | 
						|
 |