diff options
Diffstat (limited to 'tests/t_mutex.c')
| -rw-r--r-- | tests/t_mutex.c | 171 |
1 files changed, 101 insertions, 70 deletions
diff --git a/tests/t_mutex.c b/tests/t_mutex.c index 3307d70..7c1463b 100644 --- a/tests/t_mutex.c +++ b/tests/t_mutex.c | |||
| @@ -4,104 +4,135 @@ | |||
| 4 | $id$ */ | 4 | $id$ */ |
| 5 | 5 | ||
| 6 | /* System */ | 6 | /* System */ |
| 7 | #include <stdlib.h> | 7 | #include <pthread.h> |
| 8 | #include <stdatomic.h> | ||
| 8 | #include <string.h> | 9 | #include <string.h> |
| 10 | #include <unistd.h> | ||
| 9 | 11 | ||
| 10 | /* Opentracker */ | 12 | /* Opentracker */ |
| 11 | #include "ot_mutex.h" | 13 | #include "ot_mutex.h" |
| 12 | #include "test.h" | 14 | #include "test.h" |
| 13 | 15 | ||
| 14 | /* A worker asks for a class of task and blocks until one turns up, so a task | 16 | /* Every announce passes through here, and what it has to get right is which |
| 15 | whose class matches no worker is never claimed and whoever is waiting for | 17 | bucket a hash belongs to and that two threads never hold one bucket at |
| 16 | its answer waits forever. These cases pin the matching. */ | 18 | once. Both are checked against real mutexes rather than a stand-in. */ |
| 17 | 19 | ||
| 18 | static void case_task_goes_to_its_own_class(void) { | 20 | /* A hash that lands in a chosen bucket: the bucket is the top |
| 19 | ot_tasktype asked; | 21 | OT_BUCKET_COUNT_BITS of the first four bytes, read in network order */ |
| 20 | ot_taskid taskid; | 22 | static void hash_for_bucket(ot_hash hash, int bucket) { |
| 23 | memset(hash, 0, sizeof(ot_hash)); | ||
| 24 | hash[0] = (uint8_t)(bucket >> (OT_BUCKET_COUNT_BITS - 8)); | ||
| 25 | hash[1] = (uint8_t)((bucket << (16 - OT_BUCKET_COUNT_BITS)) & 0xff); | ||
| 26 | } | ||
| 27 | |||
| 28 | static void case_a_hash_names_its_bucket(void) { | ||
| 29 | static const int buckets[] = {0, 1, 2, 17, 511, 512, OT_BUCKET_COUNT - 1}; | ||
| 30 | size_t i; | ||
| 31 | |||
| 32 | test_case("a hash is served by the bucket its leading bits name"); | ||
| 33 | for (i = 0; i < sizeof(buckets) / sizeof(buckets[0]); ++i) { | ||
| 34 | ot_hash hash; | ||
| 35 | ot_vector *by_hash, *by_number; | ||
| 36 | |||
| 37 | hash_for_bucket(hash, buckets[i]); | ||
| 38 | by_hash = mutex_bucket_lock_by_hash(hash); | ||
| 39 | mutex_bucket_unlock_by_hash(hash, 0); | ||
| 21 | 40 | ||
| 22 | test_case("a worker is handed the task of the class it asked for"); | 41 | by_number = mutex_bucket_lock(buckets[i]); |
| 23 | CHECK_INT(mutex_workqueue_pushtask(41, TASK_FULLSCRAPE_TPB_ASCII), 0); | 42 | mutex_bucket_unlock(buckets[i], 0); |
| 24 | asked = TASK_FULLSCRAPE; | ||
| 25 | taskid = mutex_workqueue_poptask(&asked); | ||
| 26 | CHECK(taskid != 0); | ||
| 27 | 43 | ||
| 28 | test_case("and the full task type, not just its class"); | 44 | CHECK(by_hash == by_number); |
| 29 | CHECK_INT(asked, TASK_FULLSCRAPE_TPB_ASCII); | 45 | } |
| 30 | mutex_workqueue_canceltask(41); | ||
| 31 | } | 46 | } |
| 32 | 47 | ||
| 33 | static void case_flags_survive_the_queue(void) { | 48 | static void case_every_bucket_is_its_own(void) { |
| 34 | ot_tasktype asked; | 49 | ot_vector *first, *last, *middle; |
| 35 | 50 | ||
| 36 | test_case("flags outside the class mask are carried through"); | 51 | test_case("no two buckets are the same one"); |
| 37 | CHECK_INT(mutex_workqueue_pushtask(42, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP), 0); | 52 | first = mutex_bucket_lock(0); |
| 38 | asked = TASK_FULLSCRAPE; | 53 | mutex_bucket_unlock(0, 0); |
| 39 | CHECK(mutex_workqueue_poptask(&asked) != 0); | 54 | middle = mutex_bucket_lock(OT_BUCKET_COUNT / 2); |
| 40 | CHECK_INT(asked, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP); | 55 | mutex_bucket_unlock(OT_BUCKET_COUNT / 2, 0); |
| 41 | mutex_workqueue_canceltask(42); | 56 | last = mutex_bucket_lock(OT_BUCKET_COUNT - 1); |
| 57 | mutex_bucket_unlock(OT_BUCKET_COUNT - 1, 0); | ||
| 58 | |||
| 59 | CHECK(first != middle); | ||
| 60 | CHECK(middle != last); | ||
| 61 | CHECK(first != last); | ||
| 42 | } | 62 | } |
| 43 | 63 | ||
| 44 | static void case_classes_do_not_cross(void) { | 64 | static void case_the_count_follows_what_unlock_is_told(void) { |
| 45 | ot_tasktype asked; | 65 | size_t before = mutex_get_torrent_count(); |
| 46 | ot_taskid taskid; | 66 | |
| 67 | test_case("the torrent count moves by what unlocking reports, and not otherwise"); | ||
| 68 | mutex_bucket_lock(3); | ||
| 69 | mutex_bucket_unlock(3, 0); | ||
| 70 | CHECK_INT((int)(mutex_get_torrent_count() - before), 0); | ||
| 47 | 71 | ||
| 48 | test_case("a stats worker passes over a fullscrape task"); | 72 | mutex_bucket_lock(3); |
| 49 | CHECK_INT(mutex_workqueue_pushtask(43, TASK_FULLSCRAPE_TPB_BINARY), 0); | 73 | mutex_bucket_unlock(3, 5); |
| 50 | CHECK_INT(mutex_workqueue_pushtask(44, TASK_STATS_PEERS), 0); | 74 | CHECK_INT((int)(mutex_get_torrent_count() - before), 5); |
| 51 | 75 | ||
| 52 | asked = TASK_STATS; | 76 | mutex_bucket_lock(3); |
| 53 | taskid = mutex_workqueue_poptask(&asked); | 77 | mutex_bucket_unlock(3, -2); |
| 54 | CHECK(taskid != 0); | 78 | CHECK_INT((int)(mutex_get_torrent_count() - before), 3); |
| 55 | CHECK_INT(asked, TASK_STATS_PEERS); | 79 | } |
| 56 | 80 | ||
| 57 | test_case("and leaves it for the one that wants it"); | 81 | static _Atomic int g_other_got_in; |
| 58 | asked = TASK_FULLSCRAPE; | ||
| 59 | taskid = mutex_workqueue_poptask(&asked); | ||
| 60 | CHECK(taskid != 0); | ||
| 61 | CHECK_INT(asked, TASK_FULLSCRAPE_TPB_BINARY); | ||
| 62 | 82 | ||
| 63 | mutex_workqueue_canceltask(43); | 83 | static void *take_a_bucket(void *args) { |
| 64 | mutex_workqueue_canceltask(44); | 84 | int bucket = (int)(intptr_t)args; |
| 85 | |||
| 86 | mutex_bucket_lock(bucket); | ||
| 87 | atomic_store(&g_other_got_in, 1); | ||
| 88 | mutex_bucket_unlock(bucket, 0); | ||
| 89 | return NULL; | ||
| 90 | } | ||
| 91 | |||
| 92 | static int waited_for_entry(int patience_ms) { | ||
| 93 | int waited; | ||
| 94 | |||
| 95 | for (waited = 0; waited < patience_ms && !atomic_load(&g_other_got_in); ++waited) | ||
| 96 | usleep(1000); | ||
| 97 | return atomic_load(&g_other_got_in); | ||
| 65 | } | 98 | } |
| 66 | 99 | ||
| 67 | static void case_every_task_gets_its_own_id(void) { | 100 | static void case_a_held_bucket_keeps_others_out(void) { |
| 68 | ot_tasktype asked; | 101 | pthread_t other; |
| 69 | ot_taskid first, second; | ||
| 70 | 102 | ||
| 71 | test_case("two queued tasks are handed out under different ids"); | 103 | test_case("a second thread waits for the bucket this one holds"); |
| 72 | CHECK_INT(mutex_workqueue_pushtask(45, TASK_FULLSCRAPE), 0); | 104 | atomic_store(&g_other_got_in, 0); |
| 73 | CHECK_INT(mutex_workqueue_pushtask(46, TASK_FULLSCRAPE), 0); | 105 | mutex_bucket_lock(9); |
| 106 | CHECK_INT(pthread_create(&other, NULL, take_a_bucket, (void *)(intptr_t)9), 0); | ||
| 74 | 107 | ||
| 75 | asked = TASK_FULLSCRAPE; | 108 | CHECK_INT(waited_for_entry(200), 0); |
| 76 | first = mutex_workqueue_poptask(&asked); | ||
| 77 | asked = TASK_FULLSCRAPE; | ||
| 78 | second = mutex_workqueue_poptask(&asked); | ||
| 79 | CHECK(first != 0); | ||
| 80 | CHECK(second != 0); | ||
| 81 | CHECK(first != second); | ||
| 82 | 109 | ||
| 83 | mutex_workqueue_canceltask(45); | 110 | mutex_bucket_unlock(9, 0); |
| 84 | mutex_workqueue_canceltask(46); | 111 | CHECK_INT(waited_for_entry(2000), 1); |
| 112 | pthread_join(other, NULL); | ||
| 85 | } | 113 | } |
| 86 | 114 | ||
| 87 | static void case_cancelled_task_is_not_handed_out(void) { | 115 | static void case_another_bucket_is_free_meanwhile(void) { |
| 88 | ot_tasktype asked; | 116 | pthread_t other; |
| 89 | 117 | ||
| 90 | test_case("a cancelled task is gone before any worker sees it"); | 118 | test_case("and a different bucket is not held up by it"); |
| 91 | CHECK_INT(mutex_workqueue_pushtask(47, TASK_FULLSCRAPE_TPB_ASCII_PLUS), 0); | 119 | atomic_store(&g_other_got_in, 0); |
| 92 | CHECK_INT(mutex_workqueue_pushtask(48, TASK_FULLSCRAPE), 0); | 120 | mutex_bucket_lock(9); |
| 93 | mutex_workqueue_canceltask(47); | 121 | CHECK_INT(pthread_create(&other, NULL, take_a_bucket, (void *)(intptr_t)10), 0); |
| 94 | 122 | ||
| 95 | asked = TASK_FULLSCRAPE; | 123 | CHECK_INT(waited_for_entry(2000), 1); |
| 96 | CHECK(mutex_workqueue_poptask(&asked) != 0); | 124 | pthread_join(other, NULL); |
| 97 | CHECK_INT(asked, TASK_FULLSCRAPE); | 125 | mutex_bucket_unlock(9, 0); |
| 98 | mutex_workqueue_canceltask(48); | ||
| 99 | } | 126 | } |
| 100 | 127 | ||
| 101 | void test_mutex(void) { | 128 | void test_mutex(void) { |
| 102 | case_task_goes_to_its_own_class(); | 129 | mutex_init(); |
| 103 | case_flags_survive_the_queue(); | 130 | |
| 104 | case_classes_do_not_cross(); | 131 | case_a_hash_names_its_bucket(); |
| 105 | case_every_task_gets_its_own_id(); | 132 | case_every_bucket_is_its_own(); |
| 106 | case_cancelled_task_is_not_handed_out(); | 133 | case_the_count_follows_what_unlock_is_told(); |
| 134 | case_a_held_bucket_keeps_others_out(); | ||
| 135 | case_another_bucket_is_free_meanwhile(); | ||
| 136 | |||
| 137 | mutex_deinit(); | ||
| 107 | } | 138 | } |
