summaryrefslogtreecommitdiff
path: root/tests/t_mutex.c
blob: 7c1463be4f02ac65c04000d90c9be6c05b5606a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
   It is considered beerware. Prost. Skol. Cheers or whatever.

   $id$ */

/* System */
#include <pthread.h>
#include <stdatomic.h>
#include <string.h>
#include <unistd.h>

/* Opentracker */
#include "ot_mutex.h"
#include "test.h"

/* Every announce passes through here, and what it has to get right is which
   bucket a hash belongs to and that two threads never hold one bucket at
   once. Both are checked against real mutexes rather than a stand-in. */

/* A hash that lands in a chosen bucket: the bucket is the top
   OT_BUCKET_COUNT_BITS of the first four bytes, read in network order */
static void hash_for_bucket(ot_hash hash, int bucket) {
  memset(hash, 0, sizeof(ot_hash));
  hash[0] = (uint8_t)(bucket >> (OT_BUCKET_COUNT_BITS - 8));
  hash[1] = (uint8_t)((bucket << (16 - OT_BUCKET_COUNT_BITS)) & 0xff);
}

static void case_a_hash_names_its_bucket(void) {
  static const int buckets[] = {0, 1, 2, 17, 511, 512, OT_BUCKET_COUNT - 1};
  size_t           i;

  test_case("a hash is served by the bucket its leading bits name");
  for (i = 0; i < sizeof(buckets) / sizeof(buckets[0]); ++i) {
    ot_hash    hash;
    ot_vector *by_hash, *by_number;

    hash_for_bucket(hash, buckets[i]);
    by_hash = mutex_bucket_lock_by_hash(hash);
    mutex_bucket_unlock_by_hash(hash, 0);

    by_number = mutex_bucket_lock(buckets[i]);
    mutex_bucket_unlock(buckets[i], 0);

    CHECK(by_hash == by_number);
  }
}

static void case_every_bucket_is_its_own(void) {
  ot_vector *first, *last, *middle;

  test_case("no two buckets are the same one");
  first  = mutex_bucket_lock(0);
  mutex_bucket_unlock(0, 0);
  middle = mutex_bucket_lock(OT_BUCKET_COUNT / 2);
  mutex_bucket_unlock(OT_BUCKET_COUNT / 2, 0);
  last   = mutex_bucket_lock(OT_BUCKET_COUNT - 1);
  mutex_bucket_unlock(OT_BUCKET_COUNT - 1, 0);

  CHECK(first != middle);
  CHECK(middle != last);
  CHECK(first != last);
}

static void case_the_count_follows_what_unlock_is_told(void) {
  size_t before = mutex_get_torrent_count();

  test_case("the torrent count moves by what unlocking reports, and not otherwise");
  mutex_bucket_lock(3);
  mutex_bucket_unlock(3, 0);
  CHECK_INT((int)(mutex_get_torrent_count() - before), 0);

  mutex_bucket_lock(3);
  mutex_bucket_unlock(3, 5);
  CHECK_INT((int)(mutex_get_torrent_count() - before), 5);

  mutex_bucket_lock(3);
  mutex_bucket_unlock(3, -2);
  CHECK_INT((int)(mutex_get_torrent_count() - before), 3);
}

static _Atomic int g_other_got_in;

static void *take_a_bucket(void *args) {
  int bucket = (int)(intptr_t)args;

  mutex_bucket_lock(bucket);
  atomic_store(&g_other_got_in, 1);
  mutex_bucket_unlock(bucket, 0);
  return NULL;
}

static int waited_for_entry(int patience_ms) {
  int waited;

  for (waited = 0; waited < patience_ms && !atomic_load(&g_other_got_in); ++waited)
    usleep(1000);
  return atomic_load(&g_other_got_in);
}

static void case_a_held_bucket_keeps_others_out(void) {
  pthread_t other;

  test_case("a second thread waits for the bucket this one holds");
  atomic_store(&g_other_got_in, 0);
  mutex_bucket_lock(9);
  CHECK_INT(pthread_create(&other, NULL, take_a_bucket, (void *)(intptr_t)9), 0);

  CHECK_INT(waited_for_entry(200), 0);

  mutex_bucket_unlock(9, 0);
  CHECK_INT(waited_for_entry(2000), 1);
  pthread_join(other, NULL);
}

static void case_another_bucket_is_free_meanwhile(void) {
  pthread_t other;

  test_case("and a different bucket is not held up by it");
  atomic_store(&g_other_got_in, 0);
  mutex_bucket_lock(9);
  CHECK_INT(pthread_create(&other, NULL, take_a_bucket, (void *)(intptr_t)10), 0);

  CHECK_INT(waited_for_entry(2000), 1);
  pthread_join(other, NULL);
  mutex_bucket_unlock(9, 0);
}

void test_mutex(void) {
  mutex_init();

  case_a_hash_names_its_bucket();
  case_every_bucket_is_its_own();
  case_the_count_follows_what_unlock_is_told();
  case_a_held_bucket_keeps_others_out();
  case_another_bucket_is_free_meanwhile();

  mutex_deinit();
}