Azure-Kinect-Sensor-SDK/tests/rwlock/rwlock.cpp

268 строки
7.7 KiB
C++

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include <utcommon.h>
#include <k4ainternal/rwlock.h>
#include <k4ainternal/common.h>
#include <gtest/gtest.h>
#include <azure_c_shared_utility/threadapi.h>
int main(int argc, char **argv)
{
return k4a_test_common_main(argc, argv);
}
#define GTEST_LOG_INFO std::cout << "[ INFO ] "
TEST(rwlock_ft, rwlock_init)
{
k4a_rwlock_t lock;
rwlock_init(&lock);
rwlock_deinit(&lock);
}
typedef struct
{
k4a_rwlock_t test_lock;
// Number of times reader 1 gets the lock
volatile int reader1_count;
// Number of times reader 2 gets the lock
volatile int reader2_count;
// Number of times reader 2 fails to get the lock
volatile int reader2_fail_count;
// Number of times writer 1 gets the lock
volatile int writer1_count;
// Number of times writer 2 gets the lock
volatile int writer2_count;
// Number of times writer 2 fails to get the lock
volatile int writer2_fail_count;
volatile bool run_test;
} threaded_rwlock_test_context_t;
static void thread_reader1(threaded_rwlock_test_context_t *context)
{
while (context->run_test)
{
rwlock_acquire_read(&context->test_lock);
int writer1 = context->writer1_count;
int writer2 = context->writer2_count;
context->reader1_count++;
// No writers should be able to access the lock while
// a reader has the lock
EXPECT_EQ(writer1, context->writer1_count);
EXPECT_EQ(writer2, context->writer2_count);
ThreadAPI_Sleep(1);
rwlock_release_read(&context->test_lock);
ThreadAPI_Sleep(1);
}
}
static int thread_reader1_threadproc(void *ctx)
{
thread_reader1((threaded_rwlock_test_context_t *)ctx);
return 0;
}
static void thread_reader2(threaded_rwlock_test_context_t *context)
{
while (context->run_test)
{
if (rwlock_try_acquire_read(&context->test_lock))
{
int writer1 = context->writer1_count;
int writer2 = context->writer2_count;
context->reader2_count++;
// No writers should be able to access the lock while
// a reader has the lock
EXPECT_EQ(writer1, context->writer1_count);
EXPECT_EQ(writer2, context->writer2_count);
ThreadAPI_Sleep(2);
rwlock_release_read(&context->test_lock);
}
else
{
context->reader2_fail_count++;
}
ThreadAPI_Sleep(1);
}
}
static int thread_reader2_threadproc(void *ctx)
{
thread_reader2((threaded_rwlock_test_context_t *)ctx);
return 0;
}
static void thread_writer1(threaded_rwlock_test_context_t *context)
{
while (context->run_test)
{
rwlock_acquire_write(&context->test_lock);
// Capture the current counts
int reader1 = context->reader1_count;
int reader2 = context->reader2_count;
int reader2_fail = context->reader2_fail_count;
int writer2 = context->writer2_count;
int writer2_fail = context->writer2_fail_count;
context->writer1_count++;
// Wait a moment to allow other threads to run
ThreadAPI_Sleep(100);
// Wait for the other threads to hit contention
while (reader2_fail == context->reader2_fail_count || writer2_fail == context->writer2_fail_count)
{
ThreadAPI_Sleep(10);
// Don't wait if we reach the end of the test
if (!context->run_test)
{
break;
}
}
// No other thread should have the lock, the values should not change
EXPECT_EQ(reader1, context->reader1_count);
EXPECT_EQ(reader2, context->reader2_count);
EXPECT_EQ(writer2, context->writer2_count);
// If we reached the end of the test, we may not have waited long enough
// for these to be true
if (context->run_test)
{
// Since we have held the lock for some time, we expect reader 2 and writer 2
// to have failed to acquire the lock while we have held it
EXPECT_NE(reader2_fail, context->reader2_fail_count);
EXPECT_NE(writer2_fail, context->writer2_fail_count);
}
rwlock_release_write(&context->test_lock);
ThreadAPI_Sleep(100);
}
}
static int thread_writer1_threadproc(void *ctx)
{
thread_writer1((threaded_rwlock_test_context_t *)ctx);
return 0;
}
static void thread_writer2(threaded_rwlock_test_context_t *context)
{
while (context->run_test)
{
if (rwlock_try_acquire_write(&context->test_lock))
{
int reader1 = context->reader1_count;
int reader2 = context->reader2_count;
int writer1 = context->writer1_count;
context->writer2_count++;
// The other writer, nor any readers should have the lock while we do
EXPECT_EQ(reader1, context->reader1_count);
EXPECT_EQ(reader2, context->reader2_count);
EXPECT_EQ(writer1, context->writer1_count);
ThreadAPI_Sleep(3);
rwlock_release_write(&context->test_lock);
}
else
{
context->writer2_fail_count++;
}
ThreadAPI_Sleep(2);
}
}
static int thread_writer2_threadproc(void *ctx)
{
thread_writer2((threaded_rwlock_test_context_t *)ctx);
return 0;
}
TEST(rwlock_ft, rwlock_threaded_test)
{
/*
This test creates four threads which have contention over a rwlock.
Reader1:
Tries to acquire a read lock every 2 ms
Reader2:
Uses the try acquire method to try getting the read lock every 3 ms
Writer1:
Gets and releases the write lock with a delay. It holds the lock for a while as well.
Writer2:
Uses the try acquire method to try getting the write lock every 5 ms
Each thread checks for the known invariants of the behavior of the other threads. E.g. a read
lock can't be acquired while the write lock is held.
*/
THREAD_HANDLE r1, r2, w1, w2;
threaded_rwlock_test_context_t test_context;
memset(&test_context, 0, sizeof(test_context));
test_context.run_test = true;
rwlock_init(&test_context.test_lock);
ASSERT_EQ(THREADAPI_OK, ThreadAPI_Create(&r1, thread_reader1_threadproc, &test_context));
ASSERT_EQ(THREADAPI_OK, ThreadAPI_Create(&r2, thread_reader2_threadproc, &test_context));
ASSERT_EQ(THREADAPI_OK, ThreadAPI_Create(&w1, thread_writer1_threadproc, &test_context));
ASSERT_EQ(THREADAPI_OK, ThreadAPI_Create(&w2, thread_writer2_threadproc, &test_context));
ThreadAPI_Sleep(5000);
test_context.run_test = false;
ThreadAPI_Join(r1, NULL);
ThreadAPI_Join(r2, NULL);
ThreadAPI_Join(w1, NULL);
ThreadAPI_Join(w2, NULL);
rwlock_deinit(&test_context.test_lock);
// Ensure all the threads have acquired the lock and hit failure contention
ASSERT_NE(0, test_context.reader1_count);
ASSERT_NE(0, test_context.reader2_count);
ASSERT_NE(0, test_context.reader2_fail_count);
ASSERT_NE(0, test_context.writer1_count);
ASSERT_NE(0, test_context.writer2_count);
ASSERT_NE(0, test_context.writer2_fail_count);
GTEST_LOG_INFO << "reader1_count " << test_context.reader1_count << std::endl;
GTEST_LOG_INFO << "reader2_count " << test_context.reader2_count << std::endl;
GTEST_LOG_INFO << "reader2_fail_count " << test_context.reader2_fail_count << std::endl;
GTEST_LOG_INFO << "writer1_count " << test_context.writer1_count << std::endl;
GTEST_LOG_INFO << "writer2_count " << test_context.writer2_count << std::endl;
GTEST_LOG_INFO << "writer2_fail_count " << test_context.writer2_fail_count << std::endl;
}