| /* Licensed to the Apache Software Foundation (ASF) under one or more | |
| * contributor license agreements. See the NOTICE file distributed with | |
| * this work for additional information regarding copyright ownership. | |
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |
| * (the "License"); you may not use this file except in compliance with | |
| * the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "apr_thread_proc.h" | |
| #include "apr_file_io.h" | |
| #include "apr_thread_mutex.h" | |
| #include "apr_thread_rwlock.h" | |
| #include "apr_thread_cond.h" | |
| #include "apr_errno.h" | |
| #include "apr_general.h" | |
| #include "apr_getopt.h" | |
| #include "apr_atomic.h" | |
| #include "testutil.h" | |
| #if APR_HAS_THREADS | |
| #define MAX_ITER 40000 | |
| #define MAX_COUNTER 100000 | |
| #define MAX_RETRY 5 | |
| static void *APR_THREAD_FUNC thread_rwlock_func(apr_thread_t *thd, void *data); | |
| static void *APR_THREAD_FUNC thread_mutex_function(apr_thread_t *thd, void *data); | |
| static void *APR_THREAD_FUNC thread_mutex_sleep_function(apr_thread_t *thd, void *data); | |
| static void *APR_THREAD_FUNC thread_cond_producer(apr_thread_t *thd, void *data); | |
| static void *APR_THREAD_FUNC thread_cond_consumer(apr_thread_t *thd, void *data); | |
| static apr_thread_mutex_t *thread_mutex; | |
| static apr_thread_rwlock_t *rwlock; | |
| static int i = 0, x = 0; | |
| static int buff[MAX_COUNTER]; | |
| struct { | |
| apr_thread_mutex_t *mutex; | |
| int nput; | |
| int nval; | |
| } put; | |
| struct { | |
| apr_thread_mutex_t *mutex; | |
| apr_thread_cond_t *cond; | |
| int nready; | |
| } nready; | |
| static apr_thread_mutex_t *timeout_mutex; | |
| static apr_thread_cond_t *timeout_cond; | |
| static void *APR_THREAD_FUNC thread_rwlock_func(apr_thread_t *thd, void *data) | |
| { | |
| int exitLoop = 1; | |
| while (1) | |
| { | |
| apr_thread_rwlock_rdlock(rwlock); | |
| if (i == MAX_ITER) | |
| exitLoop = 0; | |
| apr_thread_rwlock_unlock(rwlock); | |
| if (!exitLoop) | |
| break; | |
| apr_thread_rwlock_wrlock(rwlock); | |
| if (i != MAX_ITER) | |
| { | |
| i++; | |
| x++; | |
| } | |
| apr_thread_rwlock_unlock(rwlock); | |
| } | |
| return NULL; | |
| } | |
| static void *APR_THREAD_FUNC thread_mutex_function(apr_thread_t *thd, void *data) | |
| { | |
| int exitLoop = 1; | |
| /* slight delay to allow things to settle */ | |
| apr_sleep (1); | |
| while (1) { | |
| apr_status_t rv; | |
| if (data) { | |
| rv = apr_thread_mutex_timedlock(thread_mutex, *(apr_interval_time_t *)data); | |
| } | |
| else { | |
| rv = apr_thread_mutex_lock(thread_mutex); | |
| } | |
| if (rv != APR_SUCCESS) { | |
| fprintf(stderr, "thread_mutex_function: failed locking mutex\n"); | |
| apr_thread_exit(thd, rv); | |
| break; | |
| } | |
| if (i == MAX_ITER) | |
| exitLoop = 0; | |
| else | |
| { | |
| i++; | |
| x++; | |
| } | |
| rv = apr_thread_mutex_unlock(thread_mutex); | |
| if (rv != APR_SUCCESS) { | |
| fprintf(stderr, "thread_mutex_function: failed unlocking mutex\n"); | |
| apr_thread_exit(thd, rv); | |
| break; | |
| } | |
| if (!exitLoop) | |
| break; | |
| } | |
| apr_thread_exit(thd, APR_SUCCESS); | |
| return NULL; | |
| } | |
| /* Sleepy-loop until f_ value matches val: */ | |
| #define wait_for_flag(f_, val) while (apr_atomic_read32(&(f_)) != val) apr_sleep(100000) | |
| /* Helper function. Passed (apr_uint32_t *) flag as data, sets flag | |
| * to one, locks the timeout_mutex, waits for *flag to be set to zero | |
| * and terminates. The co-ordination could also be done via mutexes | |
| * but since we're timedlocking timeout_mutex it would look like a | |
| * deadlock to a mutex implementation which detects deadlocks. */ | |
| static void *APR_THREAD_FUNC thread_mutex_sleep_function(apr_thread_t *thd, void *data) | |
| { | |
| apr_uint32_t *flag = data; | |
| apr_status_t rv; | |
| rv = apr_thread_mutex_lock(timeout_mutex); | |
| if (rv) { | |
| fprintf(stderr, "testlock: failed to lock timeout mutex, errno %d\n", rv); | |
| apr_thread_exit(thd, rv); | |
| } | |
| apr_atomic_set32(flag, 1); | |
| wait_for_flag(*flag, 0); | |
| rv = apr_thread_mutex_unlock(timeout_mutex); | |
| apr_thread_exit(thd, APR_SUCCESS); | |
| return NULL; | |
| } | |
| static void *APR_THREAD_FUNC thread_cond_producer(apr_thread_t *thd, void *data) | |
| { | |
| for (;;) { | |
| apr_thread_mutex_lock(put.mutex); | |
| if (put.nput >= MAX_COUNTER) { | |
| apr_thread_mutex_unlock(put.mutex); | |
| return NULL; | |
| } | |
| buff[put.nput] = put.nval; | |
| put.nput++; | |
| put.nval++; | |
| apr_thread_mutex_unlock(put.mutex); | |
| apr_thread_mutex_lock(nready.mutex); | |
| if (nready.nready == 0) | |
| apr_thread_cond_signal(nready.cond); | |
| nready.nready++; | |
| apr_thread_mutex_unlock(nready.mutex); | |
| *((int *) data) += 1; | |
| } | |
| return NULL; | |
| } | |
| static void *APR_THREAD_FUNC thread_cond_consumer(apr_thread_t *thd, void *data) | |
| { | |
| int i; | |
| for (i = 0; i < MAX_COUNTER; i++) { | |
| apr_thread_mutex_lock(nready.mutex); | |
| while (nready.nready == 0) | |
| apr_thread_cond_wait(nready.cond, nready.mutex); | |
| nready.nready--; | |
| apr_thread_mutex_unlock(nready.mutex); | |
| if (buff[i] != i) | |
| printf("buff[%d] = %d\n", i, buff[i]); | |
| } | |
| return NULL; | |
| } | |
| #define JOIN_WITH_SUCCESS(tc_, thd_) do { apr_status_t rv__thd; \ | |
| APR_ASSERT_SUCCESS(tc_, "join thread", apr_thread_join(&rv__thd, thd_)); \ | |
| APR_ASSERT_SUCCESS(tc_, "spawned thread terminated successfully", rv__thd); \ | |
| } while (0) | |
| static void test_thread_mutex(abts_case *tc, void *data) | |
| { | |
| apr_thread_t *t1, *t2, *t3, *t4; | |
| apr_status_t s1, s2, s3, s4; | |
| s1 = apr_thread_mutex_create(&thread_mutex, APR_THREAD_MUTEX_DEFAULT, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s1); | |
| ABTS_PTR_NOTNULL(tc, thread_mutex); | |
| i = 0; | |
| x = 0; | |
| s1 = apr_thread_create(&t1, NULL, thread_mutex_function, NULL, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s1); | |
| s2 = apr_thread_create(&t2, NULL, thread_mutex_function, NULL, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s2); | |
| s3 = apr_thread_create(&t3, NULL, thread_mutex_function, NULL, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s3); | |
| s4 = apr_thread_create(&t4, NULL, thread_mutex_function, NULL, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s4); | |
| JOIN_WITH_SUCCESS(tc, t1); | |
| JOIN_WITH_SUCCESS(tc, t2); | |
| JOIN_WITH_SUCCESS(tc, t3); | |
| JOIN_WITH_SUCCESS(tc, t4); | |
| ABTS_INT_EQUAL(tc, MAX_ITER, x); | |
| } | |
| static void test_thread_timedmutex(abts_case *tc, void *data) | |
| { | |
| apr_thread_t *t1, *t2, *t3, *t4; | |
| apr_status_t s1, s2, s3, s4; | |
| apr_interval_time_t timeout; | |
| s1 = apr_thread_mutex_create(&thread_mutex, APR_THREAD_MUTEX_TIMED, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s1); | |
| ABTS_PTR_NOTNULL(tc, thread_mutex); | |
| i = 0; | |
| x = 0; | |
| timeout = apr_time_from_sec(5); | |
| s1 = apr_thread_create(&t1, NULL, thread_mutex_function, &timeout, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s1); | |
| s2 = apr_thread_create(&t2, NULL, thread_mutex_function, &timeout, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s2); | |
| s3 = apr_thread_create(&t3, NULL, thread_mutex_function, &timeout, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s3); | |
| s4 = apr_thread_create(&t4, NULL, thread_mutex_function, &timeout, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s4); | |
| JOIN_WITH_SUCCESS(tc, t1); | |
| JOIN_WITH_SUCCESS(tc, t2); | |
| JOIN_WITH_SUCCESS(tc, t3); | |
| JOIN_WITH_SUCCESS(tc, t4); | |
| ABTS_INT_EQUAL(tc, MAX_ITER, x); | |
| } | |
| static void test_thread_rwlock(abts_case *tc, void *data) | |
| { | |
| apr_thread_t *t1, *t2, *t3, *t4; | |
| apr_status_t s1, s2, s3, s4; | |
| s1 = apr_thread_rwlock_create(&rwlock, p); | |
| if (s1 == APR_ENOTIMPL) { | |
| ABTS_NOT_IMPL(tc, "rwlocks not implemented"); | |
| return; | |
| } | |
| APR_ASSERT_SUCCESS(tc, "rwlock_create", s1); | |
| ABTS_PTR_NOTNULL(tc, rwlock); | |
| i = 0; | |
| x = 0; | |
| s1 = apr_thread_create(&t1, NULL, thread_rwlock_func, NULL, p); | |
| APR_ASSERT_SUCCESS(tc, "create thread 1", s1); | |
| s2 = apr_thread_create(&t2, NULL, thread_rwlock_func, NULL, p); | |
| APR_ASSERT_SUCCESS(tc, "create thread 2", s2); | |
| s3 = apr_thread_create(&t3, NULL, thread_rwlock_func, NULL, p); | |
| APR_ASSERT_SUCCESS(tc, "create thread 3", s3); | |
| s4 = apr_thread_create(&t4, NULL, thread_rwlock_func, NULL, p); | |
| APR_ASSERT_SUCCESS(tc, "create thread 4", s4); | |
| apr_thread_join(&s1, t1); | |
| apr_thread_join(&s2, t2); | |
| apr_thread_join(&s3, t3); | |
| apr_thread_join(&s4, t4); | |
| ABTS_INT_EQUAL(tc, MAX_ITER, x); | |
| apr_thread_rwlock_destroy(rwlock); | |
| } | |
| static void test_cond(abts_case *tc, void *data) | |
| { | |
| apr_thread_t *p1, *p2, *p3, *p4, *c1; | |
| apr_status_t s0, s1, s2, s3, s4; | |
| int count1, count2, count3, count4; | |
| int sum; | |
| APR_ASSERT_SUCCESS(tc, "create put mutex", | |
| apr_thread_mutex_create(&put.mutex, | |
| APR_THREAD_MUTEX_DEFAULT, p)); | |
| ABTS_PTR_NOTNULL(tc, put.mutex); | |
| APR_ASSERT_SUCCESS(tc, "create nready mutex", | |
| apr_thread_mutex_create(&nready.mutex, | |
| APR_THREAD_MUTEX_DEFAULT, p)); | |
| ABTS_PTR_NOTNULL(tc, nready.mutex); | |
| APR_ASSERT_SUCCESS(tc, "create condvar", | |
| apr_thread_cond_create(&nready.cond, p)); | |
| ABTS_PTR_NOTNULL(tc, nready.cond); | |
| count1 = count2 = count3 = count4 = 0; | |
| put.nput = put.nval = 0; | |
| nready.nready = 0; | |
| i = 0; | |
| x = 0; | |
| s0 = apr_thread_create(&p1, NULL, thread_cond_producer, &count1, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s0); | |
| s1 = apr_thread_create(&p2, NULL, thread_cond_producer, &count2, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s1); | |
| s2 = apr_thread_create(&p3, NULL, thread_cond_producer, &count3, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s2); | |
| s3 = apr_thread_create(&p4, NULL, thread_cond_producer, &count4, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s3); | |
| s4 = apr_thread_create(&c1, NULL, thread_cond_consumer, NULL, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s4); | |
| apr_thread_join(&s0, p1); | |
| apr_thread_join(&s1, p2); | |
| apr_thread_join(&s2, p3); | |
| apr_thread_join(&s3, p4); | |
| apr_thread_join(&s4, c1); | |
| APR_ASSERT_SUCCESS(tc, "destroy condvar", | |
| apr_thread_cond_destroy(nready.cond)); | |
| sum = count1 + count2 + count3 + count4; | |
| /* | |
| printf("count1 = %d count2 = %d count3 = %d count4 = %d\n", | |
| count1, count2, count3, count4); | |
| */ | |
| ABTS_INT_EQUAL(tc, MAX_COUNTER, sum); | |
| } | |
| static void test_timeoutcond(abts_case *tc, void *data) | |
| { | |
| apr_status_t s; | |
| apr_interval_time_t timeout; | |
| apr_time_t begin, end; | |
| int i; | |
| s = apr_thread_mutex_create(&timeout_mutex, APR_THREAD_MUTEX_DEFAULT, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s); | |
| ABTS_PTR_NOTNULL(tc, timeout_mutex); | |
| s = apr_thread_cond_create(&timeout_cond, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s); | |
| ABTS_PTR_NOTNULL(tc, timeout_cond); | |
| timeout = apr_time_from_sec(5); | |
| for (i = 0; i < MAX_RETRY; i++) { | |
| apr_thread_mutex_lock(timeout_mutex); | |
| begin = apr_time_now(); | |
| s = apr_thread_cond_timedwait(timeout_cond, timeout_mutex, timeout); | |
| end = apr_time_now(); | |
| apr_thread_mutex_unlock(timeout_mutex); | |
| if (s != APR_SUCCESS && !APR_STATUS_IS_TIMEUP(s)) { | |
| continue; | |
| } | |
| ABTS_INT_EQUAL(tc, 1, APR_STATUS_IS_TIMEUP(s)); | |
| ABTS_ASSERT(tc, "Timer returned too late", end - begin - timeout < 500000); | |
| break; | |
| } | |
| ABTS_ASSERT(tc, "Too many retries", i < MAX_RETRY); | |
| APR_ASSERT_SUCCESS(tc, "Unable to destroy the conditional", | |
| apr_thread_cond_destroy(timeout_cond)); | |
| } | |
| /* Test whether _timedlock times out appropriately. Since | |
| * double-locking a non-recursive mutex has undefined behaviour, and | |
| * double-locking a recursive mutex succeeds immediately, a thread is | |
| * spawned to hold the lock while this thread tests whether _timedlock | |
| * times out. */ | |
| static void test_timeoutmutex(abts_case *tc, void *data) | |
| { | |
| apr_status_t s; | |
| apr_interval_time_t timeout; | |
| apr_time_t begin, end; | |
| apr_thread_t *th; | |
| apr_uint32_t flag = 0; | |
| int i; | |
| s = apr_thread_mutex_create(&timeout_mutex, | |
| APR_THREAD_MUTEX_TIMED | | |
| APR_THREAD_MUTEX_UNNESTED, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s); | |
| ABTS_PTR_NOTNULL(tc, timeout_mutex); | |
| s = apr_thread_create(&th, NULL, thread_mutex_sleep_function, &flag, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, s); | |
| wait_for_flag(flag, 1); /* the thread will set flag to 1 once the | |
| * timeout_mutex is locked. */ | |
| timeout = apr_time_from_sec(5); | |
| for (i = 0; i < MAX_RETRY; i++) { | |
| begin = apr_time_now(); | |
| s = apr_thread_mutex_timedlock(timeout_mutex, timeout); | |
| end = apr_time_now(); | |
| if (s != APR_SUCCESS && !APR_STATUS_IS_TIMEUP(s)) { | |
| continue; | |
| } | |
| ABTS_INT_EQUAL(tc, 1, APR_STATUS_IS_TIMEUP(s)); | |
| ABTS_ASSERT(tc, "Timer returned too late", end - begin - timeout < 1000000); | |
| break; | |
| } | |
| apr_atomic_set32(&flag, 0); /* tell the thread to exit. */ | |
| JOIN_WITH_SUCCESS(tc, th); | |
| ABTS_ASSERT(tc, "Too many retries", i < MAX_RETRY); | |
| APR_ASSERT_SUCCESS(tc, "Unable to destroy the timeout mutex", | |
| apr_thread_mutex_destroy(timeout_mutex)); | |
| } | |
| static void test_thread_nestedmutex(abts_case *tc, void *data) | |
| { | |
| apr_thread_mutex_t *m; | |
| apr_status_t rv; | |
| rv = apr_thread_mutex_create(&m, APR_THREAD_MUTEX_NESTED, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); | |
| ABTS_PTR_NOTNULL(tc, m); | |
| rv = apr_thread_mutex_lock(m); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); | |
| rv = apr_thread_mutex_trylock(m); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); | |
| if (rv == APR_SUCCESS) | |
| { | |
| rv = apr_thread_mutex_unlock(m); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); | |
| } | |
| rv = apr_thread_mutex_unlock(m); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); | |
| } | |
| static void test_thread_unnestedmutex(abts_case *tc, void *data) | |
| { | |
| apr_thread_mutex_t *m; | |
| apr_status_t rv; | |
| rv = apr_thread_mutex_create(&m, APR_THREAD_MUTEX_UNNESTED, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); | |
| ABTS_PTR_NOTNULL(tc, m); | |
| rv = apr_thread_mutex_lock(m); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); | |
| rv = apr_thread_mutex_trylock(m); | |
| ABTS_INT_EQUAL(tc, APR_EBUSY, rv); | |
| if (rv == APR_SUCCESS) | |
| { | |
| rv = apr_thread_mutex_unlock(m); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); | |
| } | |
| rv = apr_thread_mutex_unlock(m); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); | |
| } | |
| #ifdef WIN32 | |
| static void *APR_THREAD_FUNC | |
| thread_win32_abandoned_mutex_function(apr_thread_t *thd, void *data) | |
| { | |
| apr_thread_mutex_t *mutex = data; | |
| apr_status_t rv; | |
| rv = apr_thread_mutex_lock(mutex); | |
| /* exit from thread without unlocking mutex. */ | |
| apr_thread_exit(thd, rv); | |
| return NULL; | |
| } | |
| static void test_win32_abandoned_mutex(abts_case *tc, void *data) | |
| { | |
| apr_status_t rv; | |
| apr_thread_t *thread; | |
| apr_thread_mutex_t *mutex; | |
| /* Create timed mutex: APR will create Win32 mutex object in this case. */ | |
| rv = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_TIMED, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); | |
| rv = apr_thread_create(&thread, NULL, thread_win32_abandoned_mutex_function, | |
| mutex, p); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); | |
| apr_thread_join(&rv, thread); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); | |
| rv = apr_thread_mutex_trylock(mutex); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); | |
| rv = apr_thread_mutex_unlock (mutex); | |
| ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); | |
| } | |
| #endif | |
| #endif /* !APR_HAS_THREADS */ | |
| #if !APR_HAS_THREADS | |
| static void threads_not_impl(abts_case *tc, void *data) | |
| { | |
| ABTS_NOT_IMPL(tc, "Threads not implemented on this platform"); | |
| } | |
| #endif | |
| abts_suite *testlock(abts_suite *suite) | |
| { | |
| suite = ADD_SUITE(suite) | |
| #if !APR_HAS_THREADS | |
| abts_run_test(suite, threads_not_impl, NULL); | |
| #else | |
| abts_run_test(suite, test_thread_mutex, NULL); | |
| abts_run_test(suite, test_thread_timedmutex, NULL); | |
| abts_run_test(suite, test_thread_nestedmutex, NULL); | |
| abts_run_test(suite, test_thread_unnestedmutex, NULL); | |
| abts_run_test(suite, test_thread_rwlock, NULL); | |
| abts_run_test(suite, test_cond, NULL); | |
| abts_run_test(suite, test_timeoutcond, NULL); | |
| abts_run_test(suite, test_timeoutmutex, NULL); | |
| #ifdef WIN32 | |
| abts_run_test(suite, test_win32_abandoned_mutex, NULL); | |
| #endif | |
| #endif | |
| return suite; | |
| } | |