Add code to support barrier synchronous object and implement
pthread_mutex_timedlock(). Reviewed by: deischen
This commit is contained in:
parent
cf669e5456
commit
2ab83179b5
@ -52,6 +52,7 @@
|
||||
#define PTHREAD_KEYS_MAX 256
|
||||
#define PTHREAD_STACK_MIN 1024
|
||||
#define PTHREAD_THREADS_MAX ULONG_MAX
|
||||
#define PTHREAD_BARRIER_SERIAL_THREAD -1
|
||||
|
||||
/*
|
||||
* Flags for threads and thread attributes.
|
||||
@ -95,6 +96,8 @@ struct pthread_mutex_attr;
|
||||
struct pthread_once;
|
||||
struct pthread_rwlock;
|
||||
struct pthread_rwlockattr;
|
||||
struct pthread_barrier;
|
||||
struct pthread_barrier_attr;
|
||||
|
||||
/*
|
||||
* Primitive system data type definitions required by P1003.1c.
|
||||
@ -113,6 +116,8 @@ typedef int pthread_key_t;
|
||||
typedef struct pthread_once pthread_once_t;
|
||||
typedef struct pthread_rwlock *pthread_rwlock_t;
|
||||
typedef struct pthread_rwlockattr *pthread_rwlockattr_t;
|
||||
typedef struct pthread_barrier *pthread_barrier_t;
|
||||
typedef struct pthread_barrierattr *pthread_barrierattr_t;
|
||||
|
||||
/*
|
||||
* Additional type definitions:
|
||||
@ -203,6 +208,15 @@ int pthread_attr_setguardsize(pthread_attr_t *, size_t);
|
||||
int pthread_attr_setstack(pthread_attr_t *, void *, size_t);
|
||||
int pthread_attr_setstackaddr(pthread_attr_t *, void *);
|
||||
int pthread_attr_setdetachstate(pthread_attr_t *, int);
|
||||
int pthread_barrier_destroy(pthread_barrier_t *);
|
||||
int pthread_barrier_init(pthread_barrier_t *,
|
||||
const pthread_barrierattr_t *, unsigned);
|
||||
int pthread_barrier_wait(pthread_barrier_t *);
|
||||
int pthread_barrierattr_destroy(pthread_barrierattr_t *);
|
||||
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *,
|
||||
int *);
|
||||
int pthread_barrierattr_init(pthread_barrierattr_t *);
|
||||
int pthread_barrierattr_setpshared(pthread_barrierattr_t *, int);
|
||||
void pthread_cleanup_pop(int);
|
||||
void pthread_cleanup_push(void (*) (void *), void *routine_arg);
|
||||
int pthread_condattr_destroy(pthread_condattr_t *);
|
||||
@ -236,6 +250,8 @@ int pthread_mutex_init(pthread_mutex_t *,
|
||||
const pthread_mutexattr_t *);
|
||||
int pthread_mutex_lock(pthread_mutex_t *);
|
||||
int pthread_mutex_trylock(pthread_mutex_t *);
|
||||
int pthread_mutex_timedlock(pthread_mutex_t *,
|
||||
const struct timespec *);
|
||||
int pthread_mutex_unlock(pthread_mutex_t *);
|
||||
int pthread_once(pthread_once_t *, void (*) (void));
|
||||
int pthread_rwlock_destroy(pthread_rwlock_t *);
|
||||
|
@ -28,6 +28,8 @@ SRCS+= \
|
||||
thr_attr_setstack.c \
|
||||
thr_attr_setstackaddr.c \
|
||||
thr_attr_setstacksize.c \
|
||||
thr_barrier.c \
|
||||
thr_barrierattr.c \
|
||||
thr_cancel.c \
|
||||
thr_clean.c \
|
||||
thr_close.c \
|
||||
|
122
lib/libkse/thread/thr_barrier.c
Normal file
122
lib/libkse/thread/thr_barrier.c
Normal file
@ -0,0 +1,122 @@
|
||||
/*-
|
||||
* Copyright (c) 2003 David Xu <davidxu@freebsd.org>
|
||||
* 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.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include "namespace.h"
|
||||
#include <pthread.h>
|
||||
#include "un-namespace.h"
|
||||
#include "thr_private.h"
|
||||
|
||||
__weak_reference(_pthread_barrier_init, pthread_barrier_init);
|
||||
__weak_reference(_pthread_barrier_wait, pthread_barrier_wait);
|
||||
__weak_reference(_pthread_barrier_destroy, pthread_barrier_destroy);
|
||||
|
||||
int
|
||||
_pthread_barrier_destroy(pthread_barrier_t *barrier)
|
||||
{
|
||||
pthread_barrier_t bar;
|
||||
int ret, ret2;
|
||||
|
||||
if (barrier == NULL || *barrier == NULL)
|
||||
return (EINVAL);
|
||||
|
||||
bar = *barrier;
|
||||
if (bar->b_waiters > 0)
|
||||
return (EBUSY);
|
||||
*barrier = NULL;
|
||||
ret = _pthread_mutex_destroy(&bar->b_lock);
|
||||
ret2 = _pthread_cond_destroy(&bar->b_cond);
|
||||
free(bar);
|
||||
return (ret ? ret : ret2);
|
||||
}
|
||||
|
||||
int
|
||||
_pthread_barrier_init(pthread_barrier_t *barrier,
|
||||
const pthread_barrierattr_t *attr, int count)
|
||||
{
|
||||
pthread_barrier_t bar;
|
||||
int ret;
|
||||
|
||||
if (barrier == NULL || count <= 0)
|
||||
return (EINVAL);
|
||||
|
||||
bar = malloc(sizeof(struct pthread_barrier));
|
||||
if (bar == NULL)
|
||||
return (ENOMEM);
|
||||
|
||||
if ((ret = _pthread_mutex_init(&bar->b_lock, NULL)) != 0) {
|
||||
free(bar);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
if ((ret = _pthread_cond_init(&bar->b_cond, NULL)) != 0) {
|
||||
_pthread_mutex_destroy(&bar->b_lock);
|
||||
free(bar);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
bar->b_waiters = 0;
|
||||
bar->b_count = count;
|
||||
bar->b_generation = 0;
|
||||
*barrier = bar;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
_pthread_barrier_wait(pthread_barrier_t *barrier)
|
||||
{
|
||||
int ret, gen;
|
||||
pthread_barrier_t bar;
|
||||
|
||||
if (barrier == NULL || *barrier == NULL)
|
||||
return (EINVAL);
|
||||
|
||||
bar = *barrier;
|
||||
if ((ret = _pthread_mutex_lock(&bar->b_lock)) != 0)
|
||||
return (ret);
|
||||
|
||||
if (++bar->b_waiters == bar->b_count) {
|
||||
/* Current thread is lastest thread */
|
||||
bar->b_generation++;
|
||||
bar->b_waiters = 0;
|
||||
ret = _pthread_cond_broadcast(&bar->b_cond);
|
||||
if (ret == 0)
|
||||
ret = PTHREAD_BARRIER_SERIAL_THREAD;
|
||||
} else {
|
||||
gen = bar->b_generation;
|
||||
do {
|
||||
ret = _pthread_cond_wait(
|
||||
&bar->b_cond, &bar->b_lock);
|
||||
/* test generation to avoid bogus wakeup */
|
||||
} while (ret == 0 && gen == bar->b_generation);
|
||||
}
|
||||
_pthread_mutex_unlock(&bar->b_lock);
|
||||
return (ret);
|
||||
}
|
93
lib/libkse/thread/thr_barrierattr.c
Normal file
93
lib/libkse/thread/thr_barrierattr.c
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2003 David Xu <davidxu@freebsd.org>.
|
||||
* 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(s), this list of conditions and the following disclaimer as
|
||||
* the first lines of this file unmodified other than the possible
|
||||
* addition of one or more copyright notices.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice(s), 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 COPYRIGHT HOLDER(S) ``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 COPYRIGHT HOLDER(S) 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.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include "thr_private.h"
|
||||
|
||||
__weak_reference(_pthread_barrierattr_destroy, pthread_barrierattr_destroy);
|
||||
__weak_reference(_pthread_barrierattr_init, pthread_barrierattr_init);
|
||||
__weak_reference(_pthread_barrierattr_setpshared,
|
||||
pthread_barrierattr_setpshared);
|
||||
__weak_reference(_pthread_barrierattr_getpshared,
|
||||
pthread_barrierattr_getpshared);
|
||||
|
||||
int
|
||||
_pthread_barrierattr_destroy(pthread_barrierattr_t *attr)
|
||||
{
|
||||
|
||||
if (attr == NULL || *attr == NULL)
|
||||
return (EINVAL);
|
||||
|
||||
free(*attr);
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
_pthread_barrierattr_getpshared(const pthread_barrierattr_t *attr,
|
||||
int *pshared)
|
||||
{
|
||||
|
||||
if (attr == NULL || *attr == NULL)
|
||||
return (EINVAL);
|
||||
|
||||
*pshared = (*attr)->pshared;
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
_pthread_barrierattr_init(pthread_barrierattr_t *attr)
|
||||
{
|
||||
|
||||
if (attr == NULL)
|
||||
return (EINVAL);
|
||||
|
||||
if ((*attr = malloc(sizeof(struct pthread_barrierattr))) == NULL)
|
||||
return (ENOMEM);
|
||||
|
||||
(*attr)->pshared = PTHREAD_PROCESS_PRIVATE;
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
_pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared)
|
||||
{
|
||||
|
||||
if (attr == NULL || *attr == NULL)
|
||||
return (EINVAL);
|
||||
|
||||
/* Only PTHREAD_PROCESS_PRIVATE is supported. */
|
||||
if (pshared != PTHREAD_PROCESS_PRIVATE)
|
||||
return (EINVAL);
|
||||
|
||||
(*attr)->pshared = pshared;
|
||||
return (0);
|
||||
}
|
@ -853,8 +853,9 @@ kse_sched_single(struct kse_mailbox *kmbx)
|
||||
curthread->wakeup_time.tv_nsec = -1;
|
||||
break;
|
||||
|
||||
case PS_JOIN:
|
||||
case PS_MUTEX_WAIT:
|
||||
break;
|
||||
case PS_JOIN:
|
||||
case PS_SUSPENDED:
|
||||
case PS_DEADLOCK:
|
||||
default:
|
||||
@ -1735,8 +1736,10 @@ kse_switchout_thread(struct kse *kse, struct pthread *thread)
|
||||
case PS_SIGWAIT:
|
||||
KSE_WAITQ_INSERT(kse, thread);
|
||||
break;
|
||||
case PS_JOIN:
|
||||
case PS_MUTEX_WAIT:
|
||||
KSE_WAITQ_INSERT(kse, thread);
|
||||
break;
|
||||
case PS_JOIN:
|
||||
case PS_SIGSUSPEND:
|
||||
case PS_SUSPENDED:
|
||||
case PS_DEADLOCK:
|
||||
|
@ -64,6 +64,8 @@
|
||||
#define THR_ASSERT_NOT_IN_SYNCQ(thr)
|
||||
#endif
|
||||
|
||||
#define THR_IN_MUTEXQ(thr) (((thr)->sflags & THR_FLAGS_IN_SYNCQ) != 0)
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
@ -86,6 +88,7 @@ static pthread_mutexattr_t static_mattr = &static_mutex_attr;
|
||||
|
||||
/* Single underscore versions provided for libc internal usage: */
|
||||
__weak_reference(__pthread_mutex_lock, pthread_mutex_lock);
|
||||
__weak_reference(__pthread_mutex_timedlock, pthread_mutex_timedlock);
|
||||
__weak_reference(__pthread_mutex_trylock, pthread_mutex_trylock);
|
||||
|
||||
/* No difference between libc and application usage of these: */
|
||||
@ -448,15 +451,22 @@ _pthread_mutex_trylock(pthread_mutex_t *mutex)
|
||||
}
|
||||
|
||||
static int
|
||||
mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m,
|
||||
const struct timespec * abstime)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
THR_ASSERT((m != NULL) && (*m != NULL),
|
||||
"Uninitialized mutex in pthread_mutex_trylock_basic");
|
||||
|
||||
if (abstime != NULL && (abstime->tv_sec < 0 || abstime->tv_nsec < 0 ||
|
||||
abstime->tv_nsec >= 1000000000))
|
||||
return (EINVAL);
|
||||
|
||||
/* Reset the interrupted flag: */
|
||||
curthread->interrupted = 0;
|
||||
curthread->timeout = 0;
|
||||
curthread->wakeup_time.tv_sec = -1;
|
||||
|
||||
/*
|
||||
* Enter a loop waiting to become the mutex owner. We need a
|
||||
@ -501,6 +511,14 @@ mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
/* Unlock the mutex structure: */
|
||||
THR_LOCK_RELEASE(curthread, &(*m)->m_lock);
|
||||
} else {
|
||||
/* Set the wakeup time: */
|
||||
if (abstime) {
|
||||
curthread->wakeup_time.tv_sec =
|
||||
abstime->tv_sec;
|
||||
curthread->wakeup_time.tv_nsec =
|
||||
abstime->tv_nsec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Join the queue of threads waiting to lock
|
||||
* the mutex and save a pointer to the mutex.
|
||||
@ -521,6 +539,13 @@ mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
|
||||
/* Schedule the next thread: */
|
||||
_thr_sched_switch(curthread);
|
||||
|
||||
if (THR_IN_MUTEXQ(curthread)) {
|
||||
THR_LOCK_ACQUIRE(curthread, &(*m)->m_lock);
|
||||
mutex_queue_remove(*m, curthread);
|
||||
curthread->data.mutex = NULL;
|
||||
THR_LOCK_RELEASE(curthread, &(*m)->m_lock);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -560,6 +585,14 @@ mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
/* Unlock the mutex structure: */
|
||||
THR_LOCK_RELEASE(curthread, &(*m)->m_lock);
|
||||
} else {
|
||||
/* Set the wakeup time: */
|
||||
if (abstime) {
|
||||
curthread->wakeup_time.tv_sec =
|
||||
abstime->tv_sec;
|
||||
curthread->wakeup_time.tv_nsec =
|
||||
abstime->tv_nsec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Join the queue of threads waiting to lock
|
||||
* the mutex and save a pointer to the mutex.
|
||||
@ -585,6 +618,13 @@ mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
|
||||
/* Schedule the next thread: */
|
||||
_thr_sched_switch(curthread);
|
||||
|
||||
if (THR_IN_MUTEXQ(curthread)) {
|
||||
THR_LOCK_ACQUIRE(curthread, &(*m)->m_lock);
|
||||
mutex_queue_remove(*m, curthread);
|
||||
curthread->data.mutex = NULL;
|
||||
THR_LOCK_RELEASE(curthread, &(*m)->m_lock);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -634,6 +674,14 @@ mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
/* Unlock the mutex structure: */
|
||||
THR_LOCK_RELEASE(curthread, &(*m)->m_lock);
|
||||
} else {
|
||||
/* Set the wakeup time: */
|
||||
if (abstime) {
|
||||
curthread->wakeup_time.tv_sec =
|
||||
abstime->tv_sec;
|
||||
curthread->wakeup_time.tv_nsec =
|
||||
abstime->tv_nsec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Join the queue of threads waiting to lock
|
||||
* the mutex and save a pointer to the mutex.
|
||||
@ -659,6 +707,14 @@ mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
|
||||
/* Schedule the next thread: */
|
||||
_thr_sched_switch(curthread);
|
||||
|
||||
if (THR_IN_MUTEXQ(curthread)) {
|
||||
THR_LOCK_ACQUIRE(curthread, &(*m)->m_lock);
|
||||
mutex_queue_remove(*m, curthread);
|
||||
curthread->data.mutex = NULL;
|
||||
THR_LOCK_RELEASE(curthread, &(*m)->m_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* The threads priority may have changed while
|
||||
* waiting for the mutex causing a ceiling
|
||||
@ -680,7 +736,10 @@ mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
}
|
||||
|
||||
} while (((*m)->m_owner != curthread) && (ret == 0) &&
|
||||
(curthread->interrupted == 0));
|
||||
(curthread->interrupted == 0) && (curthread->timeout == 0));
|
||||
|
||||
if (ret == 0 && curthread->timeout)
|
||||
ret = ETIMEDOUT;
|
||||
|
||||
/*
|
||||
* Check to see if this thread was interrupted and
|
||||
@ -720,7 +779,7 @@ __pthread_mutex_lock(pthread_mutex_t *m)
|
||||
* initialization:
|
||||
*/
|
||||
else if ((*m != NULL) || ((ret = init_static(curthread, m)) == 0))
|
||||
ret = mutex_lock_common(curthread, m);
|
||||
ret = mutex_lock_common(curthread, m, NULL);
|
||||
|
||||
return (ret);
|
||||
}
|
||||
@ -746,7 +805,56 @@ _pthread_mutex_lock(pthread_mutex_t *m)
|
||||
*/
|
||||
else if ((*m != NULL) ||
|
||||
((ret = init_static_private(curthread, m)) == 0))
|
||||
ret = mutex_lock_common(curthread, m);
|
||||
ret = mutex_lock_common(curthread, m, NULL);
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
int
|
||||
__pthread_mutex_timedlock(pthread_mutex_t *m,
|
||||
const struct timespec *abs_timeout)
|
||||
{
|
||||
struct pthread *curthread;
|
||||
int ret = 0;
|
||||
|
||||
if (_thr_initial == NULL)
|
||||
_libpthread_init(NULL);
|
||||
|
||||
curthread = _get_curthread();
|
||||
if (m == NULL)
|
||||
ret = EINVAL;
|
||||
|
||||
/*
|
||||
* If the mutex is statically initialized, perform the dynamic
|
||||
* initialization:
|
||||
*/
|
||||
else if ((*m != NULL) || ((ret = init_static(curthread, m)) == 0))
|
||||
ret = mutex_lock_common(curthread, m, abs_timeout);
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
int
|
||||
_pthread_mutex_timedlock(pthread_mutex_t *m,
|
||||
const struct timespec *abs_timeout)
|
||||
{
|
||||
struct pthread *curthread;
|
||||
int ret = 0;
|
||||
|
||||
if (_thr_initial == NULL)
|
||||
_libpthread_init(NULL);
|
||||
curthread = _get_curthread();
|
||||
|
||||
if (m == NULL)
|
||||
ret = EINVAL;
|
||||
|
||||
/*
|
||||
* If the mutex is statically initialized, perform the dynamic
|
||||
* initialization marking it private (delete safe):
|
||||
*/
|
||||
else if ((*m != NULL) ||
|
||||
((ret = init_static_private(curthread, m)) == 0))
|
||||
ret = mutex_lock_common(curthread, m, abs_timeout);
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
@ -390,6 +390,18 @@ struct pthread_cond_attr {
|
||||
long c_flags;
|
||||
};
|
||||
|
||||
struct pthread_barrier {
|
||||
pthread_mutex_t b_lock;
|
||||
pthread_cond_t b_cond;
|
||||
int b_count;
|
||||
int b_waiters;
|
||||
int b_generation;
|
||||
};
|
||||
|
||||
struct pthread_barrierattr {
|
||||
int pshared;
|
||||
};
|
||||
|
||||
/*
|
||||
* Flags for condition variables.
|
||||
*/
|
||||
|
@ -14,6 +14,7 @@ global:
|
||||
__pthread_cond_wait;
|
||||
__pthread_mutex_lock;
|
||||
__pthread_mutex_trylock;
|
||||
__pthread_mutex_timedlock;
|
||||
__read;
|
||||
__readv;
|
||||
__select;
|
||||
@ -26,6 +27,13 @@ global:
|
||||
_nanosleep;
|
||||
_pause;
|
||||
_pselect;
|
||||
_pthread_barrier_destroy;
|
||||
_pthread_barrier_init;
|
||||
_pthread_barrier_wait;
|
||||
_pthread_barrierattr_destroy;
|
||||
_pthread_barrierattr_getpshared;
|
||||
_pthread_barrierattr_init;
|
||||
_pthread_barrierattr_setpshared;
|
||||
_pthread_attr_default;
|
||||
_pthread_attr_destroy;
|
||||
_pthread_attr_get_np;
|
||||
@ -80,6 +88,7 @@ global:
|
||||
_pthread_mutex_init;
|
||||
_pthread_mutex_lock;
|
||||
_pthread_mutex_setprioceiling;
|
||||
_pthread_mutex_timedlock;
|
||||
_pthread_mutex_trylock;
|
||||
_pthread_mutex_unlock;
|
||||
_pthread_mutexattr_default;
|
||||
@ -162,6 +171,13 @@ global:
|
||||
pause;
|
||||
poll;
|
||||
pselect;
|
||||
pthread_barrier_destroy;
|
||||
pthread_barrier_init;
|
||||
pthread_barrier_wait;
|
||||
pthread_barrierattr_destroy;
|
||||
pthread_barrierattr_getpshared;
|
||||
pthread_barrierattr_init;
|
||||
pthread_barrierattr_setpshared;
|
||||
pthread_attr_destroy;
|
||||
pthread_attr_get_np;
|
||||
pthread_attr_getdetachstate;
|
||||
@ -214,6 +230,7 @@ global:
|
||||
pthread_mutex_init;
|
||||
pthread_mutex_lock;
|
||||
pthread_mutex_setprioceiling;
|
||||
pthread_mutex_timedlock;
|
||||
pthread_mutex_trylock;
|
||||
pthread_mutex_unlock;
|
||||
pthread_mutexattr_destroy;
|
||||
|
@ -28,6 +28,8 @@ SRCS+= \
|
||||
thr_attr_setstack.c \
|
||||
thr_attr_setstackaddr.c \
|
||||
thr_attr_setstacksize.c \
|
||||
thr_barrier.c \
|
||||
thr_barrierattr.c \
|
||||
thr_cancel.c \
|
||||
thr_clean.c \
|
||||
thr_close.c \
|
||||
|
122
lib/libpthread/thread/thr_barrier.c
Normal file
122
lib/libpthread/thread/thr_barrier.c
Normal file
@ -0,0 +1,122 @@
|
||||
/*-
|
||||
* Copyright (c) 2003 David Xu <davidxu@freebsd.org>
|
||||
* 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.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include "namespace.h"
|
||||
#include <pthread.h>
|
||||
#include "un-namespace.h"
|
||||
#include "thr_private.h"
|
||||
|
||||
__weak_reference(_pthread_barrier_init, pthread_barrier_init);
|
||||
__weak_reference(_pthread_barrier_wait, pthread_barrier_wait);
|
||||
__weak_reference(_pthread_barrier_destroy, pthread_barrier_destroy);
|
||||
|
||||
int
|
||||
_pthread_barrier_destroy(pthread_barrier_t *barrier)
|
||||
{
|
||||
pthread_barrier_t bar;
|
||||
int ret, ret2;
|
||||
|
||||
if (barrier == NULL || *barrier == NULL)
|
||||
return (EINVAL);
|
||||
|
||||
bar = *barrier;
|
||||
if (bar->b_waiters > 0)
|
||||
return (EBUSY);
|
||||
*barrier = NULL;
|
||||
ret = _pthread_mutex_destroy(&bar->b_lock);
|
||||
ret2 = _pthread_cond_destroy(&bar->b_cond);
|
||||
free(bar);
|
||||
return (ret ? ret : ret2);
|
||||
}
|
||||
|
||||
int
|
||||
_pthread_barrier_init(pthread_barrier_t *barrier,
|
||||
const pthread_barrierattr_t *attr, int count)
|
||||
{
|
||||
pthread_barrier_t bar;
|
||||
int ret;
|
||||
|
||||
if (barrier == NULL || count <= 0)
|
||||
return (EINVAL);
|
||||
|
||||
bar = malloc(sizeof(struct pthread_barrier));
|
||||
if (bar == NULL)
|
||||
return (ENOMEM);
|
||||
|
||||
if ((ret = _pthread_mutex_init(&bar->b_lock, NULL)) != 0) {
|
||||
free(bar);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
if ((ret = _pthread_cond_init(&bar->b_cond, NULL)) != 0) {
|
||||
_pthread_mutex_destroy(&bar->b_lock);
|
||||
free(bar);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
bar->b_waiters = 0;
|
||||
bar->b_count = count;
|
||||
bar->b_generation = 0;
|
||||
*barrier = bar;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
_pthread_barrier_wait(pthread_barrier_t *barrier)
|
||||
{
|
||||
int ret, gen;
|
||||
pthread_barrier_t bar;
|
||||
|
||||
if (barrier == NULL || *barrier == NULL)
|
||||
return (EINVAL);
|
||||
|
||||
bar = *barrier;
|
||||
if ((ret = _pthread_mutex_lock(&bar->b_lock)) != 0)
|
||||
return (ret);
|
||||
|
||||
if (++bar->b_waiters == bar->b_count) {
|
||||
/* Current thread is lastest thread */
|
||||
bar->b_generation++;
|
||||
bar->b_waiters = 0;
|
||||
ret = _pthread_cond_broadcast(&bar->b_cond);
|
||||
if (ret == 0)
|
||||
ret = PTHREAD_BARRIER_SERIAL_THREAD;
|
||||
} else {
|
||||
gen = bar->b_generation;
|
||||
do {
|
||||
ret = _pthread_cond_wait(
|
||||
&bar->b_cond, &bar->b_lock);
|
||||
/* test generation to avoid bogus wakeup */
|
||||
} while (ret == 0 && gen == bar->b_generation);
|
||||
}
|
||||
_pthread_mutex_unlock(&bar->b_lock);
|
||||
return (ret);
|
||||
}
|
93
lib/libpthread/thread/thr_barrierattr.c
Normal file
93
lib/libpthread/thread/thr_barrierattr.c
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2003 David Xu <davidxu@freebsd.org>.
|
||||
* 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(s), this list of conditions and the following disclaimer as
|
||||
* the first lines of this file unmodified other than the possible
|
||||
* addition of one or more copyright notices.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice(s), 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 COPYRIGHT HOLDER(S) ``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 COPYRIGHT HOLDER(S) 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.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include "thr_private.h"
|
||||
|
||||
__weak_reference(_pthread_barrierattr_destroy, pthread_barrierattr_destroy);
|
||||
__weak_reference(_pthread_barrierattr_init, pthread_barrierattr_init);
|
||||
__weak_reference(_pthread_barrierattr_setpshared,
|
||||
pthread_barrierattr_setpshared);
|
||||
__weak_reference(_pthread_barrierattr_getpshared,
|
||||
pthread_barrierattr_getpshared);
|
||||
|
||||
int
|
||||
_pthread_barrierattr_destroy(pthread_barrierattr_t *attr)
|
||||
{
|
||||
|
||||
if (attr == NULL || *attr == NULL)
|
||||
return (EINVAL);
|
||||
|
||||
free(*attr);
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
_pthread_barrierattr_getpshared(const pthread_barrierattr_t *attr,
|
||||
int *pshared)
|
||||
{
|
||||
|
||||
if (attr == NULL || *attr == NULL)
|
||||
return (EINVAL);
|
||||
|
||||
*pshared = (*attr)->pshared;
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
_pthread_barrierattr_init(pthread_barrierattr_t *attr)
|
||||
{
|
||||
|
||||
if (attr == NULL)
|
||||
return (EINVAL);
|
||||
|
||||
if ((*attr = malloc(sizeof(struct pthread_barrierattr))) == NULL)
|
||||
return (ENOMEM);
|
||||
|
||||
(*attr)->pshared = PTHREAD_PROCESS_PRIVATE;
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
_pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared)
|
||||
{
|
||||
|
||||
if (attr == NULL || *attr == NULL)
|
||||
return (EINVAL);
|
||||
|
||||
/* Only PTHREAD_PROCESS_PRIVATE is supported. */
|
||||
if (pshared != PTHREAD_PROCESS_PRIVATE)
|
||||
return (EINVAL);
|
||||
|
||||
(*attr)->pshared = pshared;
|
||||
return (0);
|
||||
}
|
@ -853,8 +853,9 @@ kse_sched_single(struct kse_mailbox *kmbx)
|
||||
curthread->wakeup_time.tv_nsec = -1;
|
||||
break;
|
||||
|
||||
case PS_JOIN:
|
||||
case PS_MUTEX_WAIT:
|
||||
break;
|
||||
case PS_JOIN:
|
||||
case PS_SUSPENDED:
|
||||
case PS_DEADLOCK:
|
||||
default:
|
||||
@ -1735,8 +1736,10 @@ kse_switchout_thread(struct kse *kse, struct pthread *thread)
|
||||
case PS_SIGWAIT:
|
||||
KSE_WAITQ_INSERT(kse, thread);
|
||||
break;
|
||||
case PS_JOIN:
|
||||
case PS_MUTEX_WAIT:
|
||||
KSE_WAITQ_INSERT(kse, thread);
|
||||
break;
|
||||
case PS_JOIN:
|
||||
case PS_SIGSUSPEND:
|
||||
case PS_SUSPENDED:
|
||||
case PS_DEADLOCK:
|
||||
|
@ -64,6 +64,8 @@
|
||||
#define THR_ASSERT_NOT_IN_SYNCQ(thr)
|
||||
#endif
|
||||
|
||||
#define THR_IN_MUTEXQ(thr) (((thr)->sflags & THR_FLAGS_IN_SYNCQ) != 0)
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
@ -86,6 +88,7 @@ static pthread_mutexattr_t static_mattr = &static_mutex_attr;
|
||||
|
||||
/* Single underscore versions provided for libc internal usage: */
|
||||
__weak_reference(__pthread_mutex_lock, pthread_mutex_lock);
|
||||
__weak_reference(__pthread_mutex_timedlock, pthread_mutex_timedlock);
|
||||
__weak_reference(__pthread_mutex_trylock, pthread_mutex_trylock);
|
||||
|
||||
/* No difference between libc and application usage of these: */
|
||||
@ -448,15 +451,22 @@ _pthread_mutex_trylock(pthread_mutex_t *mutex)
|
||||
}
|
||||
|
||||
static int
|
||||
mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m,
|
||||
const struct timespec * abstime)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
THR_ASSERT((m != NULL) && (*m != NULL),
|
||||
"Uninitialized mutex in pthread_mutex_trylock_basic");
|
||||
|
||||
if (abstime != NULL && (abstime->tv_sec < 0 || abstime->tv_nsec < 0 ||
|
||||
abstime->tv_nsec >= 1000000000))
|
||||
return (EINVAL);
|
||||
|
||||
/* Reset the interrupted flag: */
|
||||
curthread->interrupted = 0;
|
||||
curthread->timeout = 0;
|
||||
curthread->wakeup_time.tv_sec = -1;
|
||||
|
||||
/*
|
||||
* Enter a loop waiting to become the mutex owner. We need a
|
||||
@ -501,6 +511,14 @@ mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
/* Unlock the mutex structure: */
|
||||
THR_LOCK_RELEASE(curthread, &(*m)->m_lock);
|
||||
} else {
|
||||
/* Set the wakeup time: */
|
||||
if (abstime) {
|
||||
curthread->wakeup_time.tv_sec =
|
||||
abstime->tv_sec;
|
||||
curthread->wakeup_time.tv_nsec =
|
||||
abstime->tv_nsec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Join the queue of threads waiting to lock
|
||||
* the mutex and save a pointer to the mutex.
|
||||
@ -521,6 +539,13 @@ mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
|
||||
/* Schedule the next thread: */
|
||||
_thr_sched_switch(curthread);
|
||||
|
||||
if (THR_IN_MUTEXQ(curthread)) {
|
||||
THR_LOCK_ACQUIRE(curthread, &(*m)->m_lock);
|
||||
mutex_queue_remove(*m, curthread);
|
||||
curthread->data.mutex = NULL;
|
||||
THR_LOCK_RELEASE(curthread, &(*m)->m_lock);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -560,6 +585,14 @@ mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
/* Unlock the mutex structure: */
|
||||
THR_LOCK_RELEASE(curthread, &(*m)->m_lock);
|
||||
} else {
|
||||
/* Set the wakeup time: */
|
||||
if (abstime) {
|
||||
curthread->wakeup_time.tv_sec =
|
||||
abstime->tv_sec;
|
||||
curthread->wakeup_time.tv_nsec =
|
||||
abstime->tv_nsec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Join the queue of threads waiting to lock
|
||||
* the mutex and save a pointer to the mutex.
|
||||
@ -585,6 +618,13 @@ mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
|
||||
/* Schedule the next thread: */
|
||||
_thr_sched_switch(curthread);
|
||||
|
||||
if (THR_IN_MUTEXQ(curthread)) {
|
||||
THR_LOCK_ACQUIRE(curthread, &(*m)->m_lock);
|
||||
mutex_queue_remove(*m, curthread);
|
||||
curthread->data.mutex = NULL;
|
||||
THR_LOCK_RELEASE(curthread, &(*m)->m_lock);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -634,6 +674,14 @@ mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
/* Unlock the mutex structure: */
|
||||
THR_LOCK_RELEASE(curthread, &(*m)->m_lock);
|
||||
} else {
|
||||
/* Set the wakeup time: */
|
||||
if (abstime) {
|
||||
curthread->wakeup_time.tv_sec =
|
||||
abstime->tv_sec;
|
||||
curthread->wakeup_time.tv_nsec =
|
||||
abstime->tv_nsec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Join the queue of threads waiting to lock
|
||||
* the mutex and save a pointer to the mutex.
|
||||
@ -659,6 +707,14 @@ mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
|
||||
/* Schedule the next thread: */
|
||||
_thr_sched_switch(curthread);
|
||||
|
||||
if (THR_IN_MUTEXQ(curthread)) {
|
||||
THR_LOCK_ACQUIRE(curthread, &(*m)->m_lock);
|
||||
mutex_queue_remove(*m, curthread);
|
||||
curthread->data.mutex = NULL;
|
||||
THR_LOCK_RELEASE(curthread, &(*m)->m_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* The threads priority may have changed while
|
||||
* waiting for the mutex causing a ceiling
|
||||
@ -680,7 +736,10 @@ mutex_lock_common(struct pthread *curthread, pthread_mutex_t *m)
|
||||
}
|
||||
|
||||
} while (((*m)->m_owner != curthread) && (ret == 0) &&
|
||||
(curthread->interrupted == 0));
|
||||
(curthread->interrupted == 0) && (curthread->timeout == 0));
|
||||
|
||||
if (ret == 0 && curthread->timeout)
|
||||
ret = ETIMEDOUT;
|
||||
|
||||
/*
|
||||
* Check to see if this thread was interrupted and
|
||||
@ -720,7 +779,7 @@ __pthread_mutex_lock(pthread_mutex_t *m)
|
||||
* initialization:
|
||||
*/
|
||||
else if ((*m != NULL) || ((ret = init_static(curthread, m)) == 0))
|
||||
ret = mutex_lock_common(curthread, m);
|
||||
ret = mutex_lock_common(curthread, m, NULL);
|
||||
|
||||
return (ret);
|
||||
}
|
||||
@ -746,7 +805,56 @@ _pthread_mutex_lock(pthread_mutex_t *m)
|
||||
*/
|
||||
else if ((*m != NULL) ||
|
||||
((ret = init_static_private(curthread, m)) == 0))
|
||||
ret = mutex_lock_common(curthread, m);
|
||||
ret = mutex_lock_common(curthread, m, NULL);
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
int
|
||||
__pthread_mutex_timedlock(pthread_mutex_t *m,
|
||||
const struct timespec *abs_timeout)
|
||||
{
|
||||
struct pthread *curthread;
|
||||
int ret = 0;
|
||||
|
||||
if (_thr_initial == NULL)
|
||||
_libpthread_init(NULL);
|
||||
|
||||
curthread = _get_curthread();
|
||||
if (m == NULL)
|
||||
ret = EINVAL;
|
||||
|
||||
/*
|
||||
* If the mutex is statically initialized, perform the dynamic
|
||||
* initialization:
|
||||
*/
|
||||
else if ((*m != NULL) || ((ret = init_static(curthread, m)) == 0))
|
||||
ret = mutex_lock_common(curthread, m, abs_timeout);
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
int
|
||||
_pthread_mutex_timedlock(pthread_mutex_t *m,
|
||||
const struct timespec *abs_timeout)
|
||||
{
|
||||
struct pthread *curthread;
|
||||
int ret = 0;
|
||||
|
||||
if (_thr_initial == NULL)
|
||||
_libpthread_init(NULL);
|
||||
curthread = _get_curthread();
|
||||
|
||||
if (m == NULL)
|
||||
ret = EINVAL;
|
||||
|
||||
/*
|
||||
* If the mutex is statically initialized, perform the dynamic
|
||||
* initialization marking it private (delete safe):
|
||||
*/
|
||||
else if ((*m != NULL) ||
|
||||
((ret = init_static_private(curthread, m)) == 0))
|
||||
ret = mutex_lock_common(curthread, m, abs_timeout);
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
@ -390,6 +390,18 @@ struct pthread_cond_attr {
|
||||
long c_flags;
|
||||
};
|
||||
|
||||
struct pthread_barrier {
|
||||
pthread_mutex_t b_lock;
|
||||
pthread_cond_t b_cond;
|
||||
int b_count;
|
||||
int b_waiters;
|
||||
int b_generation;
|
||||
};
|
||||
|
||||
struct pthread_barrierattr {
|
||||
int pshared;
|
||||
};
|
||||
|
||||
/*
|
||||
* Flags for condition variables.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user