From 0f9669031a1bc6ab9f0ecab2981ba2214ae1c210 Mon Sep 17 00:00:00 2001 From: cherierm Date: Thu, 25 Oct 2007 11:57:20 +0000 Subject: [PATCH] Most of this change is the consequence of the refactoring of the name of the function connected to the mutex object and of the unsupported semaphore function on MAC OS X. The file synchro.h rename the functions xbt_mutex_acquire() and xbt_mutex_release() to xbt_mutex_acquire() an respectively xbt_mutex_release(). This file also contains the declaration of two new functions xbt_mutex_tryacquire() and xbt_mutex_timedacquire(). The first is the non blocking version of the function xbt_mutex_acquire() (if the lock is already owned by an other thread the function immediately returns). The second function xbt_mutex_timedacquire() attempts to get the mutex during a specified delay (timeout), if the lock is not available during this delay the function returns (throws a timeout exception). These functions (for the real life) are defined in the file xbt_rl_synchro.c and (for the simulation) in the file xbt_sg_synchro.c. The header xbt_os_thread.h declare two new functions xbt_os_mutex_tryacquire() and xbt_os_mutex_timedaquire(). These functions represent the implementation of the xbt_mutex_tryacquire() and xbt_mutex_timeacquire() for the real live. These functions are implemented in the source code file xbt_os_thread.c. The change in the files dynar.c and xbt_queue.c, philosopher.c and parallel_log_crashtest.c is the consequence of the refactoring of the functions previously described. git-svn-id: svn+ssh://scm.gforge.inria.fr/svn/simgrid/simgrid/trunk@4866 48e7efb5-ca39-0410-a469-dd3cf9ba447f --- examples/gras/synchro/philosopher.c | 22 +- include/xbt/synchro.h | 6 +- src/include/xbt/xbt_os_thread.h | 2 + src/xbt/dynar.c | 4 +- src/xbt/xbt_os_thread.c | 487 +++++++++++++++++-------- src/xbt/xbt_queue.c | 52 +-- src/xbt/xbt_rl_synchro.c | 17 +- src/xbt/xbt_sg_synchro.c | 4 +- teshsuite/xbt/parallel_log_crashtest.c | 8 +- 9 files changed, 406 insertions(+), 196 deletions(-) diff --git a/examples/gras/synchro/philosopher.c b/examples/gras/synchro/philosopher.c index 755902793b..a19c3c9311 100644 --- a/examples/gras/synchro/philosopher.c +++ b/examples/gras/synchro/philosopher.c @@ -28,7 +28,7 @@ int *id; /* to pass a pointer to the threads without race condition */ static void pickup(int id, int lunch) { INFO2("Thread %d gets hungry (lunch #%d)",id,lunch); - xbt_mutex_lock(mutex); + xbt_mutex_acquire(mutex); while (state[(id + (philosopher_amount-1))%philosopher_amount] == EATING || state[(id + 1)%philosopher_amount] == EATING) { @@ -40,17 +40,17 @@ static void pickup(int id, int lunch) { state[(id + 1)%philosopher_amount] == THINKING , "Philosopher %d eats at the same time that one of its neighbors!!!",id); - xbt_mutex_unlock(mutex); + xbt_mutex_release(mutex); INFO1("Thread %d eats",id); } static void putdown(int id) { INFO1("Thread %d is full",id); - xbt_mutex_lock(mutex); + xbt_mutex_acquire(mutex); state[id] = THINKING; xbt_cond_signal(forks[(id+(philosopher_amount-1))%philosopher_amount]); xbt_cond_signal(forks[(id+1)%philosopher_amount]); - xbt_mutex_unlock(mutex); + xbt_mutex_release(mutex); INFO1("Thread %d thinks",id); } @@ -75,14 +75,14 @@ static void philo_thread(void *arg) { gras_os_sleep(id / 100.0); } - xbt_mutex_lock(mut_end); + xbt_mutex_acquire(mut_end); running_threads--; xbt_cond_signal(cond_end); - xbt_mutex_unlock(mut_end); + xbt_mutex_release(mut_end); /* Enter an endless loop to test the killing facilities */ INFO1("Thread %d tries to enter the dead-end; hopefully, the master will cancel it",id); - xbt_mutex_lock(dead_end); + xbt_mutex_acquire(dead_end); INFO1("Oops, thread %d reached the dead-end. Cancelation failed",id); } @@ -113,7 +113,7 @@ int philosopher (int argc,char *argv[]) { cond_end = xbt_cond_init(); mut_end = xbt_mutex_init(); dead_end = xbt_mutex_init(); - xbt_mutex_lock(dead_end); + xbt_mutex_acquire(dead_end); INFO2("Spawn the %d threads (%d lunches scheduled)", philosopher_amount, lunch_amount); /* spawn threads */ @@ -124,10 +124,10 @@ int philosopher (int argc,char *argv[]) { } /* wait for them */ - xbt_mutex_lock(mut_end); + xbt_mutex_acquire(mut_end); while (running_threads) xbt_cond_wait(cond_end,mut_end); - xbt_mutex_unlock(mut_end); + xbt_mutex_release(mut_end); INFO0("Cancel all childs"); /* nuke them threads */ @@ -135,7 +135,7 @@ int philosopher (int argc,char *argv[]) { xbt_thread_cancel(philosophers[i]); } - xbt_mutex_unlock(dead_end); + xbt_mutex_release(dead_end); gras_exit(); return 0; } diff --git a/include/xbt/synchro.h b/include/xbt/synchro.h index 333df9562f..4c4cf57e73 100644 --- a/include/xbt/synchro.h +++ b/include/xbt/synchro.h @@ -51,8 +51,10 @@ SG_BEGIN_DECL() typedef struct s_xbt_mutex_ *xbt_mutex_t; XBT_PUBLIC(xbt_mutex_t) xbt_mutex_init(void); - XBT_PUBLIC(void) xbt_mutex_lock(xbt_mutex_t mutex); - XBT_PUBLIC(void) xbt_mutex_unlock(xbt_mutex_t mutex); + XBT_PUBLIC(void) xbt_mutex_acquire(xbt_mutex_t mutex); + XBT_PUBLIC(void) xbt_mutex_release(xbt_mutex_t mutex); + XBT_PUBLIC(void) xbt_mutex_tryacquire(xbt_mutex_t mutex); + XBT_PUBLIC(void) xbt_mutex_timedacquire(xbt_mutex_t mutex, double delay); XBT_PUBLIC(void) xbt_mutex_destroy(xbt_mutex_t mutex); diff --git a/src/include/xbt/xbt_os_thread.h b/src/include/xbt/xbt_os_thread.h index 8cc4f0f8c6..bfd92e90b2 100644 --- a/src/include/xbt/xbt_os_thread.h +++ b/src/include/xbt/xbt_os_thread.h @@ -45,6 +45,8 @@ SG_BEGIN_DECL() XBT_PUBLIC(xbt_os_mutex_t) xbt_os_mutex_init(void); XBT_PUBLIC(void) xbt_os_mutex_acquire(xbt_os_mutex_t mutex); + XBT_PUBLIC(void) xbt_os_mutex_timedacquire(xbt_os_mutex_t mutex, double delay); + XBT_PUBLIC(void) xbt_os_mutex_tryacquire(xbt_os_mutex_t mutex); XBT_PUBLIC(void) xbt_os_mutex_release(xbt_os_mutex_t mutex); XBT_PUBLIC(void) xbt_os_mutex_destroy(xbt_os_mutex_t mutex); diff --git a/src/xbt/dynar.c b/src/xbt/dynar.c index 2dbfcf5da4..df4f0bc8ce 100644 --- a/src/xbt/dynar.c +++ b/src/xbt/dynar.c @@ -27,10 +27,10 @@ XBT_LOG_NEW_DEFAULT_SUBCATEGORY(xbt_dyn,xbt,"Dynamic arrays"); #define _dynar_lock(dynar) \ if (dynar->mutex) \ - xbt_mutex_lock(dynar->mutex) + xbt_mutex_acquire(dynar->mutex) #define _dynar_unlock(dynar) \ if (dynar->mutex) \ - xbt_mutex_unlock(dynar->mutex) + xbt_mutex_release(dynar->mutex) #define _sanity_check_dynar(dynar) \ xbt_assert0(dynar, \ "dynar is NULL") diff --git a/src/xbt/xbt_os_thread.c b/src/xbt/xbt_os_thread.c index 8114e8401f..45aa0fe263 100644 --- a/src/xbt/xbt_os_thread.c +++ b/src/xbt/xbt_os_thread.c @@ -4,7 +4,7 @@ /* Used in RL to get win/lin portability, and in SG when CONTEXT_THREAD */ /* in SG, when using CONTEXT_UCONTEXT, xbt_os_thread_stub is used instead */ -/* Copyright 2006,2007 Malek Cherier, Martin Quinson +/* Copyright 2006,2007 Malek Cherier, Martin Quinson * All right reserved. */ /* This program is free software; you can redistribute it and/or modify it @@ -25,6 +25,14 @@ XBT_LOG_NEW_DEFAULT_SUBCATEGORY(xbt_sync_os,xbt,"Synchronization mechanism (OS-l #include #include +/* use named sempahore instead */ +#ifndef HAVE_SEM_WAIT +# define MAX_SEM_NAME ((size_t)29) +# define MIN_SEM_NAME ((size_t)11) + static int __next_sem_ID = 0; + static pthread_mutex_t __next_sem_ID_lock; +#endif + typedef struct xbt_os_thread_ { pthread_t t; char *name; @@ -58,10 +66,10 @@ static void _os_thread_ex_terminate(xbt_ex_t * e) { void xbt_os_thread_mod_init(void) { int errcode; - + if (thread_mod_inited) return; - + if ((errcode=pthread_key_create(&xbt_self_thread_key, NULL))) THROW0(system_error,errcode,"pthread_key_create failed for xbt_self_thread_key"); @@ -75,25 +83,35 @@ void xbt_os_thread_mod_init(void) { __xbt_ex_ctx = _os_thread_ex_ctx; __xbt_ex_terminate = _os_thread_ex_terminate; + #ifndef HAVE_SEM_WAIT + + /* initialize the mutex use to protect the incrementation of the variable __next_sem_ID + * used to build the name of the named sempahore + */ + if ((errcode = pthread_mutex_init(&__next_sem_ID_lock,NULL))) + THROW1(system_error,errcode,"pthread_mutex_init() failed: %s",strerror(errcode)); + + #endif + thread_mod_inited = 1; } void xbt_os_thread_mod_exit(void) { - /* FIXME: don't try to free our key on shutdown. + /* FIXME: don't try to free our key on shutdown. Valgrind detects no leak if we don't, and whine if we try to */ // int errcode; - + // if ((errcode=pthread_key_delete(xbt_self_thread_key))) // THROW0(system_error,errcode,"pthread_key_delete failed for xbt_self_thread_key"); } static void * wrapper_start_routine(void *s) { - xbt_os_thread_t t = s; + xbt_os_thread_t t = s; int errcode; if ((errcode=pthread_setspecific(xbt_self_thread_key,t))) THROW0(system_error,errcode, - "pthread_setspecific failed for xbt_self_thread_key"); - + "pthread_setspecific failed for xbt_self_thread_key"); + return (*(t->start_routine))(t->param); } xbt_os_thread_t xbt_os_thread_create(const char*name, @@ -107,10 +125,10 @@ xbt_os_thread_t xbt_os_thread_create(const char*name, res_thread->param = param; res_thread->exception = xbt_new(ex_ctx_t, 1); XBT_CTX_INITIALIZE(res_thread->exception); - - if ((errcode = pthread_create(&(res_thread->t), NULL, + + if ((errcode = pthread_create(&(res_thread->t), NULL, wrapper_start_routine, res_thread))) - THROW1(system_error,errcode, + THROW1(system_error,errcode, "pthread_create failed: %s",strerror(errcode)); return res_thread; @@ -124,11 +142,11 @@ const char* xbt_os_thread_self_name(void) { xbt_os_thread_t self = xbt_os_thread_self(); return self?self->name:"main"; } -void +void xbt_os_thread_join(xbt_os_thread_t thread,void ** thread_return) { - - int errcode; - + + int errcode; + if ((errcode = pthread_join(thread->t,thread_return))) THROW1(system_error,errcode, "pthread_join failed: %s", strerror(errcode)); @@ -138,8 +156,8 @@ xbt_os_thread_join(xbt_os_thread_t thread,void ** thread_return) { if (thread == main_thread) /* just killed main thread */ main_thread = NULL; - free(thread); -} + free(thread); +} void xbt_os_thread_exit(int *retval) { pthread_exit(retval); @@ -150,7 +168,7 @@ xbt_os_thread_t xbt_os_thread_self(void) { if (!thread_mod_inited) return NULL; - + res = pthread_getspecific(xbt_self_thread_key); if (!res) res = main_thread; @@ -171,28 +189,101 @@ typedef struct xbt_os_mutex_ { pthread_mutex_t m; } s_xbt_os_mutex_t; +#include +#include + xbt_os_mutex_t xbt_os_mutex_init(void) { xbt_os_mutex_t res = xbt_new(s_xbt_os_mutex_t,1); int errcode; - + if ((errcode = pthread_mutex_init(&(res->m),NULL))) THROW1(system_error,errcode,"pthread_mutex_init() failed: %s", strerror(errcode)); - + return res; } void xbt_os_mutex_acquire(xbt_os_mutex_t mutex) { int errcode; - + if ((errcode=pthread_mutex_lock(&(mutex->m)))) THROW2(system_error,errcode,"pthread_mutex_lock(%p) failed: %s", mutex, strerror(errcode)); } +void xbt_os_mutex_tryacquire(xbt_os_mutex_t mutex) { + int errcode; + + if ((errcode=pthread_mutex_trylock(&(mutex->m)))) + THROW2(system_error,errcode,"pthread_mutex_trylock(%p) failed: %s", + mutex, strerror(errcode)); +} + + + +#ifndef HAVE_PTHREAD_MUTEX_TIMEDLOCK +/* if the function is not availabled or if is MAC OS X use pthread_mutex_trylock() to define + * it. + */ +#include /* declaration of the timespec structure and of the nanosleep() function */ +int pthread_mutex_timedlock(pthread_mutex_t * mutex, const struct timespec * abs_timeout) +{ + int rv; + long ellapsed_time = 0; + struct timespec ts; + + do + { + /* the mutex could not be acquired because it was already locked by an other thread */ + rv = pthread_mutex_trylock(mutex); + + ts.tv_sec = 0; + ts.tv_nsec = 2.5e7 /* (25 ms (2 context switch + 5 ms)) */; + + do + { + nanosleep(&ts, &ts); + }while(EINTR == errno); + + ellapsed_time += ts.tv_nsec; + + } + while (/* locked */ (EBUSY == rv) && /* !timeout */ (ellapsed_time < ((long)(abs_timeout->tv_sec * 1e9) + abs_timeout->tv_nsec))); + + return (EBUSY == rv) ? ETIMEDOUT : rv; +} + +#endif + +void xbt_os_mutex_timedacquire(xbt_os_mutex_t mutex, double delay) { + int errcode; + struct timespec ts_end; + double end = delay + xbt_os_time(); + + if (delay < 0) { + xbt_os_mutex_acquire(mutex); + } else { + ts_end.tv_sec = (time_t) floor(end); + ts_end.tv_nsec = (long) ( ( end - ts_end.tv_sec) * 1000000000); + DEBUG2("pthread_mutex_timedlock(%p,%p)",&(mutex->m), &ts_end); + + switch ((errcode=pthread_mutex_timedlock(&(mutex->m),&ts_end))) + { + case 0: + return; + + case ETIMEDOUT: + THROW2(timeout_error,errcode,"mutex %p wasn't signaled before timeout (%f)",mutex,delay); + + default: + THROW3(system_error,errcode,"pthread_mutex_timedlock(%p,%f) failed: %s",mutex,delay, strerror(errcode)); + } + } +} + void xbt_os_mutex_release(xbt_os_mutex_t mutex) { int errcode; - + if ((errcode=pthread_mutex_unlock(&(mutex->m)))) THROW2(system_error,errcode,"pthread_mutex_unlock(%p) failed: %s", mutex, strerror(errcode)); @@ -200,9 +291,9 @@ void xbt_os_mutex_release(xbt_os_mutex_t mutex) { void xbt_os_mutex_destroy(xbt_os_mutex_t mutex) { int errcode; - + if (!mutex) return; - + if ((errcode=pthread_mutex_destroy(&(mutex->m)))) THROW2(system_error,errcode,"pthread_mutex_destroy(%p) failed: %s", mutex, strerror(errcode)); @@ -232,13 +323,12 @@ void xbt_os_cond_wait(xbt_os_cond_t cond, xbt_os_mutex_t mutex) { cond,mutex, strerror(errcode)); } -#include -#include + void xbt_os_cond_timedwait(xbt_os_cond_t cond, xbt_os_mutex_t mutex, double delay) { int errcode; struct timespec ts_end; double end = delay + xbt_os_time(); - + if (delay < 0) { xbt_os_cond_wait(cond,mutex); } else { @@ -254,7 +344,7 @@ void xbt_os_cond_timedwait(xbt_os_cond_t cond, xbt_os_mutex_t mutex, double dela default: THROW4(system_error,errcode,"pthread_cond_timedwait(%p,%p,%f) failed: %s", cond,mutex, delay, strerror(errcode)); - } + } } } @@ -264,7 +354,7 @@ void xbt_os_cond_signal(xbt_os_cond_t cond) { THROW2(system_error,errcode,"pthread_cond_signal(%p) failed: %s", cond, strerror(errcode)); } - + void xbt_os_cond_broadcast(xbt_os_cond_t cond){ int errcode; if ((errcode=pthread_cond_broadcast(&(cond->c)))) @@ -288,101 +378,197 @@ void *xbt_os_thread_getparam(void) { } typedef struct xbt_os_sem_ { + #ifndef HAVE_SEM_WAIT + char* name; + sem_t* s; + #else sem_t s; + #endif }s_xbt_os_sem_t ; xbt_os_sem_t xbt_os_sem_init(unsigned int value) { xbt_os_sem_t res = xbt_new(s_xbt_os_sem_t,1); - + int errcode; + + /* On MAC OS X, it seems that the sem_init is failing with ENOSYS, + * which means the sem_init function is not implemented use sem_open() + * instead + */ + #ifndef HAVE_SEM_INIT + res->name = (char*) calloc(MAX_SEM_NAME + 1,sizeof(char)); + + if((errcode = pthread_mutex_lock(&__next_sem_ID_lock))) + THROW1(system_error,errcode,"pthread_mutex_lock() failed: %s", strerror(errcode)); + + __next_sem_ID++; + + if((errcode = pthread_mutex_unlock(&__next_sem_ID_lock))) + THROW1(system_error,errcode,"pthread_mutex_unlock() failed: %s", strerror(errcode)); + + sprintf(res->name,"/%d",__next_sem_ID); + + if((res->s = sem_open(res->name, O_CREAT | O_EXCL, 0644, value)) == (sem_t*)SEM_FAILED) + THROW1(system_error,errno,"sem_open() failed: %s",strerror(errno)); + + #else + /* sem_init() is implemented, use it */ if(sem_init(&(res->s),0,value) < 0) THROW1(system_error,errno,"sem_init() failed: %s", strerror(errno)); - + #endif + return res; } -void +void xbt_os_sem_acquire(xbt_os_sem_t sem) { if(!sem) THROW0(arg_error,EINVAL,"Cannot acquire of the NULL semaphore"); - + #ifndef HAVE_SEM_WAIT + if(sem_wait((sem->s)) < 0) + THROW1(system_error,errno,"sem_wait() failed: %s", + strerror(errno)); + #else if(sem_wait(&(sem->s)) < 0) THROW1(system_error,errno,"sem_wait() failed: %s", - strerror(errno)); + strerror(errno)); + #endif } +#ifndef HAVE_SEM_TIMEDWAIT +/* if the function is not availabled or if is MAC OS X use sem_trywait() to define + * it. + */ +#include /* declaration of the timespec structure and of the nanosleep() function */ +int sem_timedwait(sem_t* sem, const struct timespec * abs_timeout) +{ + int rv; + long ellapsed_time = 0; + struct timespec ts; + + do + { + rv = sem_trywait(sem); + ts.tv_sec = 0; + ts.tv_nsec = 2.5e7 /* (25 ms (2 * context switch + 5 ms)) */; + + do + { + nanosleep(&ts, &ts); + }while(EINTR == errno); + + ellapsed_time += ts.tv_nsec; + + } + while(/* locked */ (EAGAIN == rv) && /* !timeout */ ellapsed_time < ((long)(abs_timeout->tv_sec * 1e9) + abs_timeout->tv_nsec)); + + return (EAGAIN == rv) ? ETIMEDOUT : rv; +} + +#endif + void xbt_os_sem_timedacquire(xbt_os_sem_t sem,double timeout) { int errcode; struct timespec ts_end; double end = timeout + xbt_os_time(); - + if(!sem) THROW0(arg_error,EINVAL,"Cannot acquire of the NULL semaphore"); - - if (timeout < 0) + + if (timeout < 0) { xbt_os_sem_acquire(sem); - } - else + } + else { - /* mac os x have not the sem_timedwait() function */ -#ifndef HAVE_SEM_TIMEDWAIT - THROW_UNIMPLEMENTED; -#else ts_end.tv_sec = (time_t) floor(end); ts_end.tv_nsec = (long) ( ( end - ts_end.tv_sec) * 1000000000); DEBUG2("sem_timedwait(%p,%p)",&(sem->s),&ts_end); - - switch ((errcode=sem_timedwait(&(sem->s),&ts_end))) + + #ifndef HAVE_SEM_WAIT + switch ((errcode = sem_timedwait(sem->s,&ts_end))) + #else + switch ((errcode = sem_timedwait(&(sem->s),&ts_end))) + #endif { case 0: return; - + case ETIMEDOUT: THROW2(timeout_error,errcode,"semaphore %p wasn't signaled before timeout (%f)",sem,timeout); - + default: THROW3(system_error,errcode,"sem_timedwait(%p,%f) failed: %s",sem,timeout, strerror(errcode)); - } -#endif + } } + } -void +void xbt_os_sem_release(xbt_os_sem_t sem) { if(!sem) THROW0(arg_error,EINVAL,"Cannot release of the NULL semaphore"); - + + #ifndef HAVE_SEM_WAIT + if(sem_post((sem->s)) < 0) + THROW1(system_error,errno,"sem_post() failed: %s", + strerror(errno)); + #else if(sem_post(&(sem->s)) < 0) THROW1(system_error,errno,"sem_post() failed: %s", - strerror(errno)); + strerror(errno)); + #endif } void xbt_os_sem_destroy(xbt_os_sem_t sem) { - if(!sem) - return; - + if(!sem) + THROW0(arg_error,EINVAL,"Cannot destroy the NULL sempahore"); + + #ifndef HAVE_SEM_WAIT + /* MAC OS X does not implement the sem_init() function so, + * we use the named semaphore (sem_open) + */ + if(sem_close((sem->s)) < 0) + THROW1(system_error,errno,"sem_close() failed: %s", + strerror(errno)); + + if(sem_unlink(sem->name) < 0) + THROW1(system_error,errno,"sem_unlink() failed: %s", + strerror(errno)); + + xbt_free(sem->name); + + #else if(sem_destroy(&(sem->s)) < 0) THROW1(system_error,errno,"sem_destroy() failed: %s", strerror(errno)); + #endif + + xbt_free(sem); } void xbt_os_sem_get_value(xbt_os_sem_t sem, int* svalue) { if(!sem) - THROW0(arg_error,EINVAL,"Cannot get the value of the NULL semaphore"); - + THROW0(arg_error,EINVAL,"Cannot get the value of the NULL semaphore",); + + #ifndef HAVE_SEM_WAIT + if(sem_getvalue((sem->s),svalue) < 0) + THROW1(system_error,errno,"sem_getvalue() failed: %s", + strerror(errno)); + #else if(sem_getvalue(&(sem->s),svalue) < 0) THROW1(system_error,errno,"sem_getvalue() failed: %s", strerror(errno)); + #endif } /* ********************************* WINDOWS IMPLEMENTATION ************************************ */ @@ -414,18 +600,18 @@ void xbt_os_thread_mod_init(void) { xbt_self_thread_key = TlsAlloc(); } void xbt_os_thread_mod_exit(void) { - - if (!TlsFree(xbt_self_thread_key)) + + if (!TlsFree(xbt_self_thread_key)) THROW0(system_error,(int)GetLastError(),"TlsFree() failed to cleanup the thread submodule"); } static DWORD WINAPI wrapper_start_routine(void *s) { xbt_os_thread_t t = (xbt_os_thread_t)s; void* rv; - + if(!TlsSetValue(xbt_self_thread_key,t)) THROW0(system_error,(int)GetLastError(),"TlsSetValue of data describing the created thread failed"); - + rv = (*(t->start_routine))(t->param); return *((DWORD*)rv); @@ -434,22 +620,22 @@ static DWORD WINAPI wrapper_start_routine(void *s) { xbt_os_thread_t xbt_os_thread_create(const char *name,pvoid_f_pvoid_t start_routine, void* param) { - + xbt_os_thread_t t = xbt_new(s_xbt_os_thread_t,1); t->name = xbt_strdup(name); t->start_routine = start_routine ; t->param = param; - + t->handle = CreateThread(NULL,XBT_DEFAULT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)wrapper_start_routine, t,STACK_SIZE_PARAM_IS_A_RESERVATION,&(t->id)); - + if(!t->handle) { xbt_free(t); THROW0(system_error,(int)GetLastError(),"CreateThread failed"); } - + return t; } @@ -462,18 +648,18 @@ const char* xbt_os_thread_self_name(void) { return t?t->name:"main"; } -void +void xbt_os_thread_join(xbt_os_thread_t thread,void ** thread_return) { - if(WAIT_OBJECT_0 != WaitForSingleObject(thread->handle,INFINITE)) + if(WAIT_OBJECT_0 != WaitForSingleObject(thread->handle,INFINITE)) THROW0(system_error,(int)GetLastError(), "WaitForSingleObject failed"); - + if(thread_return){ - + if(!GetExitCodeThread(thread->handle,(DWORD*)(*thread_return))) THROW0(system_error,(int)GetLastError(), "GetExitCodeThread failed"); } - + CloseHandle(thread->handle); free(thread->name); free(thread); @@ -506,7 +692,7 @@ void xbt_os_thread_cancel(xbt_os_thread_t t) { /****** mutex related functions ******/ typedef struct xbt_os_mutex_ { /* KEEP IT IN SYNC WITH xbt_thread.c */ - CRITICAL_SECTION lock; + CRITICAL_SECTION lock; } s_xbt_os_mutex_t; xbt_os_mutex_t xbt_os_mutex_init(void) { @@ -514,7 +700,7 @@ xbt_os_mutex_t xbt_os_mutex_init(void) { /* initialize the critical section object */ InitializeCriticalSection(&(res->lock)); - + return res; } @@ -523,6 +709,15 @@ void xbt_os_mutex_acquire(xbt_os_mutex_t mutex) { EnterCriticalSection(& mutex->lock); } +void xbt_os_mutex_tryacquire(xbt_os_mutex_t mutex) +{ + TryEnterCriticalSection(&mutex->lock); +} + +void xbt_os_mutex_timedacquire(xbt_os_mutex_t mutex, double delay) { + THROW_UNIMPLEMENTED; +} + void xbt_os_mutex_release(xbt_os_mutex_t mutex) { LeaveCriticalSection (& mutex->lock); @@ -532,8 +727,8 @@ void xbt_os_mutex_release(xbt_os_mutex_t mutex) { void xbt_os_mutex_destroy(xbt_os_mutex_t mutex) { if (!mutex) return; - - DeleteCriticalSection(& mutex->lock); + + DeleteCriticalSection(& mutex->lock); free(mutex); } @@ -547,7 +742,7 @@ void xbt_os_mutex_destroy(xbt_os_mutex_t mutex) { typedef struct xbt_os_cond_ { /* KEEP IT IN SYNC WITH xbt_thread.c */ HANDLE events[MAX_EVENTS]; - + unsigned int waiters_count; /* the number of waiters */ CRITICAL_SECTION waiters_count_lock; /* protect access to waiters_count */ } s_xbt_os_cond_t; @@ -555,31 +750,31 @@ typedef struct xbt_os_cond_ { xbt_os_cond_t xbt_os_cond_init(void) { xbt_os_cond_t res = xbt_new0(s_xbt_os_cond_t,1); - + memset(& res->waiters_count_lock,0,sizeof(CRITICAL_SECTION)); - + /* initialize the critical section object */ InitializeCriticalSection(& res->waiters_count_lock); - + res->waiters_count = 0; - + /* Create an auto-reset event */ - res->events[SIGNAL] = CreateEvent (NULL, FALSE, FALSE, NULL); - + res->events[SIGNAL] = CreateEvent (NULL, FALSE, FALSE, NULL); + if(!res->events[SIGNAL]){ DeleteCriticalSection(& res->waiters_count_lock); free(res); THROW0(system_error,0,"CreateEvent failed for the signals"); } - + /* Create a manual-reset event. */ res->events[BROADCAST] = CreateEvent (NULL, TRUE, FALSE,NULL); - + if(!res->events[BROADCAST]){ - - DeleteCriticalSection(& res->waiters_count_lock); + + DeleteCriticalSection(& res->waiters_count_lock); CloseHandle(res->events[SIGNAL]); - free(res); + free(res); THROW0(system_error,0,"CreateEvent failed for the broadcasts"); } @@ -587,7 +782,7 @@ xbt_os_cond_t xbt_os_cond_init(void) { } void xbt_os_cond_wait(xbt_os_cond_t cond, xbt_os_mutex_t mutex) { - + unsigned long wait_result; int is_last_waiter; @@ -595,38 +790,38 @@ void xbt_os_cond_wait(xbt_os_cond_t cond, xbt_os_mutex_t mutex) { EnterCriticalSection (& cond->waiters_count_lock); cond->waiters_count++; LeaveCriticalSection (& cond->waiters_count_lock); - + /* unlock the mutex associate with the condition */ LeaveCriticalSection (& mutex->lock); - + /* wait for a signal (broadcast or no) */ wait_result = WaitForMultipleObjects (2, cond->events, FALSE, INFINITE); - + if(wait_result == WAIT_FAILED) THROW0(system_error,0,"WaitForMultipleObjects failed, so we cannot wait on the condition"); - + /* we have a signal lock the condition */ EnterCriticalSection (& cond->waiters_count_lock); cond->waiters_count--; - + /* it's the last waiter or it's a broadcast ? */ is_last_waiter = ((wait_result == WAIT_OBJECT_0 + BROADCAST - 1) && (cond->waiters_count == 0)); - + LeaveCriticalSection (& cond->waiters_count_lock); - + /* yes it's the last waiter or it's a broadcast * only reset the manual event (the automatic event is reset in the WaitForMultipleObjects() function - * by the system. + * by the system. */ if (is_last_waiter) if(!ResetEvent (cond->events[BROADCAST])) THROW0(system_error,0,"ResetEvent failed"); - + /* relock the mutex associated with the condition in accordance with the posix thread specification */ EnterCriticalSection (& mutex->lock); } void xbt_os_cond_timedwait(xbt_os_cond_t cond, xbt_os_mutex_t mutex, double delay) { - + unsigned long wait_result = WAIT_TIMEOUT; int is_last_waiter; unsigned long end = (unsigned long)(delay * 1000); @@ -641,37 +836,37 @@ void xbt_os_cond_timedwait(xbt_os_cond_t cond, xbt_os_mutex_t mutex, double dela EnterCriticalSection (& cond->waiters_count_lock); cond->waiters_count++; LeaveCriticalSection (& cond->waiters_count_lock); - + /* unlock the mutex associate with the condition */ LeaveCriticalSection (& mutex->lock); /* wait for a signal (broadcast or no) */ - + wait_result = WaitForMultipleObjects (2, cond->events, FALSE, end); - + switch(wait_result) { case WAIT_TIMEOUT: THROW3(timeout_error,GetLastError(),"condition %p (mutex %p) wasn't signaled before timeout (%f)",cond,mutex, delay); case WAIT_FAILED: THROW0(system_error,GetLastError(),"WaitForMultipleObjects failed, so we cannot wait on the condition"); } - + /* we have a signal lock the condition */ EnterCriticalSection (& cond->waiters_count_lock); cond->waiters_count--; - + /* it's the last waiter or it's a broadcast ? */ is_last_waiter = ((wait_result == WAIT_OBJECT_0 + BROADCAST - 1) && (cond->waiters_count == 0)); - + LeaveCriticalSection (& cond->waiters_count_lock); - + /* yes it's the last waiter or it's a broadcast * only reset the manual event (the automatic event is reset in the WaitForMultipleObjects() function - * by the system. + * by the system. */ if (is_last_waiter) if(!ResetEvent (cond->events[BROADCAST])) THROW0(system_error,0,"ResetEvent failed"); - + /* relock the mutex associated with the condition in accordance with the posix thread specification */ EnterCriticalSection (& mutex->lock); } @@ -684,11 +879,11 @@ void xbt_os_cond_signal(xbt_os_cond_t cond) { EnterCriticalSection (& cond->waiters_count_lock); have_waiters = cond->waiters_count > 0; LeaveCriticalSection (& cond->waiters_count_lock); - + if (have_waiters) if(!SetEvent(cond->events[SIGNAL])) THROW0(system_error,0,"SetEvent failed"); - + xbt_os_thread_yield(); } @@ -698,26 +893,26 @@ void xbt_os_cond_broadcast(xbt_os_cond_t cond){ EnterCriticalSection (& cond->waiters_count_lock); have_waiters = cond->waiters_count > 0; LeaveCriticalSection (& cond->waiters_count_lock); - + if (have_waiters) SetEvent(cond->events[BROADCAST]); } void xbt_os_cond_destroy(xbt_os_cond_t cond){ int error = 0; - + if (!cond) return; - + if(!CloseHandle(cond->events[SIGNAL])) error = 1; - + if(!CloseHandle(cond->events[BROADCAST])) error = 1; - + DeleteCriticalSection(& cond->waiters_count_lock); - + xbt_free(cond); - + if (error) THROW0(system_error,0,"Error while destroying the condition"); } @@ -732,27 +927,26 @@ xbt_os_sem_t xbt_os_sem_init(unsigned int value) { xbt_os_sem_t res; - + if(value > INT_MAX) - THROW1(arg_error,value,"Semaphore initial value too big: %ud cannot be stored as a signed int", - value); - + THROW1(arg_error,value,"Semaphore initial value too big: %ud cannot be stored as a signed int",value); + res = (xbt_os_sem_t)xbt_new0(s_xbt_os_sem_t,1); - + if(!(res->h = CreateSemaphore(NULL,value,(long)INT_MAX,NULL))) { THROW1(system_error,GetLastError(),"CreateSemaphore() failed: %s", strerror(GetLastError())); return NULL; } - + res->value = value; - + InitializeCriticalSection(&(res->value_lock)); - + return res; } -void +void xbt_os_sem_acquire(xbt_os_sem_t sem) { if(!sem) @@ -772,21 +966,21 @@ void xbt_os_sem_timedacquire(xbt_os_sem_t sem, double timeout) long seconds; long milliseconds; double end = timeout + xbt_os_time(); - + if(!sem) THROW0(arg_error,EINVAL,"Cannot acquire the NULL semaphore"); - - if (timeout < 0) + + if (timeout < 0) { xbt_os_sem_acquire(sem); - } - else + } + else { - + seconds = (long) floor(end); milliseconds = (long)( ( end - seconds) * 1000); milliseconds += (seconds * 1000); - + switch(WaitForSingleObject(sem->h,milliseconds)) { case WAIT_OBJECT_0: @@ -794,25 +988,25 @@ void xbt_os_sem_timedacquire(xbt_os_sem_t sem, double timeout) sem->value--; LeaveCriticalSection(&(sem->value_lock)); return; - + case WAIT_TIMEOUT: THROW2(timeout_error,GetLastError(),"semaphore %p wasn't signaled before timeout (%f)",sem,timeout); return; - + default: - + THROW3(system_error,GetLastError(),"WaitForSingleObject(%p,%f) failed: %s",sem,timeout, strerror(GetLastError())); } } } -void +void xbt_os_sem_release(xbt_os_sem_t sem) { if(!sem) THROW0(arg_error,EINVAL,"Cannot release the NULL semaphore"); - - if(!ReleaseSemaphore(sem->h,1, NULL)) + + if(!ReleaseSemaphore(sem->h,1, NULL)) THROW1(system_error,GetLastError(),"ReleaseSemaphore() failed: %s", strerror(GetLastError())); EnterCriticalSection (&(sem->value_lock)); @@ -823,16 +1017,17 @@ xbt_os_sem_release(xbt_os_sem_t sem) void xbt_os_sem_destroy(xbt_os_sem_t sem) { - if(!sem) return; - - if(!CloseHandle(sem->h)) + if(!sem) + THROW0(arg_error,EINVAL,"Cannot destroy the NULL semaphore"); + + if(!CloseHandle(sem->h)) THROW1(system_error,GetLastError(),"CloseHandle() failed: %s", strerror(GetLastError())); - + DeleteCriticalSection(&(sem->value_lock)); - - xbt_free(sem); - + + xbt_free(sem); + } void @@ -840,8 +1035,8 @@ xbt_os_sem_get_value(xbt_os_sem_t sem, int* svalue) { if(!sem) THROW0(arg_error,EINVAL,"Cannot get the value of the NULL semaphore"); - - EnterCriticalSection(&(sem->value_lock)); + + EnterCriticalSection(&(sem->value_lock)); *svalue = sem->value; LeaveCriticalSection(&(sem->value_lock)); } diff --git a/src/xbt/xbt_queue.c b/src/xbt/xbt_queue.c index 8321246017..6a48254a67 100644 --- a/src/xbt/xbt_queue.c +++ b/src/xbt/xbt_queue.c @@ -59,9 +59,9 @@ void xbt_queue_free(xbt_queue_t *queue) { /** @brief Get the queue size */ unsigned long xbt_queue_length(const xbt_queue_t queue) { unsigned long res; - xbt_mutex_lock(queue->mutex); + xbt_mutex_acquire(queue->mutex); res=xbt_dynar_length(queue->data); - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); return res; } @@ -72,14 +72,14 @@ unsigned long xbt_queue_length(const xbt_queue_t queue) { * @see #xbt_dynar_push */ void xbt_queue_push(xbt_queue_t queue, const void *src) { - xbt_mutex_lock(queue->mutex); + xbt_mutex_acquire(queue->mutex); while (queue->capacity != 0 && queue->capacity == xbt_dynar_length(queue->data)) { DEBUG2("Capacity of %p exceded (=%d). Waiting",queue,queue->capacity); xbt_cond_wait(queue->not_full,queue->mutex); } xbt_dynar_push(queue->data,src); xbt_cond_signal(queue->not_empty); - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); } @@ -91,14 +91,14 @@ void xbt_queue_push(xbt_queue_t queue, const void *src) { * */ void xbt_queue_pop(xbt_queue_t queue, void* const dst) { - xbt_mutex_lock(queue->mutex); + xbt_mutex_acquire(queue->mutex); while (xbt_dynar_length(queue->data) == 0) { DEBUG1("Queue %p empty. Waiting",queue); xbt_cond_wait(queue->not_empty,queue->mutex); } xbt_dynar_pop(queue->data,dst); xbt_cond_signal(queue->not_full); - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); } /** @brief Unshift something to the message exchange queue. @@ -108,14 +108,14 @@ void xbt_queue_pop(xbt_queue_t queue, void* const dst) { * @see #xbt_dynar_unshift */ void xbt_queue_unshift(xbt_queue_t queue, const void *src) { - xbt_mutex_lock(queue->mutex); + xbt_mutex_acquire(queue->mutex); while (queue->capacity != 0 && queue->capacity == xbt_dynar_length(queue->data)) { DEBUG2("Capacity of %p exceded (=%d). Waiting",queue,queue->capacity); xbt_cond_wait(queue->not_full,queue->mutex); } xbt_dynar_unshift(queue->data,src); xbt_cond_signal(queue->not_empty); - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); } @@ -127,14 +127,14 @@ void xbt_queue_unshift(xbt_queue_t queue, const void *src) { * */ void xbt_queue_shift(xbt_queue_t queue, void* const dst) { - xbt_mutex_lock(queue->mutex); + xbt_mutex_acquire(queue->mutex); while (xbt_dynar_length(queue->data) == 0) { DEBUG1("Queue %p empty. Waiting",queue); xbt_cond_wait(queue->not_empty,queue->mutex); } xbt_dynar_shift(queue->data,dst); xbt_cond_signal(queue->not_full); - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); } @@ -148,13 +148,13 @@ void xbt_queue_push_timed(xbt_queue_t queue, const void *src,double delay) { double timeout = xbt_time() + delay; xbt_ex_t e; - xbt_mutex_lock(queue->mutex); + xbt_mutex_acquire(queue->mutex); if (delay == 0) { if (queue->capacity != 0 && queue->capacity == xbt_dynar_length(queue->data)) { - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); THROW2(timeout_error,0,"Capacity of %p exceded (=%d), and delay = 0", queue,queue->capacity); } @@ -169,7 +169,7 @@ void xbt_queue_push_timed(xbt_queue_t queue, const void *src,double delay) { xbt_cond_timedwait(queue->not_full,queue->mutex, delay < 0 ? -1 : timeout - xbt_time()); } CATCH(e) { - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); RETHROW; } } @@ -177,7 +177,7 @@ void xbt_queue_push_timed(xbt_queue_t queue, const void *src,double delay) { xbt_dynar_push(queue->data,src); xbt_cond_signal(queue->not_empty); - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); } @@ -190,11 +190,11 @@ void xbt_queue_pop_timed(xbt_queue_t queue, void* const dst,double delay) { double timeout = xbt_time() + delay; xbt_ex_t e; - xbt_mutex_lock(queue->mutex); + xbt_mutex_acquire(queue->mutex); if (delay == 0) { if (xbt_dynar_length(queue->data) == 0) { - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); THROW0(timeout_error,0,"Delay = 0, and queue is empty"); } } else { @@ -205,7 +205,7 @@ void xbt_queue_pop_timed(xbt_queue_t queue, void* const dst,double delay) { xbt_cond_timedwait(queue->not_empty,queue->mutex, delay<0 ? -1 : timeout - xbt_time()); } CATCH(e) { - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); RETHROW; } } @@ -213,7 +213,7 @@ void xbt_queue_pop_timed(xbt_queue_t queue, void* const dst,double delay) { xbt_dynar_pop(queue->data,dst); xbt_cond_signal(queue->not_full); - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); } /** @brief Unshift something to the message exchange queue, with a timeout. @@ -224,13 +224,13 @@ void xbt_queue_unshift_timed(xbt_queue_t queue, const void *src,double delay) { double timeout = xbt_time() + delay; xbt_ex_t e; - xbt_mutex_lock(queue->mutex); + xbt_mutex_acquire(queue->mutex); if (delay==0) { if (queue->capacity != 0 && queue->capacity == xbt_dynar_length(queue->data)) { - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); THROW2(timeout_error,0,"Capacity of %p exceded (=%d), and delay = 0", queue,queue->capacity); } @@ -245,7 +245,7 @@ void xbt_queue_unshift_timed(xbt_queue_t queue, const void *src,double delay) { xbt_cond_timedwait(queue->not_full,queue->mutex, delay < 0 ? -1 : timeout - xbt_time()); } CATCH(e) { - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); RETHROW; } } @@ -253,7 +253,7 @@ void xbt_queue_unshift_timed(xbt_queue_t queue, const void *src,double delay) { xbt_dynar_unshift(queue->data,src); xbt_cond_signal(queue->not_empty); - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); } @@ -266,11 +266,11 @@ void xbt_queue_shift_timed(xbt_queue_t queue, void* const dst,double delay) { double timeout = xbt_time() + delay; xbt_ex_t e; - xbt_mutex_lock(queue->mutex); + xbt_mutex_acquire(queue->mutex); if (delay == 0) { if (xbt_dynar_length(queue->data) == 0) { - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); THROW0(timeout_error,0,"Delay = 0, and queue is empty"); } } else { @@ -281,7 +281,7 @@ void xbt_queue_shift_timed(xbt_queue_t queue, void* const dst,double delay) { xbt_cond_timedwait(queue->not_empty,queue->mutex, delay<0 ? -1 : timeout - xbt_time()); } CATCH(e) { - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); RETHROW; } } @@ -289,5 +289,5 @@ void xbt_queue_shift_timed(xbt_queue_t queue, void* const dst,double delay) { xbt_dynar_shift(queue->data,dst); xbt_cond_signal(queue->not_full); - xbt_mutex_unlock(queue->mutex); + xbt_mutex_release(queue->mutex); } diff --git a/src/xbt/xbt_rl_synchro.c b/src/xbt/xbt_rl_synchro.c index c273ccf1d7..d9a902473e 100644 --- a/src/xbt/xbt_rl_synchro.c +++ b/src/xbt/xbt_rl_synchro.c @@ -93,12 +93,23 @@ xbt_mutex_t xbt_mutex_init(void) { return res; } -void xbt_mutex_lock(xbt_mutex_t mutex) { - DEBUG1("Lock mutex %p", mutex); +void xbt_mutex_acquire(xbt_mutex_t mutex) { + DEBUG1("Acquire mutex %p", mutex); xbt_os_mutex_acquire( (xbt_os_mutex_t)mutex ); } -void xbt_mutex_unlock(xbt_mutex_t mutex) { +void xbt_mutex_tryacquire(xbt_mutex_t mutex) { + DEBUG1("Try acquire mutex %p", mutex); + xbt_os_mutex_tryacquire( (xbt_os_mutex_t)mutex ); +} + +void xbt_mutex_timedacquire(xbt_mutex_t mutex, double delay) +{ + DEBUG2("Acquire mutex %p with delay %lf", mutex,delay); + xbt_os_mutex_timedacquire( (xbt_os_mutex_t)mutex,delay ); +} + +void xbt_mutex_release(xbt_mutex_t mutex) { DEBUG1("Unlock mutex %p", mutex); xbt_os_mutex_release( (xbt_os_mutex_t)mutex ); } diff --git a/src/xbt/xbt_sg_synchro.c b/src/xbt/xbt_sg_synchro.c index 09e89619eb..65e69d9589 100644 --- a/src/xbt/xbt_sg_synchro.c +++ b/src/xbt/xbt_sg_synchro.c @@ -98,11 +98,11 @@ xbt_mutex_t xbt_mutex_init(void) { return (xbt_mutex_t)SIMIX_mutex_init(); } -void xbt_mutex_lock(xbt_mutex_t mutex) { +void xbt_mutex_acquire(xbt_mutex_t mutex) { SIMIX_mutex_lock( (smx_mutex_t)mutex) ; } -void xbt_mutex_unlock(xbt_mutex_t mutex) { +void xbt_mutex_release(xbt_mutex_t mutex) { SIMIX_mutex_unlock( (smx_mutex_t)mutex ); } diff --git a/teshsuite/xbt/parallel_log_crashtest.c b/teshsuite/xbt/parallel_log_crashtest.c index 8749dac50a..05f724f5cc 100644 --- a/teshsuite/xbt/parallel_log_crashtest.c +++ b/teshsuite/xbt/parallel_log_crashtest.c @@ -36,10 +36,10 @@ static void crasher_thread(void *arg) { INFO10("%03d (%02d|%02d|%02d|%02d|%02d|%02d|%02d|%02d|%02d)",test_amount-i,id,id,id,id,id,id,id,id,id); } - xbt_mutex_lock(mut_end); + xbt_mutex_acquire(mut_end); running_threads--; xbt_cond_signal(cond_end); - xbt_mutex_unlock(mut_end); + xbt_mutex_release(mut_end); } int crasher (int argc,char *argv[]); @@ -69,10 +69,10 @@ int crasher (int argc,char *argv[]) { } /* wait for them */ - xbt_mutex_lock(mut_end); + xbt_mutex_acquire(mut_end); while (running_threads) xbt_cond_wait(cond_end,mut_end); - xbt_mutex_unlock(mut_end); + xbt_mutex_release(mut_end); gras_exit(); return 0; -- 2.20.1