Skip to content

Commit

Permalink
add mutex test
Browse files Browse the repository at this point in the history
  • Loading branch information
Zzzabiyaka committed Aug 22, 2023
1 parent f0632ed commit de50d6e
Show file tree
Hide file tree
Showing 11 changed files with 377 additions and 31 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/compilation_on_android_ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,11 @@ jobs:
run: bash build.sh --sysroot "$SYSROOT_PATH"
working-directory: ./core/iwasm/libraries/lib-wasi-threads/test/

- name: Build WASI thread stress tests
if: matrix.test_option == '$WASI_TEST_OPTIONS'
run: bash build.sh --sysroot "$SYSROOT_PATH"
working-directory: ./core/iwasm/libraries/lib-wasi-threads/stress_test/

- name: build socket api tests
if: matrix.test_option == '$WASI_TEST_OPTIONS'
run: bash build.sh
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/nightly_run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,11 @@ jobs:
run: bash build.sh --sysroot "$SYSROOT_PATH"
working-directory: ./core/iwasm/libraries/lib-wasi-threads/test/

- name: Build WASI thread stress tests
if: matrix.test_option == '$WASI_TEST_OPTIONS'
run: bash build.sh --sysroot "$SYSROOT_PATH"
working-directory: ./core/iwasm/libraries/lib-wasi-threads/stress_test/

- name: build socket api tests
if: matrix.test_option == '$WASI_TEST_OPTIONS'
run: bash build.sh
Expand Down
65 changes: 65 additions & 0 deletions core/iwasm/libraries/lib-wasi-threads/stress_test/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/bin/bash

#
# Copyright (C) 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#

set -eo pipefail
CC=${CC:=/opt/wasi-sdk/bin/clang}
WAMR_DIR=../../../../..

show_usage() {
echo "Usage: $0 [--sysroot PATH_TO_SYSROOT]"
echo "--sysroot PATH_TO_SYSROOT specify to build with custom sysroot for wasi-libc"
}

while [[ $# -gt 0 ]]; do
key="$1"
case $key in
--sysroot)
sysroot_path="$2"
shift
shift
;;
--help)
show_usage
exit
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done

rm -rf *.wasm
rm -rf *.aot

for test_c in *.c; do
test_wasm="$(basename $test_c .c).wasm"

if [[ -n "$sysroot_path" ]]; then
if [ ! -d "$sysroot_path" ]; then
echo "Directory $sysroot_path doesn't exist. Aborting"
exit 1
fi
sysroot_command="--sysroot $sysroot_path"
fi

echo "Compiling $test_c to $test_wasm"
$CC \
-target wasm32-wasi-threads \
-O2 \
-pthread \
-z stack-size=32768 \
-Wl,--export=__heap_base \
-Wl,--export=__data_end \
-Wl,--shared-memory,--max-memory=1966080 \
-Wl,--export=wasi_thread_start \
-Wl,--export=malloc \
-Wl,--export=free \
-I $WAMR_DIR/samples/wasi-threads/wasm-apps \
$sysroot_command \
$test_c -o $test_wasm
done
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "lib-wasi-threads stress tests"
}
261 changes: 261 additions & 0 deletions core/iwasm/libraries/lib-wasi-threads/stress_test/mutex_common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
/*
* Copyright (C) 2023 Amazon.com Inc. or its affiliates. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/

#ifndef MUTEX_COMMON_H
#define MUTEX_COMMON_H

#include <pthread.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <stdbool.h>

typedef struct {
pthread_mutex_t *mutex;
int *counter;
int limit;
bool is_sleeping;
} MutexCounter;

enum constants {
NUM_ITER = 100000,
NUM_THREADS = 12,
NUM_RETRY = 8,
RETRY_SLEEP_TIME_US = 1000,
};

// This enum defines whether thread should sleep to increase contention
enum sleep_state {
NON_SLEEP = 0,
SLEEP = 1,
};

void
spawn_thread(pthread_t *tid, void *func, void *arg)
{
int status_code = -1;
int timeout_us = RETRY_SLEEP_TIME_US;
for (int tries = 0; status_code != 0 && tries < NUM_RETRY; ++tries) {
status_code = pthread_create(tid, NULL, (void *(*)(void *))func, arg);
assert(status_code == 0 || status_code == EAGAIN);
if (status_code == EAGAIN) {
usleep(timeout_us);
timeout_us *= 2;
}
}

assert(status_code == 0 && "Thread creation should succeed");
}

// We're counting how many times each thread was called using this array
// Main thread is also counted here so we need to make arrays bigger
typedef struct {
int tids[NUM_THREADS + 1];
int calls[NUM_THREADS + 1];
} StatCollector;

StatCollector call_stat;

void
clear_stat()
{
for (int i = 0; i < NUM_THREADS + 1; ++i) {
call_stat.tids[i] = 0;
call_stat.calls[i] = 0;
}
}

void
add_to_stat(int tid)
{
int tid_num = 0;
for (; tid_num < NUM_THREADS + 1 && call_stat.tids[tid_num] != 0;
++tid_num) {
if (call_stat.tids[tid_num] == tid) {
call_stat.calls[tid_num]++;
return;
}
}

assert(tid_num < NUM_THREADS + 1);
call_stat.tids[tid_num] = tid;
call_stat.calls[tid_num] = 1;
}

// This function ensures that every of NUM_THREADS+1 threads number of
// operations is in between [NUM_ITER/NUM_THREADS, NUM_ITER/(NUM_THREADS+2)].
// For 100'000 operations it means that every thread should be called [7142,
// 8333] times. Also, such a simple hash function might be not the ideal one so
// the confidence interval is kept quite wide
void
check_stat()
{
for (int i = 0; i < NUM_THREADS + 1; ++i) {
if (call_stat.calls[i] != 0) {
assert(call_stat.calls[i] < NUM_ITER / NUM_THREADS
&& "Call distribution by TID is out of range");
assert(call_stat.calls[i] > NUM_ITER / (NUM_THREADS + 2)
&& "Call distribution by TID is out of range");
}
}
}

void
print_stat()
{
fprintf(stderr, "Thread calls count by TID\n");
for (int i = 0; i < NUM_THREADS + 1; ++i) {
if (call_stat.tids[i] != 0) {
fprintf(stderr, "TID: %d; Calls: %d\n", call_stat.tids[i],
call_stat.calls[i]);
}
}
}

// xor with a random value usually gives quite a uniform distribution
uint32_t
hash(uint32_t counter)
{
return (counter << 22) ^ (0xA2BC22A); // Random value for hash func
}

// Locking and unlocking the same thread
void
same_thread_test(pthread_mutex_t *mutex)
{
assert(pthread_mutex_lock(mutex) == 0
&& "Main thread should be able to lock a mutex");
assert(pthread_mutex_unlock(mutex) == 0
&& "Main thread should be able to unlock a mutex");
}

void *
inc_until_limit(void *arg)
{
MutexCounter mutex_counter = *(MutexCounter *)(arg);
int sleep_us = 0;
while (!pthread_mutex_lock(mutex_counter.mutex)
&& *mutex_counter.counter < mutex_counter.limit) {
(*mutex_counter.counter)++;
add_to_stat((int)(pthread_self()));
if (mutex_counter.is_sleeping) {
sleep_us = hash(*mutex_counter.counter) % 1000;
}

assert(pthread_mutex_unlock(mutex_counter.mutex) == 0
&& "Should be able to unlock a mutex");
if (mutex_counter.is_sleeping) {
usleep(sleep_us);
}
}

assert(*mutex_counter.counter == mutex_counter.limit);
assert(pthread_mutex_unlock(mutex_counter.mutex) == 0
&& "Should be able to unlock the mutex after test execution");

return NULL;
}

void
non_main_test(pthread_mutex_t *mutex, int num_iter)
{
MutexCounter mutex_counter;
int counter = 0;
mutex_counter.mutex = mutex;
mutex_counter.counter = &counter;
mutex_counter.limit = num_iter;
mutex_counter.is_sleeping = false;

pthread_t tid = 0;
spawn_thread(&tid, inc_until_limit, &mutex_counter);

assert(tid != 0 && "TID can't be 0 after successful thread creation");
pthread_join(tid, NULL);
assert(*mutex_counter.counter == mutex_counter.limit);
}

void
main_non_main_test(pthread_mutex_t *mutex, int num_iter)
{
MutexCounter mutex_counter;
int counter = 0;
mutex_counter.mutex = mutex;
mutex_counter.counter = &counter;
mutex_counter.limit = num_iter;
mutex_counter.is_sleeping = false;

pthread_t tid = 0;
spawn_thread(&tid, inc_until_limit, &mutex_counter);

assert(tid != 0 && "TID can't be 0 after successful thread creation");
inc_until_limit(&mutex_counter);
assert(pthread_join(tid, NULL) == 0
&& "Thread should be joined without errors");
;
assert(*mutex_counter.counter == mutex_counter.limit);
}

void
concurrent_inc_test(pthread_mutex_t *mutex, int num_iter, int threads_num,
bool is_sleeping)
{
MutexCounter mutex_counter;
int counter = 0;
mutex_counter.mutex = mutex;
mutex_counter.counter = &counter;
mutex_counter.limit = num_iter;
mutex_counter.is_sleeping = is_sleeping;

pthread_t tids[threads_num];
for (int i = 0; i < threads_num; ++i) {
spawn_thread(&tids[i], inc_until_limit, &mutex_counter);
}

inc_until_limit(&mutex_counter);

for (int i = 0; i < threads_num; ++i) {
assert(pthread_join(tids[i], NULL) == 0
&& "Thread should be joined without errors");
;
}
}

// This function just runs all the tests described above
void
run_common_tests(pthread_mutex_t *mutex)
{
fprintf(stderr, "Starting main_thread test\n");
clear_stat();
for (int i = 0; i < NUM_ITER; ++i) {
same_thread_test(mutex);
}
fprintf(stderr, "Finished main_thread test\n");

fprintf(stderr, "Starting non_main test\n");
clear_stat();
non_main_test(mutex, NUM_ITER);
fprintf(stderr, "Finished non_main test\n");

fprintf(stderr, "Starting main_non_main test\n");
clear_stat();
main_non_main_test(mutex, NUM_ITER);
fprintf(stderr, "Finished main_non_main test\n");

fprintf(stderr, "Starting concurrent_inc_sleep test\n");
clear_stat();
concurrent_inc_test(mutex, NUM_ITER, NUM_THREADS, SLEEP);
print_stat();
fprintf(stderr, "Finished concurrent_inc sleep test\n");

fprintf(stderr, "Starting concurrent_inc_non_sleep test\n");
check_stat();
clear_stat();
concurrent_inc_test(mutex, NUM_ITER, NUM_THREADS, NON_SLEEP);
print_stat();
fprintf(stderr, "Finished concurrent_inc sleep test\n");
}

#endif // MUTEX_COMMON_H
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include <pthread.h>
#include <errno.h>
#include "mutex_common.h"

int
main()
{
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);

run_common_tests(&mutex);

fprintf(stderr, "Normal mutex test is completed\n");
pthread_mutex_destroy(&mutex);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
enum CONSTANTS {
NUM_ITER = 100000,
NUM_RETRY = 8,
MAX_NUM_THREADS = 8,
MAX_NUM_THREADS = 12,
RETRY_SLEEP_TIME_US = 2000,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
enum CONSTANTS {
NUM_ITER = 200000,
NUM_RETRY = 8,
MAX_NUM_THREADS = 8,
MAX_NUM_THREADS = 12,
RETRY_SLEEP_TIME_US = 4000,
SECOND = 1000 * 1000 * 1000
};
Expand Down
Loading

0 comments on commit de50d6e

Please sign in to comment.