it is used to control the inline semantics. By combining it with optimize option "-fno-inline" for the internal M*LIB function (using _Pragma optimize) we can control easily if we want to use an inline version or call of an external function. Define new user API: * M_USE_EXTERN_DECL : define it before including any MLIB header to requests to call the M*LIB functions and don't inline then. * M_USE_EXTERN_DEF : define it once in a translation unit to define the M*LIB functions. This translation unit shall contain all templated macro too. If you use one option, you need to use the other one too for another translation unit.
926 lines
66 KiB
C
926 lines
66 KiB
C
/*
|
|
* M*LIB - Basic Protected Concurrent module over container.
|
|
*
|
|
* Copyright (c) 2017-2023, Patrick Pelissier
|
|
* All rights reserved.
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* + Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* + 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 REGENTS 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 REGENTS AND 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.
|
|
*/
|
|
#ifndef MSTARLIB_CONCURRENT_H
|
|
#define MSTARLIB_CONCURRENT_H
|
|
|
|
#include "m-core.h"
|
|
#include "m-thread.h"
|
|
#include "m-atomic.h"
|
|
|
|
/* Define a protected concurrent container and its associated functions
|
|
based on the given container.
|
|
USAGE: CONCURRENT_DEF(name, type [, oplist_of_the_type]) */
|
|
#define M_CONCURRENT_DEF(name, ...) \
|
|
M_CONCURRENT_DEF_AS(name, M_F(name,_t), __VA_ARGS__)
|
|
|
|
|
|
/* Define a protected concurrent container and its associated functions
|
|
based on the given container as the given name name_t
|
|
USAGE: CONCURRENT_DEF_AS(name, name_t, type [, oplist_of_the_type]) */
|
|
#define M_CONCURRENT_DEF_AS(name, name_t, ...) \
|
|
M_BEGIN_PROTECTED_CODE \
|
|
M_C0NCURRENT_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
|
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_t ), \
|
|
(name, __VA_ARGS__, name_t ))) \
|
|
M_END_PROTECTED_CODE
|
|
|
|
|
|
/* Define a protected concurrent container and its associated functions
|
|
based on its given container. Operations that perform only read of the container
|
|
can be done in parallel.
|
|
USAGE: CONCURRENT_RP_DEF(name, type [, oplist_of_the_type]) */
|
|
#define M_CONCURRENT_RP_DEF(name, ...) \
|
|
M_CONCURRENT_RP_DEF_AS(name, M_F(name,_t), __VA_ARGS__)
|
|
|
|
|
|
/* Define a protected concurrent container and its associated functions
|
|
as the given name name_t
|
|
based on its given container. Operations that perform only read of the container
|
|
can be done in parallel.
|
|
USAGE: CONCURRENT_RP_DEF_AS(name, name_t, type [, oplist_of_the_type]) */
|
|
#define M_CONCURRENT_RP_DEF_AS(name, name_t, ...) \
|
|
M_BEGIN_PROTECTED_CODE \
|
|
M_C0NCURRENT_RP_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
|
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_t ), \
|
|
(name, __VA_ARGS__, name_t ))) \
|
|
M_END_PROTECTED_CODE
|
|
|
|
|
|
/* Define the oplist of a protected concurrent container given its name and its oplist.
|
|
USAGE: CONCURRENT_OPLIST(name[, oplist of the type]) */
|
|
#define M_CONCURRENT_OPLIST(...) \
|
|
M_C0NCURRENT_OPLIST_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
|
((__VA_ARGS__, M_BASIC_OPLIST), \
|
|
(__VA_ARGS__ )))
|
|
|
|
|
|
/*****************************************************************************/
|
|
/******************************** INTERNAL ***********************************/
|
|
/*****************************************************************************/
|
|
|
|
/* Deferred evaluation for the oplist definition,
|
|
so that all arguments are evaluated before further expansion */
|
|
#define M_C0NCURRENT_OPLIST_P1(arg) M_C0NCURRENT_OPLIST_P2 arg
|
|
|
|
/* Validation of the given oplist */
|
|
#define M_C0NCURRENT_OPLIST_P2(name, oplist) \
|
|
M_IF_OPLIST(oplist)(M_C0NCURRENT_OPLIST_P3, M_C0NCURRENT_OPLIST_FAILURE)(name, oplist)
|
|
|
|
/* Prepare a clean compilation failure */
|
|
#define M_C0NCURRENT_OPLIST_FAILURE(name, oplist) \
|
|
((M_LIB_ERROR(ARGUMENT_OF_CONCURRENT_OPLIST_IS_NOT_AN_OPLIST, name, oplist)))
|
|
|
|
/* OPLIST definition
|
|
GET_KEY is not present as its interface is not compatible with a concurrent
|
|
container (_get returns a pointer to an internal data, data that may be
|
|
destroyed by another thread).
|
|
*/
|
|
#define M_C0NCURRENT_OPLIST_P3(name, oplist) \
|
|
(M_IF_METHOD(INIT, oplist)(INIT(M_F(name, _init)),) \
|
|
,M_IF_METHOD(INIT_SET, oplist)(INIT_SET(M_F(name, _init_set)),) \
|
|
,M_IF_METHOD(SET, oplist)(SET(M_F(name, _set)),) \
|
|
,M_IF_METHOD(CLEAR, oplist)(CLEAR(M_F(name, _clear)),) \
|
|
,M_IF_METHOD(INIT_MOVE, oplist)(INIT_MOVE(M_F(name, _init_move)),) \
|
|
,M_IF_METHOD(MOVE, oplist)(MOVE(M_F(name, _move)),) \
|
|
,M_IF_METHOD(SWAP,oplist)(SWAP(M_F(name, _swap)),) \
|
|
,NAME(name) \
|
|
,TYPE(M_F(name,_ct)) \
|
|
,SUBTYPE(M_F(name, _subtype_ct)) \
|
|
,OPLIST(oplist) \
|
|
,M_IF_METHOD(EMPTY_P, oplist)(EMPTY_P(M_F(name,_empty_p)),) \
|
|
,M_IF_METHOD(GET_SIZE, oplist)(GET_SIZE(M_F(name,_size)),) \
|
|
,M_IF_METHOD(RESET, oplist)(RESET(M_F(name,_reset)),) \
|
|
,M_IF_METHOD(KEY_TYPE, oplist)(KEY_TYPE(M_GET_KEY_TYPE oplist),) \
|
|
,M_IF_METHOD(VALUE_TYPE, oplist)(VALUE_TYPE(M_GET_VALUE_TYPE oplist),) \
|
|
,M_IF_METHOD(KEY_TYPE, oplist)(KEY_OPLIST(M_GET_KEY_OPLIST oplist),) \
|
|
,M_IF_METHOD(VALUE_TYPE, oplist)(VALUE_OPLIST(M_GET_VALUE_OPLIST oplist), ) \
|
|
,M_IF_METHOD(SET_KEY, oplist)(SET_KEY(M_F(name, _set_at)),) \
|
|
,M_IF_METHOD(ERASE_KEY, oplist)(ERASE_KEY(M_F(name, _erase)),) \
|
|
,M_IF_METHOD(PUSH, oplist)(PUSH(M_F(name,_push)),) \
|
|
,M_IF_METHOD(POP, oplist)(POP(M_F(name,_pop)),) \
|
|
,M_IF_METHOD(PUSH_MOVE, oplist)(PUSH_MOVE(M_F(name,_push_move)),) \
|
|
,M_IF_METHOD(POP_MOVE, oplist)(POP_MOVE(M_F(name,_pop_move)),) \
|
|
,M_IF_METHOD(GET_STR, oplist)(GET_STR(M_F(name, _get_str)),) \
|
|
,M_IF_METHOD(PARSE_STR, oplist)(PARSE_STR(M_F(name, _parse_str)),) \
|
|
,M_IF_METHOD(OUT_STR, oplist)(OUT_STR(M_F(name, _out_str)),) \
|
|
,M_IF_METHOD(IN_STR, oplist)(IN_STR(M_F(name, _in_str)),) \
|
|
,M_IF_METHOD(OUT_SERIAL, oplist)(OUT_SERIAL(M_F(name, _out_serial)),) \
|
|
,M_IF_METHOD(IN_SERIAL, oplist)(IN_SERIAL(M_F(name, _in_serial)),) \
|
|
,M_IF_METHOD(EQUAL, oplist)(EQUAL(M_F(name, _equal_p)),) \
|
|
,M_IF_METHOD(HASH, oplist)(HASH(M_F(name, _hash)),) \
|
|
)
|
|
|
|
|
|
/******************************** INTERNAL ***********************************/
|
|
|
|
/* Internal contract
|
|
NOTE: Can't check too much without locking the container itself
|
|
*/
|
|
#define M_C0NCURRENT_CONTRACT(c) do { \
|
|
M_ASSERT ((c) != NULL); \
|
|
M_ASSERT ((c)->self == (c)); \
|
|
} while (0)
|
|
|
|
/* Deferred evaluation for the concurrent definition,
|
|
so that all arguments are evaluated before further expansion */
|
|
#define M_C0NCURRENT_DEF_P1(arg) M_ID( M_C0NCURRENT_DEF_P2 arg )
|
|
|
|
/* Validate the value oplist before going further */
|
|
#define M_C0NCURRENT_DEF_P2(name, type, oplist, concurrent_t) \
|
|
M_IF_OPLIST(oplist)(M_C0NCURRENT_DEF_P3, M_C0NCURRENT_DEF_FAILURE)(name, type, oplist, concurrent_t)
|
|
|
|
/* Stop processing with a compilation failure */
|
|
#define M_C0NCURRENT_DEF_FAILURE(name, type, oplist, concurrent_t) \
|
|
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(CONCURRENT_DEF): the given argument is not a valid oplist: " M_AS_STR(oplist))
|
|
|
|
/* Internal concurrent definition
|
|
- name: prefix to be used
|
|
- type: type of the sub container
|
|
- oplist: oplist of the type of the sub container
|
|
- concurrent_t: alias for M_F(name, _t) [ type of the container ]
|
|
*/
|
|
#define M_C0NCURRENT_DEF_P3(name, type, oplist, concurrent_t) \
|
|
M_C0NCURRENT_DEF_TYPE(name, type, oplist, concurrent_t) \
|
|
M_CHECK_COMPATIBLE_OPLIST(name, 1, type, oplist) \
|
|
M_C0NCURRENT_DEF_CORE(name, type, oplist, concurrent_t) \
|
|
M_C0NCURRENT_DEF_COMMON(name, type, oplist, concurrent_t)
|
|
|
|
/* Define the type of a concurrent container */
|
|
#define M_C0NCURRENT_DEF_TYPE(name, type, oplist, concurrent_t) \
|
|
\
|
|
/* Define a concurrent container using a lock */ \
|
|
typedef struct M_F(name, _s) { \
|
|
struct M_F(name, _s) *self; \
|
|
m_mutex_t lock; \
|
|
m_cond_t there_is_data; /* condition raised when there is data */ \
|
|
type data; \
|
|
} concurrent_t[1]; \
|
|
\
|
|
/* Define alias for pointer types */ \
|
|
typedef struct M_F(name, _s) *M_F(name, _ptr); \
|
|
typedef const struct M_F(name, _s) *M_F(name, _srcptr); \
|
|
\
|
|
/* Internal types for oplist */ \
|
|
typedef concurrent_t M_F(name, _ct); \
|
|
typedef type M_F(name, _subtype_ct); \
|
|
\
|
|
/* Cannot define iterator as it cannot be reliable in a concurrent type */ \
|
|
|
|
/* Define the internal services used for the lock strategy */
|
|
#define M_C0NCURRENT_DEF_CORE(name, type, oplist, concurrent_t) \
|
|
\
|
|
/* Initial the fields of the concurrent object not associated to the \
|
|
sub-container. */ \
|
|
M_INLINE void \
|
|
M_F(name, _internal_init)(concurrent_t out) \
|
|
{ \
|
|
m_mutex_init(out->lock); \
|
|
m_cond_init(out->there_is_data); \
|
|
out->self = out; \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
} \
|
|
\
|
|
/* Clear the fields of the concurrent object not associated to the \
|
|
sub-container. */ \
|
|
M_INLINE void \
|
|
M_F(name, _internal_clear)(concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
m_mutex_clear(out->lock); \
|
|
m_cond_clear(out->there_is_data); \
|
|
out->self = NULL; \
|
|
} \
|
|
\
|
|
/* Get the read lock. Multiple threads can get it, but only for reading. \
|
|
write lock is exclusive. \
|
|
NOTE: This instance doesn't implement the read/write strategy, \
|
|
and only get the lock */ \
|
|
M_INLINE void \
|
|
M_F(name, _read_lock)(const concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
m_mutex_lock (out->self->lock); \
|
|
} \
|
|
\
|
|
/* Free the read lock. See above. \
|
|
NOTE: This instance doesn't implement the read/write strategy, \
|
|
and only get the lock */ \
|
|
M_INLINE void \
|
|
M_F(name, _read_unlock)(const concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
m_mutex_unlock (out->self->lock); \
|
|
} \
|
|
\
|
|
/* Wait for a thread pushing some data in the container. \
|
|
CONSTRAINT: the read lock shall be get before calling this service */ \
|
|
M_INLINE void \
|
|
M_F(name, _read_wait)(const concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
m_cond_wait(out->self->there_is_data, out->self->lock); \
|
|
} \
|
|
\
|
|
/* Get the write lock. Only one threads can get it, and no other threads \
|
|
can get the read lock too. \
|
|
NOTE: This instance doesn't implement the read/write strategy, \
|
|
and only get the lock */ \
|
|
M_INLINE void \
|
|
M_F(name, _write_lock)(concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
m_mutex_lock (out->lock); \
|
|
} \
|
|
\
|
|
/* Free the write lock. \
|
|
NOTE: This instance doesn't implement the read/write strategy, \
|
|
and only get the lock */ \
|
|
M_INLINE void \
|
|
M_F(name, _write_unlock)(concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
m_mutex_unlock (out->lock); \
|
|
} \
|
|
\
|
|
/* Wait for a thread pushing some data in the container. \
|
|
CONSTRAINT: the write lock shall be get before calling this service */ \
|
|
M_INLINE void \
|
|
M_F(name, _write_wait)(const concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
m_cond_wait(out->self->there_is_data, out->self->lock); \
|
|
} \
|
|
\
|
|
/* Wait to all threads that some data are available in the container. \
|
|
CONSTRAINT: the write lock shall be get before calling this service */ \
|
|
M_INLINE void \
|
|
M_F(name, _write_signal)(concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
/* We need to signal this to ALL waiting threads as multiple threads \
|
|
may wait on a some data of this container. */ \
|
|
m_cond_broadcast(out->there_is_data); \
|
|
} \
|
|
|
|
/* Internal definition of the functions commons to concurrent and rp-concurrent
|
|
- name: prefix to be used
|
|
- type: type of the sub container
|
|
- oplist: oplist of the type of the sub container
|
|
- concurrent_t: alias for M_F(name, _t) [ type of the container ]
|
|
A function is defined only if the underlying container exports the needed
|
|
services. It is usually one service declared per service exported.
|
|
*/
|
|
#define M_C0NCURRENT_DEF_COMMON(name, type, oplist, concurrent_t) \
|
|
\
|
|
M_IF_METHOD(INIT, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _init)(concurrent_t out) \
|
|
{ \
|
|
M_F(name, _internal_init)(out); \
|
|
M_CALL_INIT(oplist, out->data); \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(INIT_SET, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _init_set)(concurrent_t out, concurrent_t const src) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(src); \
|
|
M_ASSERT (out != src); \
|
|
M_F(name, _internal_init)(out); \
|
|
M_F(name, _read_lock)(src); \
|
|
M_CALL_INIT_SET(oplist, out->data, src->data); \
|
|
M_F(name, _read_unlock)(src); \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(SET, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _set)(concurrent_t out, concurrent_t const src) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
if (M_UNLIKELY (out == src)) return; \
|
|
/* Need to order the locks in a total way to avoid lock deadlock. \
|
|
Indeed, two call to _set can be done in two threads with : \
|
|
T1: A := B \
|
|
T2: B := A \
|
|
If we lock first the mutex of out, then the src, it could be possible \
|
|
in the previous scenario that both mutexs are locked: T1 has locked A \
|
|
and T2 has locked B, and T1 is waiting for locking B, and T2 is waiting \
|
|
for locking A, resulting in a deadlock. \
|
|
To avoid this problem, we **always** lock the mutex which address is \
|
|
the lowest. */ \
|
|
if (out < src) { \
|
|
M_F(name, _write_lock)(out); \
|
|
M_F(name, _read_lock)(src); \
|
|
} else { \
|
|
M_F(name, _read_lock)(src); \
|
|
M_F(name, _write_lock)(out); \
|
|
} \
|
|
M_CALL_SET(oplist, out->data, src->data); \
|
|
if (out < src) { \
|
|
M_F(name, _read_lock)(src); \
|
|
M_F(name, _write_unlock)(out); \
|
|
} else { \
|
|
M_F(name, _write_unlock)(out); \
|
|
M_F(name, _read_unlock)(src); \
|
|
} \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(CLEAR, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _clear)(concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
/* No need to lock. A clear is supposed to be called when all operations \
|
|
of the container in other threads are terminated */ \
|
|
M_CALL_CLEAR(oplist, out->data); \
|
|
M_F(name, _internal_clear)(out); \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(INIT_MOVE, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _init_move)(concurrent_t out, concurrent_t src) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(src); \
|
|
M_ASSERT (out != src); \
|
|
/* No need to lock 'src' ? */ \
|
|
M_F(name, _internal_init)(out); \
|
|
M_CALL_INIT_MOVE(oplist, out->data, src->data); \
|
|
M_F(name, _internal_clear)(src); \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(MOVE, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _move)(concurrent_t out, concurrent_t src) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_C0NCURRENT_CONTRACT(src); \
|
|
/* No need to lock 'src' ? */ \
|
|
M_F(name, _write_lock)(out); \
|
|
M_CALL_MOVE(oplist, out->data, src->data); \
|
|
M_F(name, _write_unlock)(out); \
|
|
M_F(name, _internal_clear)(src); \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(SWAP, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _swap)(concurrent_t out, concurrent_t src) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_C0NCURRENT_CONTRACT(src); \
|
|
if (M_UNLIKELY (out == src)) return; \
|
|
/* See comment above */ \
|
|
if (out < src) { \
|
|
M_F(name, _write_lock)(out); \
|
|
M_F(name, _write_lock)(src); \
|
|
} else { \
|
|
M_F(name, _write_lock)(src); \
|
|
M_F(name, _write_lock)(out); \
|
|
} \
|
|
M_CALL_SWAP(oplist, out->data, src->data); \
|
|
if (out < src) { \
|
|
M_F(name, _write_unlock)(src); \
|
|
M_F(name, _write_unlock)(out); \
|
|
} else { \
|
|
M_F(name, _write_unlock)(out); \
|
|
M_F(name, _write_unlock)(src); \
|
|
} \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(RESET, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _reset)(concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _write_lock)(out); \
|
|
M_CALL_RESET(oplist, out->data); \
|
|
M_F(name, _write_unlock)(out); \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(EMPTY_P, oplist)( \
|
|
M_INLINE bool \
|
|
M_F(name, _empty_p)(concurrent_t const out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _read_lock)(out); \
|
|
bool b = M_CALL_EMPTY_P(oplist, out->data); \
|
|
M_F(name, _read_unlock)(out); \
|
|
return b; \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(GET_SIZE, oplist)( \
|
|
M_INLINE size_t \
|
|
M_F(name, _size)(concurrent_t const out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _read_lock)(out); \
|
|
size_t r = M_CALL_GET_SIZE(oplist, out->data); \
|
|
M_F(name, _read_unlock)(out); \
|
|
return r; \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(SET_KEY, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _set_at)(concurrent_t out, M_GET_KEY_TYPE oplist const key, M_GET_VALUE_TYPE oplist const data) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _write_lock)(out); \
|
|
M_CALL_SET_KEY(oplist, out->data, key, data); \
|
|
M_F(name, _write_signal)(out); \
|
|
M_F(name, _write_unlock)(out); \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(GET_KEY, oplist)( \
|
|
M_INLINE bool \
|
|
M_F(name, _get_copy)(M_GET_VALUE_TYPE oplist *out_data, const concurrent_t out, M_GET_KEY_TYPE oplist const key) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_ASSERT (out_data != NULL); \
|
|
M_F(name, _read_lock)(out); \
|
|
M_GET_VALUE_TYPE oplist *p = M_CALL_GET_KEY(oplist, out->data, key); \
|
|
if (p != NULL) { \
|
|
M_CALL_SET(M_GET_VALUE_OPLIST oplist, *out_data, *p); \
|
|
} \
|
|
M_F(name, _read_unlock)(out); \
|
|
return p != NULL; \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(SAFE_GET_KEY, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _safe_get_copy)(M_GET_VALUE_TYPE oplist *out_data, concurrent_t out, M_GET_KEY_TYPE oplist const key) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_ASSERT (out_data != NULL); \
|
|
M_F(name, _write_lock)(out); \
|
|
M_GET_VALUE_TYPE oplist *p = M_CALL_SAFE_GET_KEY(oplist, out->data, key); \
|
|
M_ASSERT (p != NULL); \
|
|
M_CALL_SET(M_GET_VALUE_OPLIST oplist, *out_data, *p); \
|
|
M_F(name, _write_unlock)(out); \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(ERASE_KEY, oplist)( \
|
|
M_INLINE bool \
|
|
M_F(name, _erase)(concurrent_t out, M_GET_KEY_TYPE oplist const key) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _write_lock)(out); \
|
|
bool b = M_CALL_ERASE_KEY(oplist, out->data, key); \
|
|
/* We suppose that the container has 'infinite' capacity, so \
|
|
we won't signal that a free space has been created */ \
|
|
M_F(name, _write_unlock)(out); \
|
|
return b; \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(PUSH, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _push)(concurrent_t out, M_GET_SUBTYPE oplist const data) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _write_lock)(out); \
|
|
M_CALL_PUSH(oplist, out->data, data); \
|
|
M_F(name, _write_signal)(out); \
|
|
M_F(name, _write_unlock)(out); \
|
|
} \
|
|
\
|
|
M_EMPLACE_QUEUE_DEF(name, concurrent_t, M_F(name, _emplace), M_GET_OPLIST oplist, M_EMPLACE_QUEUE_GENE) \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(POP, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _pop)(M_GET_SUBTYPE oplist *p, concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _write_lock)(out); \
|
|
M_CALL_POP(oplist, p, out->data); \
|
|
/* See comment above */ \
|
|
M_F(name, _write_unlock)(out); \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(PUSH_MOVE, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _push_move)(concurrent_t out, M_GET_SUBTYPE oplist *data) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _write_lock)(out); \
|
|
M_CALL_PUSH_MOVE(oplist, out->data, data); \
|
|
M_F(name, _write_signal)(out); \
|
|
M_F(name, _write_unlock)(out); \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(POP_MOVE, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _pop_move)(M_GET_SUBTYPE oplist *p, concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _write_lock)(out); \
|
|
M_CALL_POP_MOVE(oplist, p, out->data); \
|
|
/* See comment above */ \
|
|
M_F(name, _write_unlock)(out); \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(GET_STR, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _get_str)(m_string_t str, concurrent_t const out, bool a) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _read_lock)(out); \
|
|
M_CALL_GET_STR(oplist, str, out->data, a); \
|
|
M_F(name, _read_unlock)(out); \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(OUT_STR, oplist)( \
|
|
M_INLINE void \
|
|
M_F(name, _out_str)(FILE *f, concurrent_t const out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _read_lock)(out); \
|
|
M_CALL_OUT_STR(oplist, f, out->data); \
|
|
M_F(name, _read_unlock)(out); \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(PARSE_STR, oplist)( \
|
|
M_INLINE bool \
|
|
M_F(name, _parse_str)(concurrent_t out, const char str[], const char **e) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _write_lock)(out); \
|
|
bool b = M_CALL_PARSE_STR(oplist, out->data, str, e); \
|
|
M_F(name, _write_signal)(out); \
|
|
M_F(name, _write_unlock)(out); \
|
|
return b; \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(IN_STR, oplist)( \
|
|
M_INLINE bool \
|
|
M_F(name, _in_str)(concurrent_t out, FILE *f) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _write_lock)(out); \
|
|
bool b = M_CALL_IN_STR(oplist, out->data, f); \
|
|
M_F(name, _write_signal)(out); \
|
|
M_F(name, _write_unlock)(out); \
|
|
return b; \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(OUT_SERIAL, oplist)( \
|
|
M_INLINE m_serial_return_code_t \
|
|
M_F(name, _out_serial)(m_serial_write_t f, concurrent_t const out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _read_lock)(out); \
|
|
m_serial_return_code_t r = M_CALL_OUT_SERIAL(oplist, f, out->data); \
|
|
M_F(name, _read_unlock)(out); \
|
|
return r; \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(IN_SERIAL, oplist)( \
|
|
M_INLINE m_serial_return_code_t \
|
|
M_F(name, _in_serial)(concurrent_t out, m_serial_read_t f) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _write_lock)(out); \
|
|
m_serial_return_code_t r = M_CALL_IN_SERIAL(oplist, out->data, f); \
|
|
M_F(name, _write_signal)(out); \
|
|
M_F(name, _write_unlock)(out); \
|
|
return r; \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(EQUAL, oplist)( \
|
|
M_INLINE bool \
|
|
M_F(name, _equal_p)(concurrent_t const out1, concurrent_t const out2) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out1); \
|
|
M_C0NCURRENT_CONTRACT(out2); \
|
|
if (M_UNLIKELY (out1 == out2)) return true; \
|
|
/* See comment above on mutal mutexs */ \
|
|
if (out1 < out2) { \
|
|
M_F(name, _read_lock)(out1); \
|
|
M_F(name, _read_lock)(out2); \
|
|
} else { \
|
|
M_F(name, _read_lock)(out2); \
|
|
M_F(name, _read_lock)(out1); \
|
|
} \
|
|
bool b = M_CALL_EQUAL(oplist, out1->data, out2->data); \
|
|
if (out1 < out2) { \
|
|
M_F(name, _read_unlock)(out2); \
|
|
M_F(name, _read_unlock)(out1); \
|
|
} else { \
|
|
M_F(name, _read_unlock)(out1); \
|
|
M_F(name, _read_unlock)(out2); \
|
|
} \
|
|
return b; \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(GET_KEY, oplist)( \
|
|
M_INLINE bool \
|
|
M_F(name, _get_blocking)(M_GET_VALUE_TYPE oplist *out_data, const concurrent_t out, M_GET_KEY_TYPE oplist const key, bool blocking) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_ASSERT (out_data != NULL); \
|
|
bool ret = false; \
|
|
M_F(name, _read_lock)(out); \
|
|
while (true) { \
|
|
M_GET_VALUE_TYPE oplist *p = M_CALL_GET_KEY(oplist, out->data, key); \
|
|
if (p != NULL) { \
|
|
M_CALL_SET(M_GET_VALUE_OPLIST oplist, *out_data, *p); \
|
|
ret = true; \
|
|
break; \
|
|
} \
|
|
if (blocking == false) break; \
|
|
/* No data: wait for a write to signal some data */ \
|
|
M_F(name, _read_wait)(out); \
|
|
} \
|
|
M_F(name, _read_unlock)(out); \
|
|
return ret; \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD2(POP, EMPTY_P, oplist)( \
|
|
M_INLINE bool \
|
|
M_F(name, _pop_blocking)(M_GET_SUBTYPE oplist *p, concurrent_t out, bool blocking) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_ASSERT (p != NULL); \
|
|
bool ret = false; \
|
|
M_F(name, _write_lock)(out); \
|
|
while (true) { \
|
|
if (!M_CALL_EMPTY_P(oplist, out->data)) { \
|
|
M_CALL_POP(oplist, p, out->data); \
|
|
ret = true; \
|
|
break; \
|
|
} \
|
|
if (blocking == false) break; \
|
|
/* No data: wait for a write to signal some data */ \
|
|
M_F(name, _write_wait)(out); \
|
|
} \
|
|
M_F(name, _write_unlock)(out); \
|
|
return ret; \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD2(POP_MOVE, EMPTY_P, oplist)( \
|
|
M_INLINE bool \
|
|
M_F(name, _pop_move_blocking)(M_GET_SUBTYPE oplist *p, concurrent_t out, bool blocking) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_ASSERT (p != NULL); \
|
|
bool ret = false; \
|
|
M_F(name, _write_lock)(out); \
|
|
while (true) { \
|
|
if (!M_CALL_EMPTY_P(oplist, out->data)) { \
|
|
M_CALL_POP_MOVE(oplist, p, out->data); \
|
|
ret = true; \
|
|
break; \
|
|
} \
|
|
if (blocking == false) break; \
|
|
/* No data: wait for a write to signal some data */ \
|
|
M_F(name, _write_wait)(out); \
|
|
} \
|
|
M_F(name, _write_unlock)(out); \
|
|
return ret; \
|
|
} \
|
|
,) \
|
|
\
|
|
M_IF_METHOD(HASH, oplist)( \
|
|
M_INLINE size_t \
|
|
M_F(name, _hash)(concurrent_t const out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
M_F(name, _read_lock)(out); \
|
|
size_t h = M_CALL_HASH(oplist, out->data); \
|
|
M_F(name, _read_unlock)(out); \
|
|
/* The hash is unchanged by the concurrent container */ \
|
|
return h; \
|
|
} \
|
|
,) \
|
|
|
|
|
|
/******************************** INTERNAL ***********************************/
|
|
|
|
/* Deferred evaluation for the RP concurrent definition,
|
|
so that all arguments are evaluated before further expansion */
|
|
#define M_C0NCURRENT_RP_DEF_P1(arg) M_ID( M_C0NCURRENT_RP_DEF_P2 arg )
|
|
|
|
/* Validate the value oplist before going further */
|
|
#define M_C0NCURRENT_RP_DEF_P2(name, type, oplist, concurrent_t) \
|
|
M_IF_OPLIST(oplist)(M_C0NCURRENT_RP_DEF_P3, M_C0NCURRENT_RP_DEF_FAILURE)(name, type, oplist, concurrent_t)
|
|
|
|
/* Stop processing with a compilation failure */
|
|
#define M_C0NCURRENT_RP_DEF_FAILURE(name, type, oplist, concurrent_t) \
|
|
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(CONCURRENT_RP_DEF): the given argument is not a valid oplist: " M_AS_STR(oplist))
|
|
|
|
/* Internal RP concurrent definition
|
|
- name: prefix to be used
|
|
- type: type of the sub container
|
|
- oplist: oplist of the type of the sub container
|
|
- concurrent_t: alias for M_F(name, _t) [ type of the container ]
|
|
*/
|
|
#define M_C0NCURRENT_RP_DEF_P3(name, type, oplist, concurrent_t) \
|
|
M_C0NCURRENT_RP_DEF_TYPE(name, type, oplist, concurrent_t) \
|
|
M_CHECK_COMPATIBLE_OPLIST(name, 1, type, oplist) \
|
|
M_C0NCURRENT_RP_DEF_CORE(name, type, oplist, concurrent_t) \
|
|
M_C0NCURRENT_DEF_COMMON(name, type, oplist, concurrent_t)
|
|
|
|
/* Define the type of a RP concurrent container */
|
|
#define M_C0NCURRENT_RP_DEF_TYPE(name, type, oplist, concurrent_t) \
|
|
\
|
|
typedef struct M_F(name, _s) { \
|
|
struct M_F(name, _s) *self; \
|
|
m_mutex_t lock; \
|
|
m_cond_t rw_done; \
|
|
size_t read_count; \
|
|
bool writer_waiting; \
|
|
m_cond_t there_is_data; /* condition raised when there is data */ \
|
|
type data; \
|
|
} concurrent_t[1]; \
|
|
\
|
|
typedef struct M_F(name, _s) *M_F(name, _ptr); \
|
|
typedef const struct M_F(name, _s) *M_F(name, _srcptr); \
|
|
\
|
|
typedef type M_F(name, _subtype_ct); \
|
|
|
|
/* Define the internal services for the lock strategy of a RP container */
|
|
#define M_C0NCURRENT_RP_DEF_CORE(name, type, oplist, concurrent_t) \
|
|
\
|
|
M_INLINE void \
|
|
M_F(name, _internal_init)(concurrent_t out) \
|
|
{ \
|
|
m_mutex_init(out->lock); \
|
|
m_cond_init(out->rw_done); \
|
|
m_cond_init(out->there_is_data); \
|
|
out->self = out; \
|
|
out->read_count = 0; \
|
|
out->writer_waiting = false; \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
} \
|
|
\
|
|
M_INLINE void \
|
|
M_F(name, _internal_clear)(concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
m_mutex_clear(out->lock); \
|
|
m_cond_clear(out->rw_done); \
|
|
m_cond_clear(out->there_is_data); \
|
|
out->self = NULL; \
|
|
} \
|
|
\
|
|
M_INLINE void \
|
|
M_F(name, _read_lock)(const concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
struct M_F(name, _s) *self = out->self; \
|
|
m_mutex_lock (self->lock); \
|
|
while (self->writer_waiting == true) { \
|
|
m_cond_wait(self->rw_done, self->lock); \
|
|
} \
|
|
self->read_count ++; \
|
|
m_mutex_unlock (self->lock); \
|
|
} \
|
|
\
|
|
M_INLINE void \
|
|
M_F(name, _read_unlock)(const concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
struct M_F(name, _s) *self = out->self; \
|
|
m_mutex_lock (self->lock); \
|
|
self->read_count --; \
|
|
if (self->read_count == 0) { \
|
|
m_cond_broadcast (self->rw_done); \
|
|
} \
|
|
m_mutex_unlock (self->lock); \
|
|
} \
|
|
\
|
|
M_INLINE void \
|
|
M_F(name, _write_lock)(concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
m_mutex_lock (out->lock); \
|
|
while (out->writer_waiting == true) { \
|
|
m_cond_wait(out->rw_done, out->lock); \
|
|
} \
|
|
out->writer_waiting = true; \
|
|
while (out->read_count > 0) { \
|
|
m_cond_wait(out->rw_done, out->lock); \
|
|
} \
|
|
m_mutex_unlock (out->lock); \
|
|
} \
|
|
\
|
|
M_INLINE void \
|
|
M_F(name, _write_unlock)(concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
m_mutex_lock (out->lock); \
|
|
out->writer_waiting = false; \
|
|
m_cond_broadcast (out->rw_done); \
|
|
m_mutex_unlock (out->lock); \
|
|
} \
|
|
\
|
|
M_INLINE void \
|
|
M_F(name, _read_wait)(const concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
struct M_F(name, _s) *self = out->self; \
|
|
M_ASSERT (self == out); \
|
|
m_mutex_lock (out->self->lock); \
|
|
self->read_count --; \
|
|
if (self->read_count == 0) { \
|
|
m_cond_broadcast (self->rw_done); \
|
|
} \
|
|
m_cond_wait(self->there_is_data, self->lock); \
|
|
while (self->writer_waiting == true) { \
|
|
m_cond_wait(self->rw_done, self->lock); \
|
|
} \
|
|
self->read_count ++; \
|
|
m_mutex_unlock (out->self->lock); \
|
|
} \
|
|
\
|
|
M_INLINE void \
|
|
M_F(name, _write_wait)(concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
m_mutex_lock (out->lock); \
|
|
out->writer_waiting = false; \
|
|
m_cond_broadcast (out->rw_done); \
|
|
m_cond_wait(out->there_is_data, out->lock); \
|
|
while (out->writer_waiting == true) { \
|
|
m_cond_wait(out->rw_done, out->lock); \
|
|
} \
|
|
out->writer_waiting = true; \
|
|
while (out->read_count > 0) { \
|
|
m_cond_wait(out->rw_done, out->lock); \
|
|
} \
|
|
m_mutex_unlock (out->lock); \
|
|
} \
|
|
\
|
|
M_INLINE void \
|
|
M_F(name, _write_signal)(concurrent_t out) \
|
|
{ \
|
|
M_C0NCURRENT_CONTRACT(out); \
|
|
m_mutex_lock (out->lock); \
|
|
m_cond_broadcast(out->there_is_data); \
|
|
m_mutex_unlock (out->lock); \
|
|
} \
|
|
|
|
|
|
/******************************** INTERNAL ***********************************/
|
|
|
|
#if M_USE_SMALL_NAME
|
|
#define CONCURRENT_DEF M_CONCURRENT_DEF
|
|
#define CONCURRENT_DEF_AS M_CONCURRENT_DEF_AS
|
|
#define CONCURRENT_RP_DEF M_CONCURRENT_RP_DEF
|
|
#define CONCURRENT_RP_DEF_AS M_CONCURRENT_RP_DEF_AS
|
|
#define CONCURRENT_OPLIST M_CONCURRENT_OPLIST
|
|
#endif
|
|
|
|
#endif
|