/* * 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