|
|
|
/*
|
|
|
|
* Copyright 2015 John Esmet.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
|
* SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
#include <pthread.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include <ck_epoch.h>
|
|
|
|
|
|
|
|
#include "../../common.h"
|
|
|
|
|
|
|
|
static ck_epoch_t epc;
|
|
|
|
static ck_epoch_record_t record, record2;
|
|
|
|
static unsigned int cleanup_calls;
|
|
|
|
|
|
|
|
static void
|
|
|
|
setup_test(void)
|
|
|
|
{
|
|
|
|
|
|
|
|
ck_epoch_init(&epc);
|
|
|
|
ck_epoch_register(&epc, &record, NULL);
|
|
|
|
ck_epoch_register(&epc, &record2, NULL);
|
|
|
|
cleanup_calls = 0;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
teardown_test(void)
|
|
|
|
{
|
|
|
|
|
|
|
|
memset(&epc, 0, sizeof(ck_epoch_t));
|
|
|
|
ck_epoch_unregister(&record);
|
|
|
|
memset(&record, 0, sizeof(ck_epoch_record_t));
|
|
|
|
memset(&record2, 0, sizeof(ck_epoch_record_t));
|
|
|
|
cleanup_calls = 0;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cleanup(ck_epoch_entry_t *e)
|
|
|
|
{
|
|
|
|
(void) e;
|
|
|
|
|
|
|
|
cleanup_calls++;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
test_simple_read_section(void)
|
|
|
|
{
|
|
|
|
ck_epoch_entry_t entry;
|
|
|
|
ck_epoch_section_t section;
|
|
|
|
|
|
|
|
memset(&entry, 0, sizeof(ck_epoch_entry_t));
|
|
|
|
setup_test();
|
|
|
|
|
|
|
|
ck_epoch_begin(&record, §ion);
|
|
|
|
ck_epoch_call(&record, &entry, cleanup);
|
|
|
|
assert(cleanup_calls == 0);
|
|
|
|
if (ck_epoch_end(&record, §ion) == false)
|
|
|
|
ck_error("expected no more sections");
|
|
|
|
ck_epoch_barrier(&record);
|
|
|
|
assert(cleanup_calls == 1);
|
|
|
|
|
|
|
|
teardown_test();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
test_nested_read_section(void)
|
|
|
|
{
|
|
|
|
ck_epoch_entry_t entry1, entry2;
|
|
|
|
ck_epoch_section_t section1, section2;
|
|
|
|
|
|
|
|
memset(&entry1, 0, sizeof(ck_epoch_entry_t));
|
|
|
|
memset(&entry2, 0, sizeof(ck_epoch_entry_t));
|
|
|
|
setup_test();
|
|
|
|
|
|
|
|
ck_epoch_begin(&record, §ion1);
|
|
|
|
ck_epoch_call(&record, &entry1, cleanup);
|
|
|
|
assert(cleanup_calls == 0);
|
|
|
|
|
|
|
|
ck_epoch_begin(&record, §ion2);
|
|
|
|
ck_epoch_call(&record, &entry2, cleanup);
|
|
|
|
assert(cleanup_calls == 0);
|
|
|
|
|
|
|
|
ck_epoch_end(&record, §ion2);
|
|
|
|
assert(cleanup_calls == 0);
|
|
|
|
|
|
|
|
ck_epoch_end(&record, §ion1);
|
|
|
|
assert(cleanup_calls == 0);
|
|
|
|
|
|
|
|
ck_epoch_barrier(&record);
|
|
|
|
assert(cleanup_calls == 2);
|
|
|
|
|
|
|
|
teardown_test();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct obj {
|
|
|
|
ck_epoch_entry_t entry;
|
|
|
|
unsigned int destroyed;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void *
|
|
|
|
barrier_work(void *arg)
|
|
|
|
{
|
|
|
|
unsigned int *run;
|
|
|
|
|
|
|
|
run = (unsigned int *)arg;
|
|
|
|
while (ck_pr_load_uint(run) != 0) {
|
|
|
|
/*
|
|
|
|
* Need to use record2, as record is local
|
|
|
|
* to the test thread.
|
|
|
|
*/
|
|
|
|
ck_epoch_barrier(&record2);
|
|
|
|
usleep(5 * 1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *
|
|
|
|
reader_work(void *arg)
|
|
|
|
{
|
|
|
|
ck_epoch_record_t local_record;
|
|
|
|
ck_epoch_section_t section;
|
|
|
|
struct obj *o;
|
|
|
|
|
|
|
|
ck_epoch_register(&epc, &local_record, NULL);
|
|
|
|
|
|
|
|
o = (struct obj *)arg;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Begin a read section. The calling thread has an open read section,
|
|
|
|
* so the object should not be destroyed for the lifetime of this
|
|
|
|
* thread.
|
|
|
|
*/
|
|
|
|
ck_epoch_begin(&local_record, §ion);
|
|
|
|
usleep((common_rand() % 100) * 1000);
|
|
|
|
assert(ck_pr_load_uint(&o->destroyed) == 0);
|
|
|
|
ck_epoch_end(&local_record, §ion);
|
|
|
|
|
|
|
|
ck_epoch_unregister(&local_record);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
obj_destroy(ck_epoch_entry_t *e)
|
|
|
|
{
|
|
|
|
struct obj *o;
|
|
|
|
|
|
|
|
o = (struct obj *)e;
|
|
|
|
ck_pr_fas_uint(&o->destroyed, 1);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
test_single_reader_with_barrier_thread(void)
|
|
|
|
{
|
|
|
|
const int num_sections = 10;
|
|
|
|
struct obj o;
|
|
|
|
unsigned int run;
|
|
|
|
pthread_t thread;
|
|
|
|
ck_epoch_section_t sections[num_sections];
|
|
|
|
int shuffled[num_sections];
|
|
|
|
|
|
|
|
run = 1;
|
|
|
|
memset(&o, 0, sizeof(struct obj));
|
|
|
|
common_srand(time(NULL));
|
|
|
|
setup_test();
|
|
|
|
|
|
|
|
if (pthread_create(&thread, NULL, barrier_work, &run) != 0) {
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Start a bunch of sections. */
|
|
|
|
for (int i = 0; i < num_sections; i++) {
|
|
|
|
ck_epoch_begin(&record, §ions[i]);
|
|
|
|
shuffled[i] = i;
|
|
|
|
if (i == num_sections / 2) {
|
|
|
|
usleep(1 * 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Generate a shuffle. */
|
|
|
|
for (int i = num_sections - 1; i >= 0; i--) {
|
|
|
|
int k = common_rand() % (i + 1);
|
|
|
|
int tmp = shuffled[k];
|
|
|
|
shuffled[k] = shuffled[i];
|
|
|
|
shuffled[i] = tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
ck_epoch_call(&record, &o.entry, obj_destroy);
|
|
|
|
|
|
|
|
/* Close the sections in shuffle-order. */
|
|
|
|
for (int i = 0; i < num_sections; i++) {
|
|
|
|
ck_epoch_end(&record, §ions[shuffled[i]]);
|
|
|
|
if (i != num_sections - 1) {
|
|
|
|
assert(ck_pr_load_uint(&o.destroyed) == 0);
|
|
|
|
usleep(3 * 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ck_pr_store_uint(&run, 0);
|
|
|
|
if (pthread_join(thread, NULL) != 0) {
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
ck_epoch_barrier(&record);
|
|
|
|
assert(ck_pr_load_uint(&o.destroyed) == 1);
|
|
|
|
|
|
|
|
teardown_test();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
test_multiple_readers_with_barrier_thread(void)
|
|
|
|
{
|
|
|
|
const int num_readers = 10;
|
|
|
|
struct obj o;
|
|
|
|
unsigned int run;
|
|
|
|
ck_epoch_section_t section;
|
|
|
|
pthread_t threads[num_readers + 1];
|
|
|
|
|
|
|
|
run = 1;
|
|
|
|
memset(&o, 0, sizeof(struct obj));
|
|
|
|
memset(§ion, 0, sizeof(ck_epoch_section_t));
|
|
|
|
common_srand(time(NULL));
|
|
|
|
setup_test();
|
|
|
|
|
|
|
|
/* Create a thread to call barrier() while we create reader threads.
|
|
|
|
* Each barrier will attempt to move the global epoch forward so
|
|
|
|
* it will make the read section code coverage more interesting. */
|
|
|
|
if (pthread_create(&threads[num_readers], NULL,
|
|
|
|
barrier_work, &run) != 0) {
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
ck_epoch_begin(&record, §ion);
|
|
|
|
ck_epoch_call(&record, &o.entry, obj_destroy);
|
|
|
|
|
|
|
|
for (int i = 0; i < num_readers; i++) {
|
|
|
|
if (pthread_create(&threads[i], NULL, reader_work, &o) != 0) {
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ck_epoch_end(&record, §ion);
|
|
|
|
|
|
|
|
ck_pr_store_uint(&run, 0);
|
|
|
|
if (pthread_join(threads[num_readers], NULL) != 0) {
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* After the barrier, the object should be destroyed and readers
|
|
|
|
* should return. */
|
|
|
|
for (int i = 0; i < num_readers; i++) {
|
|
|
|
if (pthread_join(threads[i], NULL) != 0) {
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
teardown_test();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
main(void)
|
|
|
|
{
|
|
|
|
|
|
|
|
test_simple_read_section();
|
|
|
|
test_nested_read_section();
|
|
|
|
test_single_reader_with_barrier_thread();
|
|
|
|
test_multiple_readers_with_barrier_thread();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|