Files
mlib/doc/ISSUES.org
T

32 KiB

Long term issues ========================================================

TODO #34 : Pass old size arguments to memory allocator   ENHANCEMENT

State

Some custom allocators don't store the old size of an object to maximize memory usage used by the allocated objects. On realloc, they need the old size argument. This is inline with the GMP allocator which provides the old size argument. There is no difficulty in the data structure to compute the old size of the object (it is quite easily available). It is needed for REALLOC operator (and FREE)

Solution #1

Add the oldsize argument to the REALLOC and FREE operators.

Solution #2

Create a local variable m_local_old_size before calling the REALLOC or FREE operators Modify GAIA interface to link OLDSIZE patttern to m_local_old_size Need to handle the warnings correctly.

Tradeof analysis

Solution #1 is simpler but breaks API. However custom allocators are not common. Solution #2 keeps API as it is but increases code complexity by adding more hacks into the codebase.

TODO #33 : Handling of partially constructed object   ANOMALY

State

Support of partially constructed object needs special code to handle it correctly (register a correct cleanup function in case of exception).

Current containers don't support such features.

Actions

Add such support in M*LIB containers. Need to analyse each function for execption safety.

It can be difficult. For example, for array _init_set function due to the copy of the object one per one. Each copied sub object may throw an exception, so we always need to have the container in a correct state so that it can be cleanup.

TODO #32 : User specialization fields in the container   ENHANCEMENT

State

Some methods of a contained object may need to have some options that are neither constant nor global, but are specialized in function of the container.

For example, a memory handler may need a parameter which is needed to identify which pool of memory has to be used.

Plan:

Add a new operator "CONTEXT" which is a type to be added in the defined container. Then it can be accessed by the operator through an extension of the GAIA by adding support for identification of specialization like this:

( CONTEXT( memory_pool_ref ), NEW( API(NONE, memory_pool_alloc, CONTEXT, ARG1)), DEL( API(NONE, memory_pool_free, CONTEXT, ARG1)) )

Open points:

  • How to get access to the specialization field of the container?

==> Define the "self" name as the name of the container for all methods?

  • What to do when there are several containers for a method?

==> self shall be the destination?

  • How to initialize the specialization field of the container? provide custom initialization functions? It seems to be the hard point. Use GAIA interface and a local variable with force naming : m_local_context for example.

Proposed Solution:

New operator USER_CONTEXT created. If operator USER_CONTEXT exist:

  • add a USER_CONTEXT user_data in the data structure.
  • for INIT & INIT_WITH, add a new parameter to the function to pass the context to initialize
  • for INIT_SET & INIT_MOVE & MOVE, initialize it with the source object (FIXME: This may not be what we want for INIT_SET if we want to copy an object from an allocator to another)
  • for each functions that use an operator that may use the USER_CONTEXT, add a local variable m_local_context and initialize to the user context

==> Limit user context to pointer or integer (Good enough)

  • Expand GAIA interface to handle specially the term USER_CONTEXT: if it exists, expands it to m_local_context. The user will therefore be able to expand its operator with the user provided local context.
  • Update OPLIST of data structure to change the call to INIT & INIT_WITH to use GAIA/USER_CONTEXT if the basic type has a USER_CONTEXT
  • To be checked: EMPLACE_TYPE ?
  • Create M_USING_CONTEXT to initialize a local variable m_local_context and initalize it with the user context to be used y the M_LET macro: M_USING_CONTEXT(int *, &y) M_LET(x, object_t) { }

Done in branch feature/user-data-v2 with a full working implementation for array

Solution limitation:

INIT & INIT_WITH interface change for the user. This however is an acceptable limitation.

Adding a user context to each data structure will increase a lot the size of the data structure in case of recursive data structures: For example, an array of string_t. Each string_t will need a user data context for it to work properly, so we'll need N*sizeof(user data context) memory just to make it work. This becomes even more problematic if we chain more data structures. And in general, we use custom allocators to be faster and use less memory: this will defeat the purpose of custom allocators a lot.

Another limitation is that string_t / bitset_t are not supported correctly. We can make this change globally but it will prevent mixing different codebase that uses the string_t together (one expects a user context, the other not).

Alternative #1

Passing user context arguments to each function call seems to be a little bit better. It might however reduces code optimization as one more register will be user everywhere in the calling chain, which may huge.

Alternative #2

The custom allocator could have a thread default allocator as a global variable (thread attribute). It seems to be a better solution, more scalable. The custom allocator will need to be able to switch quickly between scrach arena and permanent arena. This seems to be easy.

However switching allocators puts a burden to the user: the user needs to properly be sure to call the destructor with the custom allocator set to be the same as when it was created. This might not even be possible in case of exceptions.

TODO #31 : Uniformize parametrization options of containers   ENHANCEMENT

State

Currently in order to pass some parametrization of the containers, the parameters are passed to the generation macros through:

  • extra parameters of the macros,
  • special operators of the contained object oplist.

The extra parameters of some generation macros break the uniformity of the interface. It is conceptually wrong to put some special operators of the contained object that have only meanings for the container object. In case of adding new options, it break the existing interface or put another operator in the contained object oplist.

The following parameters are used:

  • Fixed size of a queue (buffer / B+Tree),
  • Policy of a queue (buffer),
  • Type of the container, IT type of the container, Ref type of the iterator

The following operators are used:

  • NEW / DEL / REALLOC / FREE
  • MEMPOOL / MEMPOOL_LINKAGE

The following new options is planned:

  • Interface (See #28)
  • Extra parameter for memory allocation function

Plan

The M\_*\_DEF\_AS macros shall be changed like this:

M_*_DEF_AS(name, cont_oplist, type[, type_oplist])

with cont\_oplist being the container oplist that gives parametrization to the container definition. The following operators are planned:

  • TYPE: to define the type of the container,
  • IT\_TYPE: to define the IT type of the container,
  • SUBTYPE: to define the type pointed by the iterator,
  • FIXED\_SIZE: to define the fixed size of a container,
  • POLICY: to define the policy of a container,
  • NEW / DEL / REALLOC / FREE: to define the memory interface,
  • MEMPOOL / MEMPOOL_LINKAGE: to request a definition of a specialized memory interface,
  • INTERFACE: to request a pure header definition, an implementation or both (see #28),
  • CONTEXT: to request an extra context parameter to add in the container.

All operators should be optional.

The NEW / DEL / REALLOC / FREE / MEMPOOL / MEMPOOL_LINKAGE won't have any effects anymore if defined in the contained object oplist.

Pro:

  • Uniformize interface

Con:

  • API breakage. It can be mitigated by providing an adaptating layer by prividing the new interface for M\_*\_DEF\_AS only

and *\_DEF\_AS will provide a translation layer.

TODO #30 : Serialization rework   ENHANCEMENT

State

Currently, each container supports 3 serialization methods:

  • old format to FILE
  • old format to string
  • generic serialization

Generic serialiation connect the container format to the serialization object constraints. It is done through a vtable. As such there is a performance penality and it avoids proper inlining. It is however quite flexible and decouple the data structure from the serialization format.

Evolution

Old format should be deprecated and the functions implemented it shall use the generic serialization interface.

Serialization object shall provide a special OPLIST of serialization. For example M-CORE will provide OLD Format oplist and M-SERIAL-JSON will provide JSON format oplist. Each oplist will provide the suffix needed for the serialization, and the interface (see already existing interface).

Then a container will generate specialized serialization methods for each provided oplist.

Pros:

  • Faster
  • In the M*LIB philosphy: much like other oplist usage.

Cons:

  • compatibility breakage.
  • increase code complexity.

Open point:

  • how can a user add a new serialization object? ==> See solution implemented by M-GENERIC.
  • Can we make a generic serialization object to support migration path? Seems possible.

Example

serial-json provides

#define M_SERIAL_1() (NAME(_json), READ_INT(…), WRITE_INT(…) )

containers provides

For i in 1 100 ; do if do exist M_SERIAL_ ## i () expand(SERIAL_METHOD with M_SERIAL_1())

SERIAL_METHOD(name, ….) static inline m_serial_return_code_t \ M_C(M_F(name, _out_serial),M_GET_NAME(serial_op) )(M_GET_TYPE(serial_op) f, const list_t list) \

DONE #29 : Support of partial initialized object   ANOMALY

State

In case of exception during the construction of an object, if the object is partially constructed, there is no destructor to call. Therefore if there is some partially constructed object (with some fields already fully constructed), there will be some links. This is the same problem with C++

Proposition

Provide new macros to perform the partial initialization before doing the remaining taf. Something like:

M_CHAIN_INIT( init_code, clear_code)

or

x is { string_t s; string_t d; int num; }

void struct_init_set(struct_t x, const struct_t y) { // First thing is Chained Initialization M_CHAIN_INIT( string_init_set(x->s, y->s), string_clear(x->s) ); M_CHAIN_INIT( string_init_set(x->d, y->d), string_clear(x->d) ); // End of chain init x->num = y->num; }

M_CHAIN_INIT will push the clean code pretty much like M_LET. Everything else is done in M_LET: when the constructor fully returns, the index of the clean code to call is reset to its value before calling the constructor. It means that the partially initialized code will be removed from this stack before the clean method of the structure itself is pushed.

If an exception is raised after a M_CHAIN_INIT, only the clean methods of the M_CHAIN_INIT are called.

TODO #28 : Separate generation of interface to implementation   ENHANCEMENT

State

Enable support for generating an interface only for the headers and an implementation only for the source code.

Analysis

Try to keep API compatibility ==> Only modify renamed macro with M_ prefix by giving a new mandatory argument for such generation.

This argument is a generation parametric argument. oplist is a parameter that define the property of the contained type. It could be used to pass this information but it will be an abuse of interface. Another argument is needed.

Used arguments to parameter generation not related to the contained type:

  • memory interface (NEW / DEL / …)
  • queue policy
  • fixed size for queue
  • fixed node size for B+Tree
  • generate static inline, public (extern) or private (define) functions.
  • name of the type.
  • name of the iterator
  • name of the iterated element.
  • …

Use another oplist as argument for M_.._DEF_AS (See #31)

M_BUFFER_DEF_AS(name, (TYPE(toto), FIXED_SIZE(10), POLICY(STACK), INTERFACE(header)), int, M_BASIC_OPLIST)

TODO #27 : Uniformization of m-buffer interface   ENHANCEMENT

State

The m-buffer interface requires special argument to work:

  • size & policy for BUFFER,
  • policy only for the others.

This is not uniform with the others generation macros and not uniform with each other. Moreover the others are the only one which can be used in a shared memory, so a fixed size will be good to have too for such interface.

Plan

  • Uniformization of the interface (See #31)
  • Provide static size for all QUEUE.

TODO #26 : Hash-Map for huge objects   ENHANCEMENT

Specialize Hashmap for huge objects on supported hardware:

Based on OA hash map with taggued pointer (needs space for tagging): low bits = high bits of hash pointer to object { key, hash, value }

Can solve collision without dereferencing object with 1/2^lowbits chances. NB: hash may be uneeded due to the small chances of collision. Reduce cache usage ==> Should be faster. Increase cache usage for very small object (integer).

Use of pointer to avoid moving object on GC.

On AMD64, only 48 bits of 64 bits are really used for addressing data. => lowbits 16 Empty representation= 0 (hash can be 0. pointer cannot be it). Deleted representation = 1 (pointer cannot be zero)

<—- PTR —-><-HASH->

#define LOW_BITS 16
#define ALIGNED_BITS 2
void *get_ptr(int64_t x) { 
  return (void*) ((x >> LOW_BITS) << ALIGNED_BITS);
}         
unsigned get_small_hash(int64_t x) {
  return x & ((1<<LOW_BITS)-1);
}

Can also (should?) use SIMD to test for several hash entries at the same times In which case a complete new implementation will be needed Note: SIMD doesn't seem to be a win if not handled properly. Since the first guess of a good hashtable shall give the right entry > 90% of the time. So 90% of the times SIMD will pay the cost of reading not needed memory. It might still be a win, but proper tuning needs to be done.

TODO #25 : Support of error return model for error handling.   ENHANCEMENT

State

Find a way to support error return code for the API in case of allocation failure.

Analysis

Any service that returns void shall return a "int" (or another type). In case of allocation failure, it shall return an error. M_CALL macro shall stop its execution if the service returns an error code and the error code represents an error (avoid rewritting everything) and throw back the error code (stopping the execution flow).

Services returning already something shall not be modified but returns the error code embedded (like a NULL pointer).

This model should be applied at the container level only and not globally. Different containers may need different levels of error handling. 4 combinaison to take into account:

==> ABORT on container / ABORT on used type: The current model ==> ABORT on container / RETCODE on used type: Needs to abort on reported error. ==> RETCODE on container / ABORT on used type: Nothing particular to note. ==> RETCODE on container / RETCODE on used type: Forward retcode to caller.

Find a way to make it while not making the code too complex (try to keep as simple as possible).

Using a specialized oplist for such containers (_OPLIST_RETCODE) using a specialized API for retcode:

API_RET_*: that expands to

if (func(args) == 0) goto handler_exit_failure;

Adding at the end of each service: M_END_FUNCTION(cleanup_code) that expands to different cases: M_END_FUNCTION_VALUE(cleanup_code, return_success, return_failure) that expands to different cases:

ABORT/ABORT: handler_exit_success: handler_exit_failure: return;

ABORT/RETCODE: handler_exit_success: return; handler_exit_failure: RAISE_FATAL_ERROR

RETCODE/ABORT handler_exit_success: return SUCESS; handler_exit_failure: cleanup_code return FAILURE;

RETCODE/RETCODE handler_exit_success: return SUCESS; handler_exit_failure: cleanup_code return FAILURE;

If really needed, the macro can be avoided and code can be hand written.

Open points:

  • How to handle warnings on unused labels?
  • What about M_LET / M_EACH? Maybe only supports those.

TODO #24 : New MIN-MAX-HEAP container   ENHANCEMENT

State

See https://en.wikipedia.org/wiki/Min-max_heap as DPRIORITY_QUEUE_DEF ?

Analysis

NOTE: Needs for such container? On hold until a user needs it.

TODO #23 : Strict MOVE semantic to clarify   ENHANCEMENT

State

Some type may need to have a force MOVE semantic (for example, they can store pointer to themselves). Currently the INIT_MOVE & MOVE operators are more a help for performance than a strict semantic usage.

ARRAY container constraint

The ARRAY container doesn't support strict MOVE semantic for example. It is not a simple matter as it performs a realloc of the table, thus moving the data before they can be moved using MOVE. Two solutions:

  • New operator MOVE_SELF to fix a type after it has been moved.
  • If MOVE defined, force another table and then copy by hand the type. This will be slower and consumme more memory.

MOVE in OPLIST

Proposal: do not export INIT_MOVE / MOVE operator in OPLIST if the MOVE operator is compatible with a pure COPY semantic. An exported MOVE operator will tell other containers than the type shall be carefully moved using the provided MOVE operator.

For example for tuple, it shall

  • create an init_move operator if no one has disabled INIT_MOVE,
  • export the init_move operator if at least one has exported a INIT_MOVE and no one has disabled INIT_MOVE.

DO_INIT_MOVE operator

DO_INIT_MOVE macro is also fully working for structure defined with [1] tricks but without an explicit INIT_MOVE / MOVE operators as it uses MOVE_DEFAULT which is not compatible. ==> Analyse limitation and possible constraint usages.

Being able to define a correct default for INIT_MOVE will be really good (compatible with trivial move copy). In such case, all INIT_MOVE & MOVE operators can be removed from oplist to only use the default, and theses operators can be disabled or defined only when really needed in the oplist. However creating a default INIT_MOVE macro seems problematic. If the type is typedef T t[1] then passing such an argument to a function, will transform the argument to T*, and the type of the argument doesn't match what is expecting resulting in a move of the pointers, not a move of the design data.

Defining this type seems possible with C11 _Generic and a TYPE in the oplist, but without C11 _Generic I don't see any way to define such macro and we still need to target C99 for such basic feature.

Without a way to write such a macro, the ticket seems pretty much a dead end.

TODO #20 : New: Bucket priority queue   ENHANCEMENT

State

Add a new kind of priority queue. See https://en.wikipedia.org/wiki/Bucket_queue

Check if it will be better as intrusive or non-intrusive container.

To test if a bucket is empty or not, a bitfield can be used to check if the bit associated to the bucket is set or not. To get the highest bucket non empty, we can perform a CLZ of the bitset, which shall be much faster than performing a linear search of the buckets (algorithm complexity is the same, except that we can scan 64 entries at a time).

Check if we can use BITSET, or introduce fixed size BITSET or use ad-hock implementation.

Analysis

NOTE: Needs for such container? On hold until a user needs it.

CANC #19 : New: Intrusive Red Black Tree   ENHANCEMENT

State

Add intrusive red black tree. Look also for AVL tree (NOTE: Is there a performance difference between the two?)

Analysis

Only needed for unmovable objects for which B+Tree cannot do the job. But standard Read/Black Tree will do the job just fine. There is really no need for it. ==> Cancelled

TODO #18 : Missing methods   ENHANCEMENT

State

Some containers don't have all the methods they should. See the cells in yellow here: http://htmlpreview.github.io/?https://github.com/P-p-H-d/mlib/blob/master/doc/Container.html

Analysis

Analyzed each missing methods and fill in the gap

TODO #17 : New: Ressource handler   ENHANCEMENT

State

A global 'ressouce handler' which shall associated a unique handle to a ressource. The handle shall be unique for the ressource and shall not be reused. It is typically a 64 bits integers always incremented (even if the program creates one billion ressources per second, the counter won't overflow until 585 years).

The ressource handler shall make an association between a HANDLE 64 bits and:

  • how much real owners claim to own the ressource (the ressource is only owned by the ressource handler, however it acts as a delegate of the real owner),
  • how much users keep a pointer to the ressource.
  • pointer to the resource itself. This may be a better alternative than shared_ptr & weak_ptr:
  • reduce fragmentation,
  • no cycle dependencies,
  • shared_ref & weak_ref becomes only HANDLE,
  • all ressources can be freed in one pass. Needs lock free dictionnary or at least concurrent dictionnary. How to handle multiple resource ?

Analysis

  • use of variant: Pro : easy. Con: Memory usage can be (much) higher than needed if there is a lot of dissimilarity between the size of the objects.
  • embedded the type in the ressource handler: Con: more work, API more complex. Pro: Memory usage seems better.

TODO #16 : New: Lock Free List   ENHANCEMENT

Implement a lock free list. Most of the difficulty is the memory reclamation part. Typically this lock free list shall be compatible with RCU method.

First step: backoff methods   DONE

Second step: lock free node pool :   DONE

Done as m-c-mempool header.

Third step: Implement generic lock free list on top of it.

The ABA problem is already taken into account by the memory alloctor provided that the lock free list doesn't try to be smart.

backoff has be used when using CAS.

Concurrent insertion / insertion and insertion / deletion and deletion / deletion shall be crefully analyzed when taken into account.

Questions:

  • singly or doubly or dual push?
  • needs to be logically deleted : needs a previous field (NULL if not logically deleted) ? TBC

NOTE: m-c-mempool doesn't seem to be fully robust. random failure of the test cases appear (more notably with Visual C++, but it is still quite rare).

DONE #14 : Memory allocation enhancement   ENHANCEMENT

Enhancement of the memory allocation scheme to find way to deal properly with advanced allocators:

  • non-default alignment requirements for types,
  • instance-based allocator (may need instance based variable access),
  • expected life of created type (temporary or permanent),
  • stack based allocator,
  • global variable access for allocator,
  • maximum allocation before failure.

Most of theses are already more or less supported. Examples shall be created to show how to deal with this:

  • alignement shall be implemented with the attributes of <stdalign.h>

However I sill don't know how to implement "instance-based allocator" which is what is missing. The problem is how to give to methods context local information store within the container itself.

Update:

API transformation support enables "instance-based allocator" to be made easily. Needs some formal operator in the oplist to support it fully and an example.

Can be supported using another API extension, some more operators and forcing some names:

  • API_N: call like FUNC(obj->extra_data, type)

'obj' is a forced named corresponding to an alias to an object in the function. Operator needed:

  • EXTRA_DATA: Add an extra-data field wihtin the container. Defines the type of data.

It is a kind of object inheritance where the container inherits some extra data from its base.

Duplicate with #32 which is more generic ==> Closed

TODO #12 : New: Atomic shared pointer   ENHANCEMENT

State

Add an extension to the SHARED_PTR API:

  • New type atomic_shared_ptr
  • name_init_atomic_set (&atomic_shared_ptr, shared_ptr);
  • name_init_set_atomic (shared_ptr, &atomic_shared_ptr);
  • name_init_atomic_set_atomic (&atomic_shared_ptr, &atomic_shared_ptr);
  • name_atomic_set (&atomic_shared_ptr, shared_ptr);
  • name_set_atomic (shared_ptr, &atomic_shared_ptr);
  • name_atomic_set_atomic (&atomic_shared_ptr, &atomic_shared_ptr);
  • name_atomic_clear

No _ref or direct _init: we need to init first a normal shared_ptr then the atomic (TBC)

_atomic_set method:

It can be implemented by incrementing the non atomic shared pointer reference, then performs a compare_and_swap to the data of the atomic shared pointer, finally decrement and dec/free the swapped previous data of the atomic like a normal shared pointer. All 3 steps are safe.

_set_atomic method:

It needs to perform the following atomic operation : <read the pointer, deref pointer and increment the pointed value> I don't known how to do it properly.

See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4162.pdf

Proposition for _set_atomic we store temporary NULL to the atomic_ptr struct to request an exclusive access to the data (this looks like a lock and other operations need to handle NULL) :

        void shared_ptr_set_atomic(ptr a, atomic_ptr *ptr)
        {
          // Get exclusive access to the data
          p = atomic_load(ptr);
          do {
            if (p == NULL) {
              // TODO: exponential backoff
              p = atomic_load(ptr);
              continue;
            }
          } while (!atomic_compare_exchange_weak(ptr, &p, NULL));
          // p has exclusive access to the pointer
          p->data->cpt ++;
          a->data = p->data;
          atomic_exchange (ptr, p);
        }

This prevents using NULL which obliges atomic shared pointer to point to a created object…

Other alternative solution is to use the bit 0 to mark the pointer as being updated, preventing other from using it (TBC only clear):

        void shared_ptr_set_atomic(ptr a, atomic_ptr *ptr)
        {
          // Get exclusive access to the data
          p = atomic_load(ptr);
          do {
            if ( (p&1) != 0) {
              // TODO: exponential backoff
              p = atomic_load(ptr);
              continue;
            }
          } while (!atomic_compare_exchange_weak(ptr, &p, p|1));
         // Exclusive access (kind of lock).
          p->data->cpt ++;
          a->data = p->data;
          atomic_set (ptr, p);
        }

Other implementation seems to have it hard to be lock-free: cf. https://github.com/llvm-mirror/libcxx/commit/5fec82dc0db3623546038e4a86baa44f749e554f

TODO #5 : New: Concurrent dictionary Container   ENHANCEMENT

State

Implement a more efficient dictionary than lock + std dictionary for all operations when dealing with threads. See https://msdn.microsoft.com/en-us/library/dd287191(v=vs.110).aspx

The best parallel algorithm is still when there is as few synchronization as possible. A concurrent dictionary will fail at this and will result in average performance at best. The typical best case will be in RCU context (a lot of readers, few writers), so the interface shall be compatible with such structure.

Multiple locks within the dictionnary

A potential implementation may be to request at initialization time the number of concurrent thread N. Create a static array of N dictionnary with N mutex. Then to access the data will perform :

  • compute hash of object,
  • access high bits of hash and select which dictionnary shall have the data,
  • lock it,
  • perform classic access to the data (check if the compiler can properly optimize the hash computation),
  • unlock it.

The property of the hash shall allow a good dispersion of the data across multiple locks, reducing the constraints on the lock. This implementation could be build easily upon the already existent dictionary.

To test.

See also https://github.com/simonhf/sharedhashfile

Lock Free dictionnary

Evaluate also lock-free dictionary (easier with open addressing). It needs a complete rewrite of the inner loop through. The hard part is the dynamic resizing of the internal array. Ssee http://preshing.com/20160222/a-resizable-concurrent-map/ for a potential solution and http://www.cs.toronto.edu/~tomhart/papers/tomhart_thesis.pdf for memory reclamation techniques). and https://www.research.ibm.com/people/m/michael/spaa-2002.pdf https://www.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.2017.11.22a.pdf presents different techniques used by linux kernel. It needs before lock-free list: http://www.cse.yorku.ca/~ruppert/papers/lfll.pdf http://www.cse.yorku.ca/~ruppert/Mikhail.pdf

A good way maybe Open Addessing table used only for indirection and a freelist memory reclamation container for handle the entries (like a transaction).

Solution?


Use of the concurrent pool (m-c-mempool) to allocate items. The big table will only store pointers (+ part of hash in unused bits?) to such allocate items. Atomic update is done by allocating a new node, update it, and atomicaly store it in the dict, putting the old one as logically deleted and to reclaim later. (Other threads may still read its data so we cannot free immediatly).

DONE #22: Enhanced services for SPSC Queue   ENHANCEMENT

Add services:

DONE _push_bulk

Test the capacity of the queue and push as much as possible in the queue with one check of the atomic structure.

DONE _pop_bulk

Test the capacity of the queue and push as much as possible in the queue with one check of the atomic structure.

DONE _push_force:

If the capacity of the queue is full, pop one element and push it: push always succeed and the queue always keep the youngest element.

DONE #21: Generic Binary serialization   ENHANCEMENT

Based on issue #26 of https://github.com/P-p-H-d/mlib/issues/26

Some kind of "binary serialization" on the model of get_str/parse_str could be possible. It would be a great feature from the application point-of-view: binary representation is more bandwidth-efficient if used on network communications.

It will be good to have import/export methods to the XML/JSON/MSGPACK/PROTOBUF/BINARY format. However, adding all of them on by one in the M*LIB containers doesn't seem satisfactory.

Instead, adding a generic interface for the serialization of data that may be customized by the user to perform the import/export of objects in whatever format they want into what they want (FILE/memory/…). To simplify it, this interface could only support one kind of import/export per compilation unit.

DONE #15: Prologue / Epilogue for Constructor / Destructor for error handling   ENHANCEMENT

Constructor (and destructor) need to use user-defined prologue / epilogue. This is in order to register the constructed object into a proper Exception Handling Stack so that throwing exceptions may work reliably.

Proposal:

  • M_CONSTRUCTOR_PROLOGUE(object, oplist);
  • M_CONSTRUCTOR_EPILOGUE(object, oplist);
  • M_DESTRUCTOR_PROLOGUE(object, oplist);
  • M_DESTRUCTOR_EPILOGUE(object, oplist);

Object creation will need to add all sub-objects into the stack, then unstack all to push instead the root object (which recursively remove them).

See also http://freetype.sourceforge.net/david/reliable-c.html#cseh

  • How to handle like allocation of the object?
  • How to avoid calling the destructor multiple times? (It is needed?)
  • How the code can be factorized with RETCODE needs?