From 3c75a002c610e5431e77a03693cfe46998daa9b0 Mon Sep 17 00:00:00 2001 From: KerstinKeller Date: Tue, 8 Aug 2023 15:17:09 +0200 Subject: [PATCH 01/17] Doc: Added tutorial for host group feature - ipc communication across host borders (#1183) Co-authored-by: Kaan Evlende <124665298+kaanconti@users.noreply.github.com> Co-authored-by: KerstinKeller Co-authored-by: Kristof Hannemann <50989282+hannemn@users.noreply.github.com> Co-authored-by: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> --- doc/rst/advanced/ecal_in_docker.rst | 76 +++++++++++++++++- .../img_documentation/vscode_etc_hosts.png | Bin 0 -> 37494 bytes 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 doc/rst/advanced/img_documentation/vscode_etc_hosts.png diff --git a/doc/rst/advanced/ecal_in_docker.rst b/doc/rst/advanced/ecal_in_docker.rst index 48c185ccab..f0b97cf471 100644 --- a/doc/rst/advanced/ecal_in_docker.rst +++ b/doc/rst/advanced/ecal_in_docker.rst @@ -1,4 +1,4 @@ -.. include:: /include.txt +.. include:: /include.txt .. _ecal_in_docker: @@ -186,4 +186,76 @@ Run the docker containers .. code-block:: bash sudo docker-compose build - sudo docker-compose up \ No newline at end of file + sudo docker-compose up + +Seamless IPC-Communication across host borders +---------------------------------------------- + +.. important:: + This will work with eCAL 5.12 and higher. + Older versions lack the ability to utilize the ``host_group_name`` in the :file:`ecal.ini` file, thus it won't work. + + +In eCAL, you are able to set host belonging over network borders by utilizing the :file:`ecal.ini` configuration file with the same ``host_group_name`` - in the following steps, you will learn how to set this up. + +.. note:: + If we don't set the same ``host_group_name`` on our Host and our Containers, an IPC-Communication across host borders is not available with different host names. + +#. To encapsulate your container network from your host network, you need to create a new docker network with the following command: + + .. code-block:: bash + + sudo docker network create --driver=bridge --subnet=192.100.0.0/24 my_network + +#. Edit your :file:`ecal.ini` and run your Container within the newly created docker network + + * You will use our previously discussed :ref:`ecal-runtime-image` for the next step. + + * First, open :file:`/etc/ecal/ecal.ini` from your preferred editor. + + * Search for the line ``network_enabled`` and set it to ``true``. + + * Search for the line ``host_group_name`` and write your preferred name. + + * Save and close the :file:`ecal.ini` file. + + * Now your :file:`ecal.ini` file is prepared. + We want to use it not only for our Host-System but also for our Container, so we don't need to edit the :file:`ecal.ini` in our Container again. + To achieve that, run following command to start your container: + + .. code-block:: bash + + sudo docker run --rm -it --ipc=host --pid=host --network=my_network --name=container1 --h=container1 --ip=192.168.100.2 -v /etc/ecal/ecal.ini:/etc/ecal/ecal.ini ecal-runtime + + - You should now be inside the root shell of your Container. + Check if your :file:`ecal.ini` file is correct. + + - Now your Container is prepared and configured correctly, so we are ready to start an eCAL example. + + .. code-block:: bash + + ecal_sample_person_snd + + +#. Configure the Host network + + - eCAL is sending UDP messages to a multicast IP group ``239.0.0.0/24``, further information in :ref:`Getting Started Section `. + The idea is now, to successfully receive those messages from your previously started container on your host. + For that, you need to add a route to your routing table. + By typing ``ifconfig`` in your shell, you can identify the right docker network. + In our case, the prefix of the docker network is always ``br`` followed by random numbers. + After identifying the right network, run following command. + + .. code-block:: bash + + sudo ip route add 239.0.0.0/24 dev metric 1 + +#. (optional) After adding the route, you register the Container with IP address and name in /etc/hosts for DNS resolution, enabling easy access to it by hostname within the network. + + .. code-block:: bash + + sudo nano /etc/hosts + + .. image:: img_documentation/vscode_etc_hosts.png + +After all steps are done, all eCAL nodes can communicate seamlessly from docker to the host and vice versa. diff --git a/doc/rst/advanced/img_documentation/vscode_etc_hosts.png b/doc/rst/advanced/img_documentation/vscode_etc_hosts.png new file mode 100644 index 0000000000000000000000000000000000000000..d4a0e148c6c419a1e6626dc8779ea247d46bf4a9 GIT binary patch literal 37494 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 ZNXC-Q3iE(|_0j#8n6Q*k;m6P4{tw5JR_6c! literal 0 HcmV?d00001 From ce128e7c0ec998dbb786ce2d34339a225fcad5c9 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Wed, 9 Aug 2023 07:52:48 +0200 Subject: [PATCH 02/17] Doc: Added documentation for 5.12 release (#1182) Co-authored-by: Kerstin Keller Co-authored-by: Rex Schilasky <49162693+rex-schilasky@users.noreply.github.com> --- doc/rst/advanced/layers/shm.rst | 75 ++-- doc/rst/advanced/layers/shm_zerocopy.rst | 374 ++++++++++++++++++ doc/rst/versions/5.10/whats_new.rst | 3 +- doc/rst/versions/5.11/whats_new.rst | 2 +- doc/rst/versions/5.12/compatibility_table.txt | 42 ++ doc/rst/versions/5.12/whats_new.rst | 58 +++ doc/rst/versions/5.8/whats_new.rst | 2 +- doc/rst/versions/5.9/whats_new.rst | 3 +- doc/rst/versions/compatibility.rst | 7 + doc/rst/versions/ecal_versions.rst | 1 + 10 files changed, 510 insertions(+), 57 deletions(-) create mode 100644 doc/rst/advanced/layers/shm_zerocopy.rst create mode 100644 doc/rst/versions/5.12/compatibility_table.txt create mode 100644 doc/rst/versions/5.12/whats_new.rst diff --git a/doc/rst/advanced/layers/shm.rst b/doc/rst/advanced/layers/shm.rst index 24987315b4..b43f0f8169 100644 --- a/doc/rst/advanced/layers/shm.rst +++ b/doc/rst/advanced/layers/shm.rst @@ -40,7 +40,7 @@ Communication phase (default configuration): * The subscribers close the memory file and release the access-mutex. -To support one to many publisher/subscriber connections the publisher creates in fact one named update event per connection. +To support one to many publisher/subscriber connections, the publisher creates one named update event per connection. .. note:: @@ -92,73 +92,41 @@ Finally that means the publishers ``CPublisher::Send`` API function call is now Zero Copy mode (optional) ------------------------- -*Zero-copy has been added in eCAL 5.10. It is turned off by default. When turned on, old eCAL Version can still receive the data but will not use zero-copy.** - -The “normal” eCAL Shared memory communication results in the payload being copied at least twice: - -1. Into the SHM file by the publisher - -2. From the SHM file the private memory of each subscriber - -Usually there is no issue with that. -Copying the payload from the memory file before executing the subscriber callback results in better decoupling, so the publisher can update the memory file with the next message while the subscriber is still processing the last one. -Small messages will be transmitted in a few microseconds and will not benefit from zero-copy. - -If it comes to very large messages (e.g. high resolution images) however, copying really matters and it can make sense to activate eCAL's zero-copy mode. -With zero-copy, the communication would look like this: - -1. The publisher still has to copy the data into the memory file. - -2. The subscriber executes its callback directly on the memory file. - The memory file is blocked, while being used. - - .. warning:: - - The memory file is blocked for new publications as long as the user’s callback is processing its content. - It will also block other subscribers from reading the same SHM file. - -3. The subscriber releases the memory file, so the publisher can update it again. - .. note:: - Even though it is called zero-copy, only the subscribers are zero-copy. - Publishers still have to copy the data into the memory file, as they have to also support other layers like UDP or TCP and therefore cannot directly work on the memory file. + Zero-copy has been added in eCAL 5.10 for subscription and in 5.12 for publishing. + It is turned off by default. + When turned on, old eCAL Versions can still receive the data but will not use zero-copy. -Zero-copy can be enabled in the following ways: +The "normal" eCAL Shared memory communication results in the payload being copied at least twice: -- **Use zero-copy as system-default (not recommended!):** - - Activating zero copy system-wide is not recommended because of the mentioned disadvantages for small payloads. - But if this is wanted for reasons it can be done by adapting your :file:`ecal.ini` like this. - - .. code-block:: ini - - [publisher] - memfile_zero_copy = 1 +1. Into the SHM file by the publisher -- **Use zero-copy for a single publisher (from your code):** +2. From the SHM file the private memory of each subscriber - Zero copy could be activated either per connection or for a complete system using the eCAL configuration file. - To activate it for a specific publisher this ``CPublisher`` `API function `_ needs to be called. +Copying the payload from the memory file before executing the subscriber callback results in **better decoupling**, so the publisher can update the memory file with the next message while the subscriber is still processing the last one. +**Small messages** will be transmitted in a few microseconds and will not benefit from zero-copy. - .. code-block:: cpp +If it comes to very **large messages** (e.g. high resolution images) however, copying really matters and it can make sense to activate eCAL's **zero-copy mode**. - // Create a publisher (topic name "person") - eCAL::protobuf::CPublisher pub("person"); +.. seealso:: - // Enable zero-copy for this publisher - pub.ShmEnableZeroCopy(true); + Check out the following Chapter to learn how to enable zero-copy. + The chapter will also teach you about advantages and disadvantages of zero-copy: -.. note:: + .. toctree:: + :maxdepth: 1 - In general, it is advisable to combine zero-copy with multi-buffering to reduce the impact on the publisher. + shm_zerocopy.rst Multi-buffering mode (optional) ------------------------------- -*Multi-buffering has been added in eCAL 5.10. -Multi-buffered topics cannot be received by older eCAL versions. -The feature is turned off by default.* +.. note:: + + Multi-buffering has been added in eCAL 5.10. + Multi-buffered topics cannot be received by older eCAL versions. + The feature is turned off by default. As described in the previous sections, eCAL uses one shared memory file per publisher. This can lead to performance reduction if @@ -199,3 +167,4 @@ You can activate the feature in the following ways. pub.ShmSetBufferCount(3); Combining the zero-copy feature with an increased number of memory buffer files (like 2 or 3) could be a nice setup allowing the subscriber to work on the memory file content without copying its content and nevertheless not blocking the publisher to write new data. +Using Multibuffering however will force each Send operation to re-write the entire memory file and disable partial updates. \ No newline at end of file diff --git a/doc/rst/advanced/layers/shm_zerocopy.rst b/doc/rst/advanced/layers/shm_zerocopy.rst new file mode 100644 index 0000000000..1ffba04ffc --- /dev/null +++ b/doc/rst/advanced/layers/shm_zerocopy.rst @@ -0,0 +1,374 @@ +.. include:: /include.txt + +.. _transport_layer_shm_zerocopy: + +============== +eCAL Zero Copy +============== + +.. note:: + + The eCAL Zero Copy mode was introduced in two steps: + + - eCAL 5.10 (zero-copy on subscriber side only, still one memory copy on the publisher side) + - eCAL 5.12 (zero-copy on publisher and subscriber side, see Full Zero Copy Behavior) + + In all versions it is turned off by default. + +Enabling eCAL Zero Copy +======================= + +- **Use zero-copy as system-default:** + + Zero Copy can be enabled as system default from the :file:`ecal.ini` file like follows: + + .. code-block:: ini + + [publisher] + memfile_zero_copy = 1 + +- **Use zero-copy for a single publisher (from your code):** + + Zero-copy can be activated (or deactivated) for a single publisher from the eCAL API: + + .. code-block:: cpp + + // Create a publisher (topic name "person") + eCAL::protobuf::CPublisher pub("person"); + + // Enable zero-copy for this publisher + pub.ShmEnableZeroCopy(true); + + Keep in mind, that using protobuf for serialization will still: + + #. Require to set the data in the protobuf object + #. Later cause a copy by serializing into the SHM buffer. + + If you want to avoid this copy, you can use the :ref:`low-level API ` to directly operate on the SHM buffer. + +Full Zero Copy behavior +======================= + +The Full eCAL Zero Copy mechanism is working for local (inner-host) publish-subscribe connections only. Sending data over a network connection will not benefit from that feature. + +Shared-Memory-only connection +----------------------------- + +This describes the case, where a publisher publishes it's data **only via shared memory** to 1 or more subscribers. + +**Publisher**: + +- Protobuf API Level: + + #. The user sets the data in the **protobuf object** + + #. The publisher **locks** the SHM buffer. + + *This operation may take some time, as the publisher needs to wait for the subscriber to release the buffer.* + *This can be relaxed by using the multi-buffering feature.* + + #. The publisher **serializes** the protobuf object **directly into the SHM** buffer + + *Due to the technical implementation of protobuf, this will cause the entire message to be serialized and re-written* + + #. The publisher **unlocks** the SHM buffer + +- Binary API Level: + + #. The publisher **locks** the SHM buffer + + #. The user **directly** writes data to the **SHM buffer**. + + #. The publisher **unlocks** the SHM buffer + + #. The publisher **informs** all connected subscriber + +**Subscriber**: + +#. The subscriber **locks** the SHM buffer + + *The subscriber will need to wait for any publisher and subscriber to unlock the buffer.* + *Currently, there is no parallel read access to the SHM buffer.* + +#. The subscriber calls the **callback** function directly with the **SHM buffer** as parameter + +#. After the callback has **finished**, the subscriber **unlocks** the SHM buffer + +Mixed Layer connection +---------------------- + +This describes the case where a publisher publishes its data parallel via **shared memory and network** (tcp or udp). So we have at least one local subscription and one external (network) subscription on the provided topic. + +**Publisher**: + +Regardless of whether the data is generated by a Low Level Binary Publisher or by a Protobuf Publisher, it is always written to an process internal cache first. This memory cache is then passed sequentially to the connected transport layers "shared memory", "innerprocess", "udp" and "tcp" in this order. + +Compared to the Full Zero Copy behavior described above with only local (shm) connections, we have a copy of the user payload on the publisher side again. + +This leads to the following publication sequence for a local connection: + +- Protobuf API Level: + + #. The user sets the data in the **protobuf object** + + #. The publisher **serializes** the protobuf object into a process internal data cache + + #. The publisher **locks** the SHM buffer. + + #. The publisher **copies** the process internal data cache to the SHM buffer. + + #. The publisher **unlocks** the SHM buffer + +- Binary API Level: + + #. The publisher **copies** the binary user data into a process internal data cache + + #. The publisher **locks** the SHM buffer + + #. The publisher **copies** the process internal data cache to the SHM buffer. + + #. The publisher **unlocks** the SHM buffer + + #. The publisher **informs** all connected subscriber + +**Subscriber**: + +Subscribers will always use Zero Copy, if enabled. +So they will **directly** read from the SHM buffer. + +#. The subscriber **locks** the SHM buffer + +#. The subscriber calls the **callback** function directly with the **SHM buffer** as parameter + +#. After the callback has **finished**, the subscriber **unlocks** the SHM buffer + +.. _transport_layer_shm_zerocopy_low_level: + +Low Level Memory Access +======================= + +For unleashing the full power of Full eCAL Zero Copy, the user needs to directly work on the eCAL Shared Memory via the ``CPayloadWriter`` API. The idea behind the new ``CPayloadWriter`` API is to give the user the possibility to modify only the data in the memory that has changed since the last time the date was sent. The aim is to avoid writing the complete memory and thus save computing time and reduce the latency of data transmission. + +The new payload type ``CPayloadWriter`` looks like this (all functions unnecessary for the explanation have been omitted): + +.. code-block:: cpp + + /** + * @brief Base payload writer class to allow zero copy memory operations. + * + * This class serves as the base class for payload writers, allowing zero-copy memory + * operations. The `WriteFull` and `WriteModified` calls may operate on the target + * memory file directly in zero-copy mode. + * + * A partial writing / modification of the memory file is only possible when zero-copy mode + * is activated. If zero-copy is not enabled, the `WriteModified` method is ignored and the + * `WriteFull` method is always executed (see CPublisher::ShmEnableZeroCopy) + * + */ + class CPayloadWriter + { + public: + /** + * @brief Perform a full write operation on uninitialized memory. + * + * This virtual function allows derived classes to perform a full write operation + * when the provisioned memory is uninitialized. Typically, this is the case when a + * memory file had to be recreated or its size had to be changed. + * + * @param buffer_ Pointer to the buffer containing the data to be written. + * @param size_ Size of the data to be written. + * + * @return True if the write operation is successful, false otherwise. + */ + virtual bool WriteFull(void* buffer_, size_t size_) = 0; + + /** + * @brief Perform a partial write operation to modify existing data. + * + * This virtual function allows derived classes to modify existing data when the provisioned + * memory is already initialized by a WriteFull call (i.e. contains the data from that full write operation). + * + * The memory can be partially modified and does not have to be completely rewritten, which leads to significantly + * higher performance (lower latency). + * + * If not implemented (by default), this operation will just call the `WriteFull` function. + * + * @param buffer_ Pointer to the buffer containing the data to be modified. + * @param size_ Size of the data to be modified. + * + * @return True if the write/update operation is successful, false otherwise. + */ + virtual bool WriteModified(void* buffer_, size_t size_) { return WriteFull(buffer_, size_); }; + + /** + * @brief Get the size of the required memory. + * + * This virtual function allows derived classes to provide the size of the memory + * that eCAL needs to allocate. + * + * @return The size of the required memory. + */ + virtual size_t GetSize() = 0; + }; + +The user must derive his own playload data class and implement at least the ``WriteFull`` function. This ``WriteFull`` function will be called by the low level eCAL SHM layer when finally the shared memory file needs to be written the first time (initial full write action). + +For writing partial content (modifying the memory content) the user may define a second function called ``WriteModified``. This function is called by the eCAL SHM layer if the shared memory file is in an initialized state i.e. if it was written with the previously mentioned ``WriteFull`` method. As you can see, the ``WriteModified`` function simply calls the ``WriteFull`` function by default if it is not overwritten. + +The implementation of the ``GetSize`` method is mandatory. This method is used by the eCAL SHM layer to obtain the size of the memory file that needs to be allocated. + +**Example**: + +The following primitive example shows the usage of the ``CPayloadWriter`` API to send a simple binary struct efficient by implementing a full ``WriteFull`` and an ``WriteModified`` method that is modifying a few struct elements without memcopying the whole structure again into memory. Note the in case of the none Full Zero Copy Mode only the ``WriteFull`` function will be called by eCAL. + +This is the customized new payload writer class. The ``WriteFull`` method is creating a new ``SSimpleStruct`` struct, updating its content and copying the whole structure into the opened shared memory file buffer. The ``WriteModified`` method gets a view of the opened shared memory file, and applies modifications on the struct elements ``clock`` and ``bytes`` by just apllying ``UpdateStruct``. + +.. code-block:: cpp + + // a simple struct to demonstrate + // zero copy modifications + struct alignas(4) SSimpleStruct + { + uint32_t version = 1; + uint16_t rows = 5; + uint16_t cols = 3; + uint32_t clock = 0; + uint8_t bytes[5 * 3] = { 0 }; + }; + + // a binary payload object that handles + // SSimpleStruct WriteFull and WriteModified functionality + class CStructPayload : public eCAL::CPayloadWriter + { + public: + // Write the complete SSimpleStruct to the shared memory + bool WriteFull(void* buf_, size_t len_) override + { + // check available size and pointer + if (len_ < GetSize() || buf_ == nullptr) return false; + + // create a new struct and update its content + SSimpleStruct simple_struct; + UpdateStruct(&simple_struct); + + // copy complete struct into the memory + *static_cast(buf_) = simple_struct; + + return true; + }; + + // Modify the SSimpleStruct in the shared memory + bool WriteModified(void* buf_, size_t len_) override + { + // check available size and pointer + if (len_ < GetSize() || buf_ == nullptr) return false; + + // update the struct in memory + UpdateStruct(static_cast(buf_)); + + return true; + }; + + size_t GetSize() override { return sizeof(SSimpleStruct); }; + + private: + void UpdateStruct(SSimpleStruct* simple_struct) + { + // modify the simple_struct + simple_struct->clock = clock; + for (auto i = 0; i < (simple_struct->rows * simple_struct->cols); ++i) + { + simple_struct->bytes[i] = static_cast(simple_struct->clock); + } + + // increase internal state clock + clock++; + }; + + uint32_t clock = 0; + }; + +To send this payload you just need a few lines of code: + +.. code-block:: cpp + + int main(int argc, char** argv) + { + // initialize eCAL API + eCAL::Initialize(argc, argv, "binary_payload_snd"); + + // publisher for topic "simple_struct" + eCAL::CPublisher pub("simple_struct"); + + // turn zero copy mode on + pub.ShmEnableZeroCopy(true); + + // create the simple struct payload + CStructPayload struct_payload; + + // send updates every 100 ms + while (eCAL::Ok()) + { + pub.Send(struct_payload); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + // finalize eCAL API + eCAL::Finalize(); + + return(0); + } + +Default eCAL SHM vs. Full Zero Copy SHM +============================= + +.. list-table:: Default eCAL SHM vs. Full Zero Copy SHM + :widths: 10 45 45 + :header-rows: 1 + :stub-columns: 1 + + * - + + - Default eCAL SHM + + - Full Zero Copy SHM + + * - Memcopies + + - ❌ 2 additional memcpy (1 for publishing, 1 for each subscriber) + + - ✅ No memcpy (if Low Level API is used) + + * - Partial changes + + - ❌ Changing only 1 byte causes the entire updated message to be copied to the buffer, again + + - ✅ Changing only 1 byte only costs as much as changing that 1 byte in the target memory, independent from the message size + + * - Subscriber decoupling + + - ✅ Good decoupling between subscribers. + Subscribers only block each other for the duration of that 1 memcpy + + - ❌ Subscribers need to wait for each other to finish their callbacks + + * - Pub/Sub decoupling + + - ✅ Good decoupling between publisher and subscribers. + + - If the serialization takes a long time, this can be done beforehand without having a lock on the SHM buffer + + - Publishers don't have to wait for the subscribers to finish their callbacks, only for them to copy the data to their own process memory + + - ❌ Subscribers may block publishers + + - Publishers need to wait for all subscriber callbacks to finish + + - Publishers need to keep the the SHM buffer locked while performing the message serialization + +Combining Zero Copy and Multibuffering +====================================== + +For technical reasons the Full Zero Copy mode described above is turned of if the Multibuffering option ``CPublisher::ShmSetBufferCount`` is activated. + +Default (subscriber side) Zero Copy is working in combination with Multibuffering as described. diff --git a/doc/rst/versions/5.10/whats_new.rst b/doc/rst/versions/5.10/whats_new.rst index 6ff845b870..46c7734fdc 100644 --- a/doc/rst/versions/5.10/whats_new.rst +++ b/doc/rst/versions/5.10/whats_new.rst @@ -10,7 +10,8 @@ eCAL 5.10 was released in May 2022. The release notably brings eCAL Core improvements (TCP and Shared Memory) - **Release**: May 2022 -- **End of life**: June 2023 (planned) +- +- **End of life**: July 2023 (after 14 months) New features ============ diff --git a/doc/rst/versions/5.11/whats_new.rst b/doc/rst/versions/5.11/whats_new.rst index abf6e6983b..2fda4a65e8 100644 --- a/doc/rst/versions/5.11/whats_new.rst +++ b/doc/rst/versions/5.11/whats_new.rst @@ -10,7 +10,7 @@ eCAL 5.11 was released in Nobember 2022. The release contains the eCAL Mon TUI, the eCAL Meas Cutter and the SHM Monitoring Layer. - **Release**: December 2022 -- **End of life**: November 2023 (planned) +- **End of life**: March 2024 (planned) New features ============ diff --git a/doc/rst/versions/5.12/compatibility_table.txt b/doc/rst/versions/5.12/compatibility_table.txt new file mode 100644 index 0000000000..c76e57f91c --- /dev/null +++ b/doc/rst/versions/5.12/compatibility_table.txt @@ -0,0 +1,42 @@ +.. list-table:: eCAL 5.12 vs. 5.11 + :widths: 20 80 + + * - Wire compatibility + + - * **eCAL UDP**: 100% compatible in default settings + + A proper Topic-Name -> UDP-Multicast-Group computation has been added. + By default, the old version (``multicast_config_version = v1``) is enabled in the :file:`ecal.ini`, which makes the UDP Layer 100% compatible. + If the new version (``v2``) is enabled, UDP communication between eCAL 5.12 and older versions of eCAL will fail. + + * **eCAL TCP**: 100% compatible + + * **Services**: 100% compatible + + * **eCAL Shared Memory**: 100% compatible + + * **eCAL Registration Layer**: 100% compatible + + New fields have been added in the internal protobuf format. + + * - API / ABI + + - * **API**: downwards compatible. + + * **ABI**: not compatible + + From now on, the official eCAL installer for Windows is built with Visual Studio 2019 / v142. + This means, that you need VS 2019 or newer to link against the included SDK. + + * - Tools + + - * **Rec**: Downwards compatible + + The ``SetConfig`` RPC Call has been added, which was not available in eCAL 5.11 + + * **Sys**: 100% compatible + + * **Measurements**: 100% compatible + + * **Meas Cutter**: The structure of the output directory has changed. + It is now aligned to the output structure of eCAL Rec. \ No newline at end of file diff --git a/doc/rst/versions/5.12/whats_new.rst b/doc/rst/versions/5.12/whats_new.rst new file mode 100644 index 0000000000..c52df9a31b --- /dev/null +++ b/doc/rst/versions/5.12/whats_new.rst @@ -0,0 +1,58 @@ +.. include:: /include.txt + +.. _ecal_version_5_12: + +========= +eCAL 5.12 +========= + +eCAL 5.12 was released in August 2023. + +- **Release**: August 2023 +- **End of life**: August 2024 (planned) + +New features +============ + +- **True Zero Copy!** + + eCAL had Zero Copy in the past. + But now it's even better! + With the new **low level API** you can precisely control which part of your Shared Memory message you actually have to change and therefore speed up your system even more! + + Check out the :ref:`Zero Copy documentation` for explanations and examples on how to use it! + +- **Container support through host-groups** + + You want to containerize your eCAL Apps? + Well, then this is the release for you. + In the past, you had to choose between enabling support for eCAL services or utilizing the shared memory transport layer, now you can have both simultaneously. + + Check out the :ref:`eCAL in Docker tutorial` to learn more! + + This also enables you to manage apps in your docker containers with eCAL Sys! + +- **Improved eCAL Monitor for Debugging** + + We enriched the eCAL Monitor with even more information, that allow you to debug your system. + You can now see the size and hash of your publishers' and subscribers' **descriptors**. + And - if that isn't enough for you - the eCAL Monitor got an entirely new **Raw Data panel** for very deep (but easy!) inspections of the registration layer. + +- **Improved support for C#** + + The C# language binding has seen some mayor updates. Most notably, the Client / Server API is now available in C#, so you can use eCAL's RPC features! + + +- Check out the `entire 5.12.0 changelog `_!. + +Compatibility table +=================== + +.. include:: compatibility_table.txt + +Where to download +================= + +- |fa-windows| Windows: Head to the :ref:`Download Archive ` and pick your version from there! +- |fa-ubuntu| Ubuntu: Install eCAL from :ref:`our PPA ` with ``apt-get`` *(or pick a Version from the download archive as well)* + diff --git a/doc/rst/versions/5.8/whats_new.rst b/doc/rst/versions/5.8/whats_new.rst index cf259f6b14..ff9f4ad3e2 100644 --- a/doc/rst/versions/5.8/whats_new.rst +++ b/doc/rst/versions/5.8/whats_new.rst @@ -11,7 +11,7 @@ The release notably contains the new application eCAL Sys. - **Release**: December 2020. -- **End of life**: May 2022 (after 17 Month). +- **End of life**: May 2022 (after 17 months). New features ============ diff --git a/doc/rst/versions/5.9/whats_new.rst b/doc/rst/versions/5.9/whats_new.rst index 4b4d2088f2..457bb9a86b 100644 --- a/doc/rst/versions/5.9/whats_new.rst +++ b/doc/rst/versions/5.9/whats_new.rst @@ -10,7 +10,8 @@ eCAL 5.9 was released in August 2021. The release notably contains the eCAL Rec command line Application. - **Release**: August 2021 -- **End of life**: December 2022 +- +- **End of life**: December 2022 (After 16 months) New features ============ diff --git a/doc/rst/versions/compatibility.rst b/doc/rst/versions/compatibility.rst index 07e29b9f15..62fbc615eb 100644 --- a/doc/rst/versions/compatibility.rst +++ b/doc/rst/versions/compatibility.rst @@ -33,6 +33,13 @@ Depending on what we changed in eCAL, we increment the appropriate number: You can upgrade through patch versions without having to expect any problems. Usually, you just want the latest release to profit from all bugfixes. +eCAL 5.12 vs. 5.11 +================== + +Compatibility table when upgrading from eCAL 5.11: + +.. include:: 5.12/compatibility_table.txt + eCAL 5.11 vs. 5.10 ================== diff --git a/doc/rst/versions/ecal_versions.rst b/doc/rst/versions/ecal_versions.rst index c2a46099e7..49faf15020 100644 --- a/doc/rst/versions/ecal_versions.rst +++ b/doc/rst/versions/ecal_versions.rst @@ -9,6 +9,7 @@ eCAL Versions compatibility + 5.12/whats_new 5.11/whats_new 5.10/whats_new 5.9/whats_new From b2739f12d0755ed95e0da4e2c59eb3f820705691 Mon Sep 17 00:00:00 2001 From: KerstinKeller Date: Wed, 23 Aug 2023 14:51:37 +0200 Subject: [PATCH 03/17] #1186 App, Dependencies: Make compatible with ftxui 5.0.0 (#1188) * Make version info of ftxui available to build. --- app/mon/mon_tui/CMakeLists.txt | 5 ++++- app/mon/mon_tui/src/tui/style_sheet.hpp | 22 ++++++++++++++++++++-- thirdparty/build-ftxui.cmake | 9 +++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/app/mon/mon_tui/CMakeLists.txt b/app/mon/mon_tui/CMakeLists.txt index 30520b99ee..1f3311c2d0 100644 --- a/app/mon/mon_tui/CMakeLists.txt +++ b/app/mon/mon_tui/CMakeLists.txt @@ -108,7 +108,10 @@ target_include_directories(${PROJECT_NAME} PRIVATE $) target_compile_definitions(${PROJECT_NAME} - PRIVATE $<$:PCRE_STATIC;_UNICODE>) + PRIVATE + $<$:PCRE_STATIC;_UNICODE> + FTXUI_VERSION_MAJOR=${ftxui_VERSION_MAJOR} + ) create_targets_protobuf() diff --git a/app/mon/mon_tui/src/tui/style_sheet.hpp b/app/mon/mon_tui/src/tui/style_sheet.hpp index 2b2073d281..f39c4b7a2e 100644 --- a/app/mon/mon_tui/src/tui/style_sheet.hpp +++ b/app/mon/mon_tui/src/tui/style_sheet.hpp @@ -100,7 +100,12 @@ struct StyleSheet default: return nothing; } }; - sheet.tab.entries.transform = [](EntryState state) +#if FTXUI_VERSION_MAJOR >= 5 + sheet.tab.entries_option.transform = +#else + sheet.tab.entries.transform = +#endif + [](EntryState state) { if(state.active) { @@ -110,7 +115,11 @@ struct StyleSheet } return text(state.label); }; +#if FTXUI_VERSION_MAJOR >= 5 + sheet.tab.direction = Direction::Right; +#else sheet.tab.direction = MenuOption::Right; +#endif sheet.tab.elements_prefix = [] { return text(" "); }; @@ -202,14 +211,23 @@ struct StyleSheet default: return nothing; } }; - sheet.tab.entries.transform = [](EntryState state) { +#if FTXUI_VERSION_MAJOR >= 5 + sheet.tab.entries_option.transform = +#else + sheet.tab.entries.transform = +#endif + [](EntryState state) { if(state.active) { return text(state.label) | inverted; } return text(state.label); }; +#if FTXUI_VERSION_MAJOR >= 5 + sheet.tab.direction = Direction::Right; +#else sheet.tab.direction = MenuOption::Right; +#endif sheet.tab.elements_prefix = [] { return text(" "); }; diff --git a/thirdparty/build-ftxui.cmake b/thirdparty/build-ftxui.cmake index 4665677217..a9cb1a8f77 100644 --- a/thirdparty/build-ftxui.cmake +++ b/thirdparty/build-ftxui.cmake @@ -1,3 +1,12 @@ option(FTXUI_BUILD_EXAMPLES "Set to ON to build examples" OFF) add_subdirectory(thirdparty/ftxui EXCLUDE_FROM_ALL) + +# Set ftxui_VERSION_MAJOR, because it's only defined in the subdirectory scope and we cannot access it +# Reading it automatically is less error prone than setting itt by hand +file(READ thirdparty/ftxui/CMakeLists.txt content) +if(content MATCHES "VERSION ([0-9]+)\\.[0-9]+\\.[0-9]+") + set(ftxui_VERSION_MAJOR "${CMAKE_MATCH_1}") +else() + message(FATAL_ERROR "Couldn't read version info") +endif() \ No newline at end of file From 216f4b3ce58b21f6f85954580f4a44ef027774b6 Mon Sep 17 00:00:00 2001 From: KaanConti <124665298+KaanConti@users.noreply.github.com> Date: Thu, 31 Aug 2023 09:22:35 +0200 Subject: [PATCH 04/17] Small fix for eCAL-docker documentation and one image added. (#1192) --- doc/rst/advanced/ecal_in_docker.rst | 11 ++++++++--- .../img_documentation/doku_ecal_docker_mon.png | Bin 0 -> 163422 bytes 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 doc/rst/advanced/img_documentation/doku_ecal_docker_mon.png diff --git a/doc/rst/advanced/ecal_in_docker.rst b/doc/rst/advanced/ecal_in_docker.rst index f0b97cf471..39c8167e45 100644 --- a/doc/rst/advanced/ecal_in_docker.rst +++ b/doc/rst/advanced/ecal_in_docker.rst @@ -205,7 +205,7 @@ In eCAL, you are able to set host belonging over network borders by utilizing th .. code-block:: bash - sudo docker network create --driver=bridge --subnet=192.100.0.0/24 my_network + sudo docker network create --driver=bridge --subnet=10.0.10.0/24 my_network #. Edit your :file:`ecal.ini` and run your Container within the newly created docker network @@ -225,7 +225,7 @@ In eCAL, you are able to set host belonging over network borders by utilizing th .. code-block:: bash - sudo docker run --rm -it --ipc=host --pid=host --network=my_network --name=container1 --h=container1 --ip=192.168.100.2 -v /etc/ecal/ecal.ini:/etc/ecal/ecal.ini ecal-runtime + sudo docker run --rm -it --ipc=host --pid=host --network=my_network --name=container1 -h=container1 --ip=10.0.10.10 -v /etc/ecal/ecal.ini:/etc/ecal/ecal.ini ecal-runtime - You should now be inside the root shell of your Container. Check if your :file:`ecal.ini` file is correct. @@ -250,6 +250,11 @@ In eCAL, you are able to set host belonging over network borders by utilizing th sudo ip route add 239.0.0.0/24 dev metric 1 + - Review your network configuration. Your eCAL-Monitor should resemble this example: + + .. image:: img_documentation/doku_ecal_docker_mon.png + + #. (optional) After adding the route, you register the Container with IP address and name in /etc/hosts for DNS resolution, enabling easy access to it by hostname within the network. .. code-block:: bash @@ -258,4 +263,4 @@ In eCAL, you are able to set host belonging over network borders by utilizing th .. image:: img_documentation/vscode_etc_hosts.png -After all steps are done, all eCAL nodes can communicate seamlessly from docker to the host and vice versa. +When you are done, all eCAL nodes can communicate seamlessly from docker to the host and vice versa. diff --git a/doc/rst/advanced/img_documentation/doku_ecal_docker_mon.png b/doc/rst/advanced/img_documentation/doku_ecal_docker_mon.png new file mode 100644 index 0000000000000000000000000000000000000000..881bb12f33d18083c66c0b9019c782796b03d04f GIT binary patch literal 163422 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%0D*~eq8!wq_LNqIQrf6?`pL2)(R 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%0AM7i= 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 p0000EWmrjOO-%qQ00008000000002eQ Date: Wed, 13 Sep 2023 11:04:17 +0200 Subject: [PATCH 05/17] Fixes bug regarding unregistration introduced with #1086. (#1197) Introduces two new structs (SLocalSubscriptionInfo and SExternSubscriptionInfo) and uses them for applying / unapplying registrations. They are also stored in the ExpiredMap when topic are no longer visible on monitoring layer. --- ecal/core/src/pubsub/ecal_pubgate.cpp | 32 +++++++++-------- ecal/core/src/readwrite/ecal_writer.cpp | 47 ++++++++++++------------- ecal/core/src/readwrite/ecal_writer.h | 43 +++++++++++++++++----- 3 files changed, 75 insertions(+), 47 deletions(-) diff --git a/ecal/core/src/pubsub/ecal_pubgate.cpp b/ecal/core/src/pubsub/ecal_pubgate.cpp index f6daae705f..e9324adfd9 100644 --- a/ecal/core/src/pubsub/ecal_pubgate.cpp +++ b/ecal/core/src/pubsub/ecal_pubgate.cpp @@ -114,9 +114,10 @@ namespace eCAL const auto& ecal_sample = ecal_sample_.topic(); const std::string& topic_name = ecal_sample.tname(); - const std::string& topic_id = ecal_sample.tid(); + CDataWriter::SLocalSubscriptionInfo subscription_info; + subscription_info.topic_id = ecal_sample.tid(); + subscription_info.process_id = std::to_string(ecal_sample.pid()); SDataTypeInformation topic_information{ eCALSampleToTopicInformation(ecal_sample_) }; - const std::string process_id = std::to_string(ecal_sample.pid()); std::string reader_par; for (const auto& layer : ecal_sample.tlayer()) @@ -135,7 +136,7 @@ namespace eCAL auto res = m_topic_name_datawriter_map.equal_range(topic_name); for(TopicNameDataWriterMapT::const_iterator iter = res.first; iter != res.second; ++iter) { - iter->second->ApplyLocSubscription(process_id, topic_id, topic_information, reader_par); + iter->second->ApplyLocSubscription(subscription_info, topic_information, reader_par); } } @@ -145,15 +146,16 @@ namespace eCAL const auto& ecal_sample = ecal_sample_.topic(); const std::string& topic_name = ecal_sample.tname(); - const std::string& topic_id = ecal_sample.tid(); - const std::string process_id = std::to_string(ecal_sample.pid()); + CDataWriter::SLocalSubscriptionInfo subscription_info; + subscription_info.topic_id = ecal_sample.tid(); + subscription_info.process_id = std::to_string(ecal_sample.pid()); // unregister local subscriber const std::shared_lock lock(m_topic_name_datawriter_sync); auto res = m_topic_name_datawriter_map.equal_range(topic_name); for (TopicNameDataWriterMapT::const_iterator iter = res.first; iter != res.second; ++iter) { - iter->second->RemoveLocSubscription(process_id, topic_id); + iter->second->RemoveLocSubscription(subscription_info); } } @@ -162,11 +164,12 @@ namespace eCAL if(!m_created) return; const auto& ecal_sample = ecal_sample_.topic(); - const std::string& host_name = ecal_sample.hname(); const std::string& topic_name = ecal_sample.tname(); - const std::string& topic_id = ecal_sample.tid(); + CDataWriter::SExternalSubscriptionInfo subscription_info; + subscription_info.host_name = ecal_sample.hname(); + subscription_info.topic_id = ecal_sample.tid(); + subscription_info.process_id = std::to_string(ecal_sample.pid()); SDataTypeInformation topic_information{ eCALSampleToTopicInformation(ecal_sample_) }; - const std::string process_id = std::to_string(ecal_sample.pid()); std::string reader_par; for (const auto& layer : ecal_sample.tlayer()) @@ -185,7 +188,7 @@ namespace eCAL auto res = m_topic_name_datawriter_map.equal_range(topic_name); for(TopicNameDataWriterMapT::const_iterator iter = res.first; iter != res.second; ++iter) { - iter->second->ApplyExtSubscription(host_name, process_id, topic_id, topic_information, reader_par); + iter->second->ApplyExtSubscription(subscription_info, topic_information, reader_par); } } @@ -194,17 +197,18 @@ namespace eCAL if (!m_created) return; const auto& ecal_sample = ecal_sample_.topic(); - const std::string& host_name = ecal_sample.hname(); const std::string& topic_name = ecal_sample.tname(); - const std::string& topic_id = ecal_sample.tid(); - const std::string process_id = std::to_string(ecal_sample.pid()); + CDataWriter::SExternalSubscriptionInfo subscription_info; + subscription_info.host_name = ecal_sample.hname(); + subscription_info.topic_id = ecal_sample.tid(); + subscription_info.process_id = std::to_string(ecal_sample.pid()); // unregister external subscriber const std::shared_lock lock(m_topic_name_datawriter_sync); auto res = m_topic_name_datawriter_map.equal_range(topic_name); for (TopicNameDataWriterMapT::const_iterator iter = res.first; iter != res.second; ++iter) { - iter->second->RemoveExtSubscription(host_name, process_id, topic_id); + iter->second->RemoveExtSubscription(subscription_info); } } diff --git a/ecal/core/src/readwrite/ecal_writer.cpp b/ecal/core/src/readwrite/ecal_writer.cpp index acd6b6ce8b..ce9c6f13ca 100644 --- a/ecal/core/src/readwrite/ecal_writer.cpp +++ b/ecal/core/src/readwrite/ecal_writer.cpp @@ -655,22 +655,21 @@ namespace eCAL else return 0; } - void CDataWriter::ApplyLocSubscription(const std::string& process_id_, const std::string& tid_, const SDataTypeInformation& tinfo_, const std::string& reader_par_) + void CDataWriter::ApplyLocSubscription(const SLocalSubscriptionInfo& local_info_, const SDataTypeInformation& tinfo_, const std::string& reader_par_) { - Connect(tid_, tinfo_); + Connect(local_info_.topic_id, tinfo_); // add key to local subscriber map - const std::string topic_key = process_id_ + tid_; { const std::lock_guard lock(m_sub_map_sync); - m_loc_sub_map[topic_key] = true; + m_loc_sub_map[local_info_] = true; } m_loc_subscribed = true; // add a new local subscription - m_writer.udp_mc.AddLocConnection (process_id_, reader_par_); - m_writer.shm.AddLocConnection (process_id_, reader_par_); + m_writer.udp_mc.AddLocConnection (local_info_.process_id, reader_par_); + m_writer.shm.AddLocConnection (local_info_.process_id, reader_par_); #ifndef NDEBUG // log it @@ -678,18 +677,17 @@ namespace eCAL #endif } - void CDataWriter::RemoveLocSubscription(const std::string& process_id_, const std::string& tid_) + void CDataWriter::RemoveLocSubscription(const SLocalSubscriptionInfo& local_info_) { // remove key from local subscriber map - const std::string topic_key = process_id_ + tid_; { const std::lock_guard lock(m_sub_map_sync); - m_loc_sub_map.erase(topic_key); + m_loc_sub_map.erase(local_info_); } // remove a local subscription - m_writer.udp_mc.RemLocConnection (process_id_); - m_writer.shm.RemLocConnection (process_id_); + m_writer.udp_mc.RemLocConnection (local_info_.process_id); + m_writer.shm.RemLocConnection (local_info_.process_id); #ifndef NDEBUG // log it @@ -697,22 +695,21 @@ namespace eCAL #endif } - void CDataWriter::ApplyExtSubscription(const std::string& host_name_, const std::string& process_id_, const std::string& tid_, const SDataTypeInformation& tinfo_, const std::string& reader_par_) + void CDataWriter::ApplyExtSubscription(const SExternalSubscriptionInfo& external_info_, const SDataTypeInformation& tinfo_, const std::string& reader_par_) { - Connect(tid_, tinfo_); + Connect(external_info_.topic_id, tinfo_); // add key to external subscriber map - const std::string topic_key = host_name_ + process_id_ + tid_; { const std::lock_guard lock(m_sub_map_sync); - m_ext_sub_map[topic_key] = true; + m_ext_sub_map[external_info_] = true; } m_ext_subscribed = true; // add a new external subscription - m_writer.udp_mc.AddExtConnection (host_name_, process_id_, reader_par_); - m_writer.shm.AddExtConnection (host_name_, process_id_, reader_par_); + m_writer.udp_mc.AddExtConnection (external_info_.host_name, external_info_.process_id, reader_par_); + m_writer.shm.AddExtConnection (external_info_.host_name, external_info_.process_id, reader_par_); #ifndef NDEBUG // log it @@ -720,18 +717,17 @@ namespace eCAL #endif } - void CDataWriter::RemoveExtSubscription(const std::string& host_name_, const std::string& process_id_, const std::string& tid_) + void CDataWriter::RemoveExtSubscription(const SExternalSubscriptionInfo& external_info_) { // remove key from external subscriber map - const std::string topic_key = host_name_ + process_id_ + tid_; { const std::lock_guard lock(m_sub_map_sync); - m_ext_sub_map.erase(topic_key); + m_ext_sub_map.erase(external_info_); } // remove external subscription - m_writer.udp_mc.RemExtConnection (host_name_, process_id_); - m_writer.shm.RemExtConnection (host_name_, process_id_); + m_writer.udp_mc.RemExtConnection (external_info_.host_name, external_info_.process_id); + m_writer.shm.RemExtConnection (external_info_.host_name, external_info_.process_id); } void CDataWriter::RefreshRegistration() @@ -768,7 +764,8 @@ namespace eCAL Register(false); // check connection timeouts - const std::shared_ptr> loc_timeouts = std::make_shared>(); + // Todo: Why are only Local connections removed, not external connections? + const std::shared_ptr> loc_timeouts = std::make_shared>(); { const std::lock_guard lock(m_sub_map_sync); m_loc_sub_map.remove_deprecated(loc_timeouts.get()); @@ -780,7 +777,7 @@ namespace eCAL for(const auto& loc_sub : *loc_timeouts) { - m_writer.shm.RemLocConnection(loc_sub); + m_writer.shm.RemLocConnection(loc_sub.process_id); } if (!m_loc_subscribed && !m_ext_subscribed) @@ -1275,7 +1272,7 @@ namespace eCAL const std::lock_guard lock(m_sub_map_sync); for (auto sub : m_loc_sub_map) { - if (sub.first != process_id) + if (sub.first.process_id != process_id) { is_internal_only = false; break; diff --git a/ecal/core/src/readwrite/ecal_writer.h b/ecal/core/src/readwrite/ecal_writer.h index 9cc77c90c2..ec0661c601 100644 --- a/ecal/core/src/readwrite/ecal_writer.h +++ b/ecal/core/src/readwrite/ecal_writer.h @@ -47,6 +47,31 @@ namespace eCAL class CDataWriter { public: + struct SExternalSubscriptionInfo + { + std::string host_name; + std::string process_id; + std::string topic_id; + + friend bool operator<(const SExternalSubscriptionInfo& l, const SExternalSubscriptionInfo& r) + { + return std::tie(l.host_name, l.process_id, l.topic_id) + < std::tie(r.host_name, r.process_id, r.topic_id); + } + }; + + struct SLocalSubscriptionInfo + { + std::string process_id; + std::string topic_id; + + friend bool operator<(const SLocalSubscriptionInfo& l, const SLocalSubscriptionInfo& r) + { + return std::tie(l.process_id, l.topic_id) + < std::tie(r.process_id, r.topic_id); + } + }; + CDataWriter(); ~CDataWriter(); @@ -77,11 +102,11 @@ namespace eCAL size_t Write(CPayloadWriter& payload_, long long time_, long long id_); - void ApplyLocSubscription(const std::string& process_id_, const std::string& tid_, const SDataTypeInformation& tinfo_, const std::string& reader_par_); - void RemoveLocSubscription(const std::string & process_id_, const std::string& tid_); + void ApplyLocSubscription(const SLocalSubscriptionInfo& local_info_, const SDataTypeInformation& tinfo_, const std::string& reader_par_); + void RemoveLocSubscription(const SLocalSubscriptionInfo& local_info_); - void ApplyExtSubscription(const std::string& host_name_, const std::string& process_id_, const std::string& tid_, const SDataTypeInformation& tinfo_, const std::string& reader_par_); - void RemoveExtSubscription(const std::string & host_name_, const std::string & process_id_, const std::string& tid_); + void ApplyExtSubscription(const SExternalSubscriptionInfo& external_info_, const SDataTypeInformation& tinfo_, const std::string& reader_par_); + void RemoveExtSubscription(const SExternalSubscriptionInfo& external_info_); void RefreshRegistration(); void RefreshSendCounter(); @@ -137,10 +162,12 @@ namespace eCAL std::vector m_payload_buffer; std::atomic m_connected; - using ConnectedMapT = Util::CExpMap; - mutable std::mutex m_sub_map_sync; - ConnectedMapT m_loc_sub_map; - ConnectedMapT m_ext_sub_map; + + using LocalConnectedMapT = Util::CExpMap; + using ExternalConnectedMapT = Util::CExpMap; + mutable std::mutex m_sub_map_sync; + LocalConnectedMapT m_loc_sub_map; + ExternalConnectedMapT m_ext_sub_map; std::mutex m_event_callback_map_sync; using EventCallbackMapT = std::map; From a44ce764230201a2f140a50711f0a10e18f0547e Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Tue, 19 Sep 2023 15:32:51 +0200 Subject: [PATCH 06/17] Sphinx Doc: downgrade sphinxcontrib-youtube (#1202) The latest version of sphinxcontrib-youtube is not compatible with our Sphinx version (4.5) anymore. --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 5762f479b7..dc1d7efca4 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -7,6 +7,6 @@ empy semantic-version exhale Jinja2 -sphinxcontrib-youtube +sphinxcontrib-youtube<=1.2 sphinxcontrib-apidoc From a02abee0c3a460e3251cef5773455498d5ab32b1 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:23:12 +0200 Subject: [PATCH 07/17] Rec GUI: Fixed a tooltip typo (#1201) --- .../src/widgets/recordermanager_widget/recorder_model.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/rec/rec_gui/src/widgets/recordermanager_widget/recorder_model.cpp b/app/rec/rec_gui/src/widgets/recordermanager_widget/recorder_model.cpp index 83a13762af..af959a1bcb 100644 --- a/app/rec/rec_gui/src/widgets/recordermanager_widget/recorder_model.cpp +++ b/app/rec/rec_gui/src/widgets/recordermanager_widget/recorder_model.cpp @@ -511,7 +511,7 @@ QVariant RecorderModel::data(const QModelIndex &index, int role) const { if (recorder_list_[row].recording_enabled_ && recorder_list_[row].time_error_warning_) { - return "On of the recorders appears to be out of sync!"; + return "One of the recorders appears to be out of sync!"; } } From 1a50ffacd2ef044d50348a8dcd64b110ef43c5ea Mon Sep 17 00:00:00 2001 From: KerstinKeller Date: Tue, 19 Sep 2023 17:51:05 +0200 Subject: [PATCH 08/17] eCAL Core: Callback after zero length send contains stale message 2333d20 introduced a bug when sending zero length messages over SHM - the callback would contain stale non-zero length messages from the previous call. (#1203) This commit fixes the introduced bug, and adds a testcase for this scenario. --- ecal/core/src/io/ecal_memfile_pool.cpp | 13 +-- testing/ecal/pubsub_test/src/pubsub_test.cpp | 112 +++++++++++++++++++ 2 files changed, 118 insertions(+), 7 deletions(-) diff --git a/ecal/core/src/io/ecal_memfile_pool.cpp b/ecal/core/src/io/ecal_memfile_pool.cpp index 4aa8d50b17..5a19905eda 100644 --- a/ecal/core/src/io/ecal_memfile_pool.cpp +++ b/ecal/core/src/io/ecal_memfile_pool.cpp @@ -206,19 +206,18 @@ namespace eCAL // and close the file immediately else { + // need to resize the buffer especially if data_size = 0, otherwise it might contain stale data. + receive_buffer.resize((size_t)mfile_hdr.data_size); + // read payload // if data length == 0, there is no need to further read data // we just flag to process the empty buffer - if (mfile_hdr.data_size == 0) - { - post_process_buffer = true; - } - else + if (mfile_hdr.data_size != 0) { - receive_buffer.resize((size_t)mfile_hdr.data_size); m_memfile.Read(receive_buffer.data(), (size_t)mfile_hdr.data_size, mfile_hdr.hdr_size); - post_process_buffer = true; } + + post_process_buffer = true; } // store clock diff --git a/testing/ecal/pubsub_test/src/pubsub_test.cpp b/testing/ecal/pubsub_test/src/pubsub_test.cpp index ff85053007..214412cc92 100644 --- a/testing/ecal/pubsub_test/src/pubsub_test.cpp +++ b/testing/ecal/pubsub_test/src/pubsub_test.cpp @@ -18,6 +18,8 @@ */ #include +#include +#include #include #include @@ -761,6 +763,116 @@ TEST(IO, ZeroPayloadMessageUDP) eCAL::Finalize(); } + +TEST(IO, MultipleSendsSHM) +{ + // default send string + std::vector send_vector{ "this", "is", "a", "", "testtest" }; + std::string last_received_msg; + long long last_received_timestamp; + + // initialize eCAL API + eCAL::Initialize(0, nullptr, "pubsub_test"); + + // publish / subscribe match in the same process + eCAL::Util::EnableLoopback(true); + + // create subscriber for topic "A" + eCAL::string::CSubscriber sub("A"); + + // create publisher for topic "A" + eCAL::string::CPublisher pub("A"); + pub.SetLayerMode(eCAL::TLayer::tlayer_all, eCAL::TLayer::smode_off); + pub.SetLayerMode(eCAL::TLayer::tlayer_shm, eCAL::TLayer::smode_on); + pub.ShmSetAcknowledgeTimeout(10); // Make sure we receive the data + + // add callback + auto save_data = [&last_received_msg, &last_received_timestamp](const char* /*topic_name_*/, const std::string& msg_, long long time_, long long /*clock_*/, long long /*id_*/) + { + last_received_msg = msg_; + last_received_timestamp = time_; + }; + EXPECT_TRUE(sub.AddReceiveCallback(save_data)); + + // let's match them + eCAL::Process::SleepMS(2 * CMN_REGISTRATION_REFRESH); + long long timestamp = 1; + for (const auto elem : send_vector) + { + pub.Send(elem, timestamp); + eCAL::Process::SleepMS(DATA_FLOW_TIME); + EXPECT_EQ(last_received_msg, elem); + EXPECT_EQ(last_received_timestamp, timestamp); + ++timestamp; + } + + // destroy subscriber + sub.Destroy(); + + // destroy publisher + pub.Destroy(); + + // finalize eCAL API + eCAL::Finalize(); +} + + +TEST(IO, MultipleSendsUDP) +{ + // default send string + std::vector send_vector{ "this", "is", "a", "", "testtest" }; + std::string last_received_msg; + long long last_received_timestamp; + + // initialize eCAL API + eCAL::Initialize(0, nullptr, "pubsub_test"); + + // publish / subscribe match in the same process + eCAL::Util::EnableLoopback(true); + + // create subscriber for topic "A" + eCAL::string::CSubscriber sub("A"); + + // create publisher for topic "A" + eCAL::string::CPublisher pub("A"); + pub.SetLayerMode(eCAL::TLayer::tlayer_all, eCAL::TLayer::smode_off); + pub.SetLayerMode(eCAL::TLayer::tlayer_udp_mc, eCAL::TLayer::smode_on); + pub.ShmSetAcknowledgeTimeout(10); // Make sure we receive the data + + // add callback + auto save_data = [&last_received_msg, &last_received_timestamp](const char* /*topic_name_*/, const std::string& msg_, long long time_, long long /*clock_*/, long long /*id_*/) + { + last_received_msg = msg_; + last_received_timestamp = time_; + }; + EXPECT_TRUE(sub.AddReceiveCallback(save_data)); + + // let's match them + eCAL::Process::SleepMS(2 * CMN_REGISTRATION_REFRESH); + long long timestamp = 1; + for (const auto elem : send_vector) + { + pub.Send(elem, timestamp); + eCAL::Process::SleepMS(DATA_FLOW_TIME); + EXPECT_EQ(last_received_msg, elem); + EXPECT_EQ(last_received_timestamp, timestamp); + ++timestamp; + } + + // destroy subscriber + sub.Destroy(); + + // destroy publisher + pub.Destroy(); + + // finalize eCAL API + eCAL::Finalize(); +} + + + + + #if 0 TEST(IO, ZeroPayloadMessageTCP) { From 0f2381c36f8c81d4dd017748f36fef00a39d866c Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Thu, 28 Sep 2023 09:56:15 +0200 Subject: [PATCH 09/17] GH Actions: Remove Python 3.6 wheel build on Windows (#1208) The default GH Action runners dropped Python 3.6, so we are dropping it as well. --- .github/workflows/build-windows.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 996185f666..7265a25fd2 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -240,18 +240,6 @@ jobs: shell: cmd working-directory: ${{ runner.workspace }}/_build/complete - - name: Build Python 3.6 Wheel - run: | - mkdir ".venv_36" - py -3.6 -m venv ".venv_36" - CALL ".venv_36\Scripts\activate.bat" - pip install wheel - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A x64 -T v142 -DPython_FIND_VIRTUALENV=FIRST - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A x64 -T v142 -DPython_FIND_VIRTUALENV=ONLY - cmake --build . --target create_python_wheel --config Release - shell: cmd - working-directory: ${{ runner.workspace }}/_build/complete - # - name: Build Documentation C # run: cmake --build . --target documentation_c # working-directory: ${{ runner.workspace }}/_build From 7ba32dc500adf0e8fd902ea93aabf30bdbb13019 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Fri, 29 Sep 2023 10:13:00 +0200 Subject: [PATCH 10/17] Core: Fixed a bug that may have caused drops in the SHM Layer (#1198) Prior to this commit, SHM data was dropped, if the subscriber wasn't able to access it in 5ms. Now, the subscriber re-tries, until it got access to it, or new data is available. --- ecal/core/src/io/ecal_memfile_pool.cpp | 39 ++++++++++++++++---------- ecal/core/src/io/ecal_memfile_pool.h | 2 +- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/ecal/core/src/io/ecal_memfile_pool.cpp b/ecal/core/src/io/ecal_memfile_pool.cpp index 5a19905eda..a028b5780b 100644 --- a/ecal/core/src/io/ecal_memfile_pool.cpp +++ b/ecal/core/src/io/ecal_memfile_pool.cpp @@ -36,7 +36,7 @@ namespace eCAL m_created(false), m_do_stop(false), m_is_observing(false), - m_timeout_read(0) + m_time_of_last_life_signal(std::chrono::steady_clock::now()) { } @@ -137,7 +137,7 @@ namespace eCAL { if (!m_is_observing) return false; - m_timeout_read = 0; + m_time_of_last_life_signal = std::chrono::steady_clock::now(); return true; } @@ -150,14 +150,28 @@ namespace eCAL // buffer to store memory file content std::vector receive_buffer; + // Boolean that tells whether the SHM file has new data that we have NOT already accessed + bool has_unprocessed_data = false; + // runs as long as there is no timeout and no external stop request - while((m_timeout_read < timeout_) && !m_do_stop) + while(std::chrono::steady_clock::now() - std::chrono::steady_clock::time_point(m_time_of_last_life_signal) < std::chrono::milliseconds(timeout_) + && !m_do_stop) { - // loop start in ms - auto loop_start = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + if (!has_unprocessed_data) + { + // Only wait for the new-data-event, if we haven't processed the data, yet + // check for memory file update event from shm writer (20 ms) + has_unprocessed_data = gWaitForEvent(m_event_snd, 20); + + if (has_unprocessed_data) + { + // We got a signal from the publisher! It is alive! So we reset the time since the last live signal + m_time_of_last_life_signal = std::chrono::steady_clock::now(); + } + } - // check for memory file update event from shm writer (20 ms) - if(gWaitForEvent(m_event_snd, 20)) + // If we have unprocessed data, we try to access (and process!) it + if(has_unprocessed_data) { // last chance to stop .. if(m_do_stop) break; @@ -165,6 +179,9 @@ namespace eCAL // try to open memory file (timeout 5 ms) if(m_memfile.GetReadAccess(5)) { + // We have gotten access! Now the data qualifies as processed, so next loop we will wait for the signal for new data, again. + has_unprocessed_data = false; + // read the file header SMemFileHeader mfile_hdr; ReadFileHeader(mfile_hdr); @@ -240,14 +257,6 @@ namespace eCAL } } } - - // reset timeout - m_timeout_read = 0; - } - else - { - // increase timeout in ms - m_timeout_read += std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count() - loop_start; } } diff --git a/ecal/core/src/io/ecal_memfile_pool.h b/ecal/core/src/io/ecal_memfile_pool.h index bb57da42e1..c09c7f6b7e 100644 --- a/ecal/core/src/io/ecal_memfile_pool.h +++ b/ecal/core/src/io/ecal_memfile_pool.h @@ -67,7 +67,7 @@ namespace eCAL std::atomic m_do_stop; std::atomic m_is_observing; - std::atomic m_timeout_read; + std::atomic m_time_of_last_life_signal; MemFileDataCallbackT m_data_callback; From 6e43100da13ba5ca1a20b52b9db234c9f34ddf10 Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Wed, 4 Oct 2023 10:55:13 +0200 Subject: [PATCH 11/17] Core: Fixed ack timeout issue (#1207) * Core: Fixed ack timeout issue This fixes the issue that 1 SHM Ack timeout destroyed the entire ack timeout feature. Now, the internal events are neither closed nor invalidated on timeout, so they can actually be reused later on. It is stored however that this particular event had timeouted, and it will only be waited on again, after the subscriber requested that via registration layer. * Added test --- ecal/core/src/io/ecal_memfile_sync.cpp | 22 +++- ecal/core/src/io/ecal_memfile_sync.h | 1 + testing/ecal/pubsub_test/CMakeLists.txt | 1 + .../pubsub_test/src/pubsub_acknowledge.cpp | 112 ++++++++++++++++++ 4 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 testing/ecal/pubsub_test/src/pubsub_acknowledge.cpp diff --git a/ecal/core/src/io/ecal_memfile_sync.cpp b/ecal/core/src/io/ecal_memfile_sync.cpp index f9deaab2b6..ee22d8a363 100644 --- a/ecal/core/src/io/ecal_memfile_sync.cpp +++ b/ecal/core/src/io/ecal_memfile_sync.cpp @@ -79,6 +79,10 @@ namespace eCAL { gOpenEvent(&iter->second.event_ack, event_ack_name); } + + // Set the ack event to valid again, so we will wait for the subscriber + iter->second.event_ack_is_invalid = false; + return true; } } @@ -360,14 +364,20 @@ namespace eCAL long time_to_wait_ms = static_cast(std::chrono::duration_cast(time_to_wait).count()); if (time_to_wait_ms <= 0) time_to_wait_ms = 0; + if (event_handle.second.event_ack_is_invalid) + { + // The ack event has timeouted before. Thus, we don't wait for it + // anymore, until the subscriber notifies us via registration layer + // that it is still alive. + continue; + } + if (!gWaitForEvent(event_handle.second.event_ack, time_to_wait_ms)) { - // we close the event immediately to not waste time in the next - // write call, the event will be reopened later - // in ApplyLocSubscription if the connection still exists - gCloseEvent(event_handle.second.event_ack); - // invalidate it - gInvalidateEvent(&event_handle.second.event_ack); + // Remember that this event has timeouted. This will not cause the + // publisher to wait for it anymore, until the subscriber actively + // requests that via registration layer again. + event_handle.second.event_ack_is_invalid = true; #ifndef NDEBUG Logging::Log(log_level_debug2, m_base_name + "::CSyncMemoryFile::SignalWritten - ACK event timeout"); #endif diff --git a/ecal/core/src/io/ecal_memfile_sync.h b/ecal/core/src/io/ecal_memfile_sync.h index 05b60d553d..daefd5add5 100644 --- a/ecal/core/src/io/ecal_memfile_sync.h +++ b/ecal/core/src/io/ecal_memfile_sync.h @@ -76,6 +76,7 @@ namespace eCAL { EventHandleT event_snd; EventHandleT event_ack; + bool event_ack_is_invalid = false; //!< The ack event has timeouted. Thus, we don't wait for it anymore, until the subscriber notifies us via registration layer that it is still alive. }; typedef std::unordered_map EventHandleMapT; std::mutex m_event_handle_map_sync; diff --git a/testing/ecal/pubsub_test/CMakeLists.txt b/testing/ecal/pubsub_test/CMakeLists.txt index 62505a3a34..d98150d66e 100644 --- a/testing/ecal/pubsub_test/CMakeLists.txt +++ b/testing/ecal/pubsub_test/CMakeLists.txt @@ -22,6 +22,7 @@ find_package(Threads REQUIRED) find_package(GTest REQUIRED) set(pubsub_test_src + src/pubsub_acknowledge.cpp src/pubsub_gettopics.cpp src/pubsub_multibuffer.cpp src/pubsub_test.cpp diff --git a/testing/ecal/pubsub_test/src/pubsub_acknowledge.cpp b/testing/ecal/pubsub_test/src/pubsub_acknowledge.cpp new file mode 100644 index 0000000000..32df00f385 --- /dev/null +++ b/testing/ecal/pubsub_test/src/pubsub_acknowledge.cpp @@ -0,0 +1,112 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2019 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + std::chrono::nanoseconds TimeOperation(std::function func) + { + auto start = std::chrono::steady_clock::now(); + func(); + auto end = std::chrono::steady_clock::now(); + return end - start; + } + + template + void AssertOperationExecutionTimeInRange(std::function func, std::chrono::duration min, std::chrono::duration max) + { + auto operation_time = TimeOperation(func); + EXPECT_GE(operation_time.count(), std::chrono::duration_cast(min).count()) << "Timed operation less than minimum threshold"; + EXPECT_LE(operation_time.count(), std::chrono::duration_cast(max).count()) << "Timed operation greater than maximum threshold"; + } +} + +// This test asserts that a timeouted acknowledge does not break subsequent calls +TEST(Core, TimeoutAcknowledgment) +{ + // initialize eCAL API + EXPECT_EQ(0, eCAL::Initialize(0, nullptr, "TimeoutAcknowledgment", eCAL::Init::All)); + + // enable loop back communication in the same thread + eCAL::Util::EnableLoopback(true); + + eCAL::string::CPublisher pub("topic"); + pub.ShmSetAcknowledgeTimeout(500); + auto sub1 = std::make_shared< eCAL::string::CSubscriber>("topic"); + auto sleeper_variable_time = [](const char* /*topic_name_*/, const std::string& msg_, long long /*time_*/, long long /*clock_*/, long long /*id_*/) + { + int sleep = std::stoi(msg_); + std::this_thread::sleep_for(std::chrono::milliseconds(sleep)); + }; + + sub1->AddReceiveCallback(sleeper_variable_time); + + // Registration activities + std::this_thread::sleep_for(std::chrono::seconds(2)); + + // Regular call with acknowledge should take between 9 and 12 ms. + for (int i = 0; i < 5; ++i) + { + AssertOperationExecutionTimeInRange([&pub]() + { + auto send = pub.Send("100"); + EXPECT_TRUE(send); + } + , std::chrono::milliseconds(99) + , std::chrono::milliseconds(120) + ); + } + + AssertOperationExecutionTimeInRange([&pub]() + { + auto send = pub.Send("600"); + EXPECT_TRUE(send); + } + , std::chrono::milliseconds(499) + , std::chrono::milliseconds(550) + ); + + for (int i = 0; i < 5; ++i) + { + auto now = std::chrono::steady_clock::now(); + AssertOperationExecutionTimeInRange([&pub]() + { + auto send = pub.Send("100"); + EXPECT_TRUE(send); + } + , std::chrono::milliseconds(0) + , std::chrono::milliseconds(120) + ); + std::this_thread::sleep_until(now + std::chrono::milliseconds(200)); + } + + // finalize eCAL API + // without destroying any pub / sub + EXPECT_EQ(0, eCAL::Finalize()); + +} \ No newline at end of file From 9ef4ba15cd13942338299f961a5615814e0f06f4 Mon Sep 17 00:00:00 2001 From: KerstinKeller Date: Tue, 10 Oct 2023 11:24:20 +0200 Subject: [PATCH 12/17] HDF5: Make sure datasets are always closed upon reading, even when size 0 data is read. (#1211) HDF5MeasDir should return if all files were closed properly. --- contrib/ecalhdf5/src/eh5_meas_dir.cpp | 10 ++++++---- contrib/ecalhdf5/src/eh5_meas_file_v2.cpp | 10 ++++++---- testing/contrib/ecalhdf5/hdf5_test/src/hdf5_test.cpp | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/contrib/ecalhdf5/src/eh5_meas_dir.cpp b/contrib/ecalhdf5/src/eh5_meas_dir.cpp index f1d82248b6..d2750d3184 100644 --- a/contrib/ecalhdf5/src/eh5_meas_dir.cpp +++ b/contrib/ecalhdf5/src/eh5_meas_dir.cpp @@ -95,18 +95,20 @@ bool eCAL::eh5::HDF5MeasDir::Open(const std::string& path, eAccessType access /* bool eCAL::eh5::HDF5MeasDir::Close() { + bool successfully_closed{ true }; + if (access_ == eAccessType::CREATE) { // Close all existing file writers for (auto& file_writer : file_writers_) { - file_writer.second->Close(); + successfully_closed &= file_writer.second->Close(); } // Clear the list of all file writers, which will delete them file_writers_.clear(); - return true; + return successfully_closed; } else { @@ -114,7 +116,7 @@ bool eCAL::eh5::HDF5MeasDir::Close() { if (file != nullptr) { - file->Close(); + successfully_closed &= file->Close(); delete file; file = nullptr; } @@ -125,7 +127,7 @@ bool eCAL::eh5::HDF5MeasDir::Close() entries_by_id_.clear(); entries_by_chn_.clear(); - return true; + return successfully_closed; } } diff --git a/contrib/ecalhdf5/src/eh5_meas_file_v2.cpp b/contrib/ecalhdf5/src/eh5_meas_file_v2.cpp index 2033b777c0..008623edc3 100644 --- a/contrib/ecalhdf5/src/eh5_meas_file_v2.cpp +++ b/contrib/ecalhdf5/src/eh5_meas_file_v2.cpp @@ -294,13 +294,15 @@ bool eCAL::eh5::HDF5MeasFileV2::GetEntryData(long long entry_id, void* data) con auto size = H5Dget_storage_size(dataset_id); - if (size <= 0) return false; - - auto readStatus = H5Dread(dataset_id, H5T_NATIVE_UCHAR, H5S_ALL, H5S_ALL, H5P_DEFAULT, data); + herr_t read_status = -1; + if (size >= 0) + { + read_status = H5Dread(dataset_id, H5T_NATIVE_UCHAR, H5S_ALL, H5S_ALL, H5P_DEFAULT, data); + } H5Dclose(dataset_id); - return (readStatus >= 0); + return (read_status >= 0); } diff --git a/testing/contrib/ecalhdf5/hdf5_test/src/hdf5_test.cpp b/testing/contrib/ecalhdf5/hdf5_test/src/hdf5_test.cpp index 5f7307544a..be1f3c430f 100644 --- a/testing/contrib/ecalhdf5/hdf5_test/src/hdf5_test.cpp +++ b/testing/contrib/ecalhdf5/hdf5_test/src/hdf5_test.cpp @@ -84,7 +84,7 @@ TEST(HDF5, WriteReadIntegrity) const long long t1_clock = 11LL; const std::string t2_name = "another,topic"; - const std::string t2_data = "Data of topic 2"; + const std::string t2_data = ""; const long long t2_snd_timestamp = 1002LL; const long long t2_rcv_timestamp = 2002LL; const long long t2_id = 2LL; From 286f984d3fdbbe81d9ba7f7670ab1594524d8a22 Mon Sep 17 00:00:00 2001 From: Rex Schilasky <49162693+rex-schilasky@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:43:52 +0200 Subject: [PATCH 13/17] cleanup add/rem datawriter connection logic (#1216) Core: Cleanup add/rem datawriter connection logic (no need to cherry pick anywhere) --- ecal/core/src/readwrite/ecal_writer.cpp | 29 ++++++++--------- ecal/core/src/readwrite/ecal_writer_base.h | 8 ++--- ecal/core/src/readwrite/ecal_writer_shm.cpp | 31 ++----------------- ecal/core/src/readwrite/ecal_writer_shm.h | 3 +- .../src/person_rec_events.cpp | 2 +- .../src/person_snd_events.cpp | 2 +- 6 files changed, 23 insertions(+), 52 deletions(-) diff --git a/ecal/core/src/readwrite/ecal_writer.cpp b/ecal/core/src/readwrite/ecal_writer.cpp index ce9c6f13ca..5a18c3d08d 100644 --- a/ecal/core/src/readwrite/ecal_writer.cpp +++ b/ecal/core/src/readwrite/ecal_writer.cpp @@ -668,8 +668,9 @@ namespace eCAL m_loc_subscribed = true; // add a new local subscription - m_writer.udp_mc.AddLocConnection (local_info_.process_id, reader_par_); - m_writer.shm.AddLocConnection (local_info_.process_id, reader_par_); + m_writer.udp_mc.AddLocConnection (local_info_.process_id, local_info_.topic_id, reader_par_); + m_writer.shm.AddLocConnection (local_info_.process_id, local_info_.topic_id, reader_par_); + m_writer.tcp.AddLocConnection(local_info_.process_id, local_info_.topic_id, reader_par_); #ifndef NDEBUG // log it @@ -686,8 +687,9 @@ namespace eCAL } // remove a local subscription - m_writer.udp_mc.RemLocConnection (local_info_.process_id); - m_writer.shm.RemLocConnection (local_info_.process_id); + m_writer.udp_mc.RemLocConnection (local_info_.process_id, local_info_.topic_id); + m_writer.shm.RemLocConnection (local_info_.process_id, local_info_.topic_id); + m_writer.tcp.RemLocConnection (local_info_.process_id, local_info_.topic_id); #ifndef NDEBUG // log it @@ -708,8 +710,9 @@ namespace eCAL m_ext_subscribed = true; // add a new external subscription - m_writer.udp_mc.AddExtConnection (external_info_.host_name, external_info_.process_id, reader_par_); - m_writer.shm.AddExtConnection (external_info_.host_name, external_info_.process_id, reader_par_); + m_writer.udp_mc.AddExtConnection (external_info_.host_name, external_info_.process_id, external_info_.topic_id, reader_par_); + m_writer.shm.AddExtConnection (external_info_.host_name, external_info_.process_id, external_info_.topic_id, reader_par_); + m_writer.tcp.AddExtConnection (external_info_.host_name, external_info_.process_id, external_info_.topic_id, reader_par_); #ifndef NDEBUG // log it @@ -726,8 +729,9 @@ namespace eCAL } // remove external subscription - m_writer.udp_mc.RemExtConnection (external_info_.host_name, external_info_.process_id); - m_writer.shm.RemExtConnection (external_info_.host_name, external_info_.process_id); + m_writer.udp_mc.RemExtConnection (external_info_.host_name, external_info_.process_id, external_info_.topic_id); + m_writer.shm.RemExtConnection (external_info_.host_name, external_info_.process_id, external_info_.topic_id); + m_writer.tcp.RemExtConnection (external_info_.host_name, external_info_.process_id, external_info_.topic_id); } void CDataWriter::RefreshRegistration() @@ -764,22 +768,15 @@ namespace eCAL Register(false); // check connection timeouts - // Todo: Why are only Local connections removed, not external connections? - const std::shared_ptr> loc_timeouts = std::make_shared>(); { const std::lock_guard lock(m_sub_map_sync); - m_loc_sub_map.remove_deprecated(loc_timeouts.get()); + m_loc_sub_map.remove_deprecated(); m_ext_sub_map.remove_deprecated(); m_loc_subscribed = !m_loc_sub_map.empty(); m_ext_subscribed = !m_ext_sub_map.empty(); } - for(const auto& loc_sub : *loc_timeouts) - { - m_writer.shm.RemLocConnection(loc_sub.process_id); - } - if (!m_loc_subscribed && !m_ext_subscribed) { Disconnect(); diff --git a/ecal/core/src/readwrite/ecal_writer_base.h b/ecal/core/src/readwrite/ecal_writer_base.h index 8eca724dcc..c2eee1c385 100644 --- a/ecal/core/src/readwrite/ecal_writer_base.h +++ b/ecal/core/src/readwrite/ecal_writer_base.h @@ -48,11 +48,11 @@ namespace eCAL virtual bool SetQOS(const QOS::SWriterQOS& qos_) { m_qos = qos_; return true; }; QOS::SWriterQOS GetQOS() { return(m_qos); }; - virtual bool AddLocConnection(const std::string& /*process_id_*/, const std::string& /*conn_par_*/) { return false; }; - virtual bool RemLocConnection(const std::string& /*process_id_*/) { return false; }; + virtual void AddLocConnection(const std::string& /*process_id_*/, const std::string& /*topic_id_*/, const std::string& /*conn_par_*/) {}; + virtual void RemLocConnection(const std::string& /*process_id_*/, const std::string& /*topic_id_*/) {}; - virtual bool AddExtConnection(const std::string& /*host_name_*/, const std::string& /*process_id_*/, const std::string& /*conn_par_*/) { return false; }; - virtual bool RemExtConnection(const std::string& /*host_name_*/, const std::string& /*process_id_*/) { return false; }; + virtual void AddExtConnection(const std::string& /*host_name_*/, const std::string& /*process_id_*/, const std::string& /*topic_id_*/, const std::string& /*conn_par_*/) {}; + virtual void RemExtConnection(const std::string& /*host_name_*/, const std::string& /*process_id_*/, const std::string& /*topic_id_*/) {}; virtual std::string GetConnectionParameter() { return ""; }; diff --git a/ecal/core/src/readwrite/ecal_writer_shm.cpp b/ecal/core/src/readwrite/ecal_writer_shm.cpp index eefbb74227..5dbee9cd44 100644 --- a/ecal/core/src/readwrite/ecal_writer_shm.cpp +++ b/ecal/core/src/readwrite/ecal_writer_shm.cpp @@ -177,39 +177,14 @@ namespace eCAL return sent; } - bool CDataWriterSHM::AddLocConnection(const std::string& process_id_, const std::string& /*conn_par_*/) + void CDataWriterSHM::AddLocConnection(const std::string& process_id_, const std::string& /*topic_id_*/, const std::string& /*conn_par_*/) { - if (!m_created) return false; - bool ret_state(true); + if (!m_created) return; for (auto& memory_file : m_memory_file_vec) { - ret_state &= memory_file->Connect(process_id_); + memory_file->Connect(process_id_); } - - return ret_state; - } - - bool CDataWriterSHM::RemLocConnection(const std::string& process_id_) - { - if (!m_created) return false; - bool ret_state(true); - - for (auto& memory_file : m_memory_file_vec) - { - // This is not working correctly under POSIX for memory files that are read and written within the same process. - // - // The functions 'CSyncMemoryFile::Disconnect' and 'CDataWriterSHM::RemLocConnection' are now called - // by the new Subscriber Unregistration event logic and were never called in any previous eCAL version. - // - // TODO: Fix this in 'CSyncMemoryFile::Disconnect' to handle event resources properly. - if (std::to_string(eCAL::Process::GetProcessID()) != process_id_) - { - ret_state &= memory_file->Disconnect(process_id_); - } - } - - return ret_state; } std::string CDataWriterSHM::GetConnectionParameter() diff --git a/ecal/core/src/readwrite/ecal_writer_shm.h b/ecal/core/src/readwrite/ecal_writer_shm.h index b4dbdbfd64..6dd7e72e40 100644 --- a/ecal/core/src/readwrite/ecal_writer_shm.h +++ b/ecal/core/src/readwrite/ecal_writer_shm.h @@ -51,8 +51,7 @@ namespace eCAL bool Write(CPayloadWriter& payload_, const SWriterAttr& attr_) override; - bool AddLocConnection(const std::string& process_id_, const std::string& conn_par_) override; - bool RemLocConnection(const std::string& process_id_) override; + void AddLocConnection(const std::string& process_id_, const std::string& /*topic_id_*/, const std::string& conn_par_) override; std::string GetConnectionParameter() override; diff --git a/samples/cpp/pubsub/protobuf/person_rec_events/src/person_rec_events.cpp b/samples/cpp/pubsub/protobuf/person_rec_events/src/person_rec_events.cpp index dd592e5da9..fbabe467ea 100644 --- a/samples/cpp/pubsub/protobuf/person_rec_events/src/person_rec_events.cpp +++ b/samples/cpp/pubsub/protobuf/person_rec_events/src/person_rec_events.cpp @@ -26,7 +26,7 @@ void OnEvent(const char* topic_name_, const struct eCAL::SSubEventCallbackData* data_) { - std::cout << "topic name : " << topic_name_ << std::endl; + std::cout << "topic name : " << topic_name_ << std::endl; switch (data_->type) { case sub_event_connected: diff --git a/samples/cpp/pubsub/protobuf/person_snd_events/src/person_snd_events.cpp b/samples/cpp/pubsub/protobuf/person_snd_events/src/person_snd_events.cpp index 3c70c7767a..a61356622e 100644 --- a/samples/cpp/pubsub/protobuf/person_snd_events/src/person_snd_events.cpp +++ b/samples/cpp/pubsub/protobuf/person_snd_events/src/person_snd_events.cpp @@ -26,7 +26,7 @@ void OnEvent(const char* topic_name_, const struct eCAL::SPubEventCallbackData* data_) { - std::cout << "topic name : " << topic_name_ << std::endl; + std::cout << "topic name : " << topic_name_ << std::endl; switch (data_->type) { case pub_event_connected: From f05fdb87a475565aacba3d2ccb6f562e41a71129 Mon Sep 17 00:00:00 2001 From: Rex Schilasky <49162693+rex-schilasky@users.noreply.github.com> Date: Wed, 25 Oct 2023 17:03:32 +0200 Subject: [PATCH 14/17] additional logging added for shm memfile creation/handling (#1225) return values of ShmSetBufferCount evaluated in CSyncMemoryFile::Create function to state failed creation --- ecal/core/src/io/ecal_memfile.cpp | 4 +-- ecal/core/src/io/ecal_memfile_sync.cpp | 4 +-- ecal/core/src/io/ecal_memfile_sync.h | 1 + ecal/core/src/io/linux/ecal_memfile_os.cpp | 13 +++++-- ecal/core/src/readwrite/ecal_writer.cpp | 40 ++++++++++++++++----- ecal/core/src/readwrite/ecal_writer_shm.cpp | 17 ++++++--- 6 files changed, 59 insertions(+), 20 deletions(-) diff --git a/ecal/core/src/io/ecal_memfile.cpp b/ecal/core/src/io/ecal_memfile.cpp index 5d1b84dfdf..6c671845b5 100644 --- a/ecal/core/src/io/ecal_memfile.cpp +++ b/ecal/core/src/io/ecal_memfile.cpp @@ -87,7 +87,7 @@ namespace eCAL if (!memfile::db::AddFile(name_, create_, create_ ? len_ + m_header.int_hdr_size : SIZEOF_PARTIAL_STRUCT(SInternalHeader, int_hdr_size), m_memfile_info)) { #ifndef NDEBUG - printf("Could not create memory file: %s.\n\n", name_); + printf("Could not create memory file: %s.\n", name_); #endif return(false); } @@ -98,7 +98,7 @@ namespace eCAL if(!m_memfile_mutex.Create(name_, m_auto_sanitizing)) { #ifndef NDEBUG - printf("Could not create memory file mutex: %s.\n\n", name_); + printf("Could not create memory file mutex: %s.\n", name_); #endif return(false); } diff --git a/ecal/core/src/io/ecal_memfile_sync.cpp b/ecal/core/src/io/ecal_memfile_sync.cpp index ee22d8a363..ce071df73d 100644 --- a/ecal/core/src/io/ecal_memfile_sync.cpp +++ b/ecal/core/src/io/ecal_memfile_sync.cpp @@ -251,12 +251,12 @@ namespace eCAL // create the memory file if (!m_memfile.Create(m_memfile_name.c_str(), true, memfile_size)) { - Logging::Log(log_level_error, std::string(m_base_name + "::CSyncMemoryFile::Create - FAILED : ") + m_memfile_name); + Logging::Log(log_level_error, std::string("CSyncMemoryFile::Create FAILED : ") + m_memfile_name); return false; } #ifndef NDEBUG - Logging::Log(log_level_debug2, std::string(m_base_name + "::CSyncMemoryFile::Create - SUCCESS : ") + m_memfile_name); + Logging::Log(log_level_debug2, std::string("CSyncMemoryFile::Create SUCCESS : ") + m_memfile_name); #endif // initialize memory file with empty header diff --git a/ecal/core/src/io/ecal_memfile_sync.h b/ecal/core/src/io/ecal_memfile_sync.h index daefd5add5..a6756116d0 100644 --- a/ecal/core/src/io/ecal_memfile_sync.h +++ b/ecal/core/src/io/ecal_memfile_sync.h @@ -57,6 +57,7 @@ namespace eCAL std::string GetName() const; size_t GetSize() const; + bool IsCreated() const { return m_created; }; protected: bool Create(const std::string& base_name_, size_t size_); diff --git a/ecal/core/src/io/linux/ecal_memfile_os.cpp b/ecal/core/src/io/linux/ecal_memfile_os.cpp index 8857bce875..5d2fb89132 100644 --- a/ecal/core/src/io/linux/ecal_memfile_os.cpp +++ b/ecal/core/src/io/linux/ecal_memfile_os.cpp @@ -60,7 +60,14 @@ namespace eCAL umask(previous_umask); // reset umask to previous permissions if (mem_file_info_.memfile == -1) { - std::cout << "shm_open failed : " << mem_file_info_.name << " errno: " << strerror(errno) << std::endl; + if(create_) + { + std::cerr << "shm_open failed to CREATE memory file (memfile::os::AllocFile): " << mem_file_info_.name << " errno: " << strerror(errno) << std::endl; + } + else + { + std::cerr << "shm_open failed to OPEN memory file (memfile::os::AllocFile): " << mem_file_info_.name << " errno: " << strerror(errno) << std::endl; + } mem_file_info_.memfile = 0; mem_file_info_.name = ""; mem_file_info_.exists = false; @@ -101,7 +108,7 @@ namespace eCAL // truncate file if (::ftruncate(mem_file_info_.memfile, mem_file_info_.size) != 0) { - std::cout << "ftruncate failed : " << mem_file_info_.name << " errno: " << strerror(errno) << std::endl; + std::cerr << "ftruncate failed (memfile::os::MapFile): " << mem_file_info_.name << " errno: " << strerror(errno) << std::endl; } } @@ -113,7 +120,7 @@ namespace eCAL if (mem_file_info_.mem_address == MAP_FAILED) { mem_file_info_.mem_address = nullptr; - std::cout << "mmap failed : " << mem_file_info_.name << " errno: " << strerror(errno) << std::endl; + std::cerr << "mmap failed (memfile::os::MapFile): " << mem_file_info_.name << " errno: " << strerror(errno) << std::endl; return(false); } } diff --git a/ecal/core/src/readwrite/ecal_writer.cpp b/ecal/core/src/readwrite/ecal_writer.cpp index 5a18c3d08d..aa289ffcdb 100644 --- a/ecal/core/src/readwrite/ecal_writer.cpp +++ b/ecal/core/src/readwrite/ecal_writer.cpp @@ -1057,10 +1057,16 @@ namespace eCAL { case TLayer::eSendMode::smode_auto: case TLayer::eSendMode::smode_on: - m_writer.udp_mc.Create(m_host_name, m_topic_name, m_topic_id); + if (m_writer.udp_mc.Create(m_host_name, m_topic_name, m_topic_id)) + { #ifndef NDEBUG - Logging::Log(log_level_debug4, m_topic_name + "::CDataWriter::Create::UDP_MC_WRITER"); + Logging::Log(log_level_debug4, m_topic_name + "::CDataWriter::Create::UDP_MC_WRITER - SUCCESS"); #endif + } + else + { + Logging::Log(log_level_error, m_topic_name + "::CDataWriter::Create::UDP_MC_WRITER - FAILED"); + } break; case TLayer::eSendMode::smode_none: case TLayer::eSendMode::smode_off: @@ -1081,10 +1087,16 @@ namespace eCAL { case TLayer::eSendMode::smode_auto: case TLayer::eSendMode::smode_on: - m_writer.shm.Create(m_host_name, m_topic_name, m_topic_id); + if (m_writer.shm.Create(m_host_name, m_topic_name, m_topic_id)) + { #ifndef NDEBUG - Logging::Log(log_level_debug4, m_topic_name + "::CDataWriter::Create::SHM_WRITER"); + Logging::Log(log_level_debug4, m_topic_name + "::CDataWriter::Create::SHM_WRITER - SUCCESS"); #endif + } + else + { + Logging::Log(log_level_error, m_topic_name + "::CDataWriter::Create::SHM_WRITER - FAILED"); + } break; case TLayer::eSendMode::smode_none: case TLayer::eSendMode::smode_off: @@ -1105,10 +1117,16 @@ namespace eCAL { case TLayer::eSendMode::smode_auto: case TLayer::eSendMode::smode_on: - m_writer.tcp.Create(m_host_name, m_topic_name, m_topic_id); + if (m_writer.tcp.Create(m_host_name, m_topic_name, m_topic_id)) + { #ifndef NDEBUG - Logging::Log(log_level_debug4, m_topic_name + "::CDataWriter::Create::TCP_WRITER"); + Logging::Log(log_level_debug4, m_topic_name + "::CDataWriter::Create::TCP_WRITER - SUCCESS"); #endif + } + else + { + Logging::Log(log_level_error, m_topic_name + "::CDataWriter::Create::TCP_WRITER - FAILED"); + } break; case TLayer::eSendMode::smode_none: case TLayer::eSendMode::smode_off: @@ -1129,10 +1147,16 @@ namespace eCAL { case TLayer::eSendMode::smode_auto: case TLayer::eSendMode::smode_on: - m_writer.inproc.Create(m_host_name, m_topic_name, m_topic_id); + if (m_writer.inproc.Create(m_host_name, m_topic_name, m_topic_id)) + { #ifndef NDEBUG - Logging::Log(log_level_debug4, m_topic_name + "::CDataWriter::Create::INPROC_WRITER"); + Logging::Log(log_level_debug4, m_topic_name + "::CDataWriter::Create::INPROC_WRITER - SUCCESS"); #endif + } + else + { + Logging::Log(log_level_error, m_topic_name + "::CDataWriter::Create::INPROC_WRITER - FAILED"); + } break; default: m_writer.inproc.Destroy(); diff --git a/ecal/core/src/readwrite/ecal_writer_shm.cpp b/ecal/core/src/readwrite/ecal_writer_shm.cpp index 5dbee9cd44..fd11fd548d 100644 --- a/ecal/core/src/readwrite/ecal_writer_shm.cpp +++ b/ecal/core/src/readwrite/ecal_writer_shm.cpp @@ -79,9 +79,7 @@ namespace eCAL m_memory_file_attr.timeout_ack_ms = Config::GetMemfileAckTimeoutMs(); // initialize memory file buffer - SetBufferCount(m_buffer_count); - - m_created = true; + m_created = SetBufferCount(m_buffer_count);; return m_created; } @@ -124,12 +122,21 @@ namespace eCAL memory_file_size = m_memory_file_attr.min_size; } - // recreate memory file vector + // create memory file vector m_memory_file_vec.clear(); while (m_memory_file_vec.size() < buffer_count_) { auto sync_memfile = std::make_shared(m_memfile_base_name, memory_file_size, m_memory_file_attr); - m_memory_file_vec.push_back(sync_memfile); + if (sync_memfile->IsCreated()) + { + m_memory_file_vec.push_back(sync_memfile); + } + else + { + m_memory_file_vec.clear(); + Logging::Log(log_level_error, "CDataWriterSHM::SetBufferCount - FAILED"); + return false; + } } return true; From db69c37b6710d2aa4c96fc6d4b82e1e56e5276c4 Mon Sep 17 00:00:00 2001 From: Rex Schilasky <49162693+rex-schilasky@users.noreply.github.com> Date: Wed, 25 Oct 2023 18:26:30 +0200 Subject: [PATCH 15/17] core: new internal event functions with ownership handling (ownership used for linux implementation only) (#1222) --- ecal/core/src/ecal_event.cpp | 73 ++++++++++++++++-- ecal/core/src/ecal_event_internal.h | 54 +++++++++++++ ecal/core/src/ecal_thread.cpp | 3 +- ecal/core/src/ecal_util.cpp | 3 +- ecal/core/src/io/ecal_memfile_pool.cpp | 5 +- ecal/core/src/io/ecal_memfile_sync.cpp | 7 +- ecal/core/src/pubsub/ecal_subgate.cpp | 3 +- ecal/core/src/readwrite/ecal_writer_shm.cpp | 3 + ecal/core/src/readwrite/ecal_writer_shm.h | 2 +- testing/ecal/pubsub_test/src/pubsub_test.cpp | 79 ++++++++++++++++++-- 10 files changed, 208 insertions(+), 24 deletions(-) create mode 100644 ecal/core/src/ecal_event_internal.h diff --git a/ecal/core/src/ecal_event.cpp b/ecal/core/src/ecal_event.cpp index e93ecb9344..9b81d2e122 100644 --- a/ecal/core/src/ecal_event.cpp +++ b/ecal/core/src/ecal_event.cpp @@ -18,7 +18,7 @@ */ /** - * @brief eCAL handle helper class - windows platform + * @brief eCAL handle helper class **/ #include @@ -35,12 +35,12 @@ #include "ecal_win_main.h" -namespace eCAL +namespace { - bool gOpenEvent(EventHandleT* event_, const std::string& event_name_) + bool OpenEvent(eCAL::EventHandleT* event_, const std::string& event_name_) { if(event_ == nullptr) return(false); - EventHandleT event; + eCAL::EventHandleT event; event.name = event_name_; event.handle = ::CreateEvent(nullptr, false, false, event_name_.c_str()); if(event.handle != nullptr) @@ -50,6 +50,25 @@ namespace eCAL } return(false); } +} + +namespace eCAL +{ + bool gOpenNamedEvent(eCAL::EventHandleT* event_, const std::string& event_name_, bool /*ownership_*/) + { + return OpenEvent(event_, event_name_); + } + + bool gOpenUnnamedEvent(eCAL::EventHandleT* event_) + { + return OpenEvent(event_, ""); + } + + // deprecated + bool gOpenEvent(EventHandleT* event_, const std::string& event_name_) + { + return OpenEvent(event_, event_name_); + } bool gCloseEvent(const EventHandleT& event_) { @@ -302,9 +321,10 @@ namespace eCAL class CNamedEvent { public: - explicit CNamedEvent(const std::string& name_) : + explicit CNamedEvent(const std::string& name_, bool ownership_) : m_name(name_ + "_evt"), - m_event(nullptr) + m_event(nullptr), + m_owner(ownership_) { m_name = (m_name[0] != '/') ? "/" + m_name : m_name; // make memory file path compatible for all posix systems m_event = named_event_open(m_name.c_str()); @@ -318,7 +338,10 @@ namespace eCAL { if(m_event == nullptr) return; named_event_close(m_event); - named_event_destroy(m_name.c_str()); + if(m_owner) + { + named_event_destroy(m_name.c_str()); + } } void set() @@ -371,8 +394,42 @@ namespace eCAL std::string m_name; named_event_t* m_event; + bool m_owner; }; + bool gOpenNamedEvent(EventHandleT* event_, const std::string& event_name_, bool ownership_) + { + if(event_ == nullptr) return(false); + + EventHandleT event; + event.name = event_name_; + event.handle = new CNamedEvent(event.name, ownership_); + + if(event.handle != nullptr) + { + *event_ = event; + return true; + } + return false; + } + + bool gOpenUnnamedEvent(EventHandleT* event_) + { + if(event_ == nullptr) return(false); + + EventHandleT event; + event.name = ""; + event.handle = new CEvent(); + + if(event.handle != nullptr) + { + *event_ = event; + return true; + } + return false; + } + + // deprecated bool gOpenEvent(EventHandleT* event_, const std::string& event_name_) { if(event_ == nullptr) return(false); @@ -386,7 +443,7 @@ namespace eCAL } else { - event.handle = new CNamedEvent(event.name); + event.handle = new CNamedEvent(event.name, true); } if(event.handle != nullptr) diff --git a/ecal/core/src/ecal_event_internal.h b/ecal/core/src/ecal_event_internal.h new file mode 100644 index 0000000000..5441d95769 --- /dev/null +++ b/ecal/core/src/ecal_event_internal.h @@ -0,0 +1,54 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2019 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +/** + * @file ecal_event_internal.h + * @brief eCAL event interface (internal) + * + * This file will be renamed back to ecal_event.h after removing event API from eCAL's public API. +**/ + +#pragma once + +#include + +#include + +namespace eCAL +{ + /** + * @brief Open a named event with ownership. + * + * @param [out] event_ Returned event struct. + * @param event_name_ Event name. + * @param ownership_ Event is owned by the caller and will be destroyed on CloseEvent + * + * @return True if succeeded. + **/ + bool gOpenNamedEvent(eCAL::EventHandleT* event_, const std::string& event_name_, bool ownership_); + + /** + * @brief Open an unnamed event. + * + * @param [out] event_ Returned event struct. + * + * @return True if succeeded. + **/ + bool gOpenUnnamedEvent(eCAL::EventHandleT* event_); +} diff --git a/ecal/core/src/ecal_thread.cpp b/ecal/core/src/ecal_thread.cpp index e4bf8aed82..7bb1ce5cc9 100644 --- a/ecal/core/src/ecal_thread.cpp +++ b/ecal/core/src/ecal_thread.cpp @@ -23,6 +23,7 @@ #include +#include "ecal_event_internal.h" #include "ecal_thread.h" #include @@ -41,7 +42,7 @@ namespace eCAL { if(m_tdata.is_started) return(0); - gOpenEvent(&m_tdata.event); + gOpenUnnamedEvent(&m_tdata.event); m_tdata.do_stop = false; m_tdata.period = period_; m_tdata.ext_caller = ext_caller_; diff --git a/ecal/core/src/ecal_util.cpp b/ecal/core/src/ecal_util.cpp index df8806be9e..e4e3f117d2 100644 --- a/ecal/core/src/ecal_util.cpp +++ b/ecal/core/src/ecal_util.cpp @@ -22,6 +22,7 @@ #include #include "ecal_def.h" +#include "ecal_event_internal.h" #include "ecal_descgate.h" #include "ecal_process.h" #include "ecal_registration_receiver.h" @@ -91,7 +92,7 @@ namespace eCAL { const std::string event_name = EVENT_SHUTDOWN_PROC + std::string("_") + std::to_string(process_id_); EventHandleT event; - if (gOpenEvent(&event, event_name)) + if (gOpenNamedEvent(&event, event_name, true)) { std::cout << "Shutdown local eCAL process " << process_id_ << std::endl; gSetEvent(event); diff --git a/ecal/core/src/io/ecal_memfile_pool.cpp b/ecal/core/src/io/ecal_memfile_pool.cpp index a028b5780b..04ac67929a 100644 --- a/ecal/core/src/io/ecal_memfile_pool.cpp +++ b/ecal/core/src/io/ecal_memfile_pool.cpp @@ -23,6 +23,7 @@ **/ #include "ecal_def.h" +#include "ecal_event_internal.h" #include "ecal_memfile_pool.h" #include @@ -54,8 +55,8 @@ namespace eCAL if (m_created) return false; // open memory file events - gOpenEvent(&m_event_snd, memfile_event_); - gOpenEvent(&m_event_ack, memfile_event_ + "_ack"); + gOpenNamedEvent(&m_event_snd, memfile_event_, false); + gOpenNamedEvent(&m_event_ack, memfile_event_ + "_ack", false); // create memory file access m_memfile.Create(memfile_name_.c_str(), false); diff --git a/ecal/core/src/io/ecal_memfile_sync.cpp b/ecal/core/src/io/ecal_memfile_sync.cpp index ce071df73d..4dd1cf310b 100644 --- a/ecal/core/src/io/ecal_memfile_sync.cpp +++ b/ecal/core/src/io/ecal_memfile_sync.cpp @@ -24,6 +24,7 @@ #include #include +#include "ecal_event_internal.h" #include "ecal_memfile_header.h" #include "ecal_memfile_naming.h" #include "ecal_memfile_sync.h" @@ -65,8 +66,8 @@ namespace eCAL if (iter == m_event_handle_map.end()) { SEventHandlePair event_pair; - gOpenEvent(&event_pair.event_snd, event_snd_name); - gOpenEvent(&event_pair.event_ack, event_ack_name); + gOpenNamedEvent(&event_pair.event_snd, event_snd_name, true); + gOpenNamedEvent(&event_pair.event_ack, event_ack_name, true); m_event_handle_map.insert(std::pair(process_id_, event_pair)); return true; } @@ -77,7 +78,7 @@ namespace eCAL // event was deactivated by a sync timeout in SendSyncEvents if (!gEventIsValid(iter->second.event_ack)) { - gOpenEvent(&iter->second.event_ack, event_ack_name); + gOpenNamedEvent(&iter->second.event_ack, event_ack_name, true); } // Set the ack event to valid again, so we will wait for the subscriber diff --git a/ecal/core/src/pubsub/ecal_subgate.cpp b/ecal/core/src/pubsub/ecal_subgate.cpp index de43f46526..757e4ac827 100644 --- a/ecal/core/src/pubsub/ecal_subgate.cpp +++ b/ecal/core/src/pubsub/ecal_subgate.cpp @@ -31,6 +31,7 @@ #endif #include "ecal_def.h" +#include "ecal_event_internal.h" #include "ecal_descgate.h" #include "pubsub/ecal_subgate.h" @@ -47,7 +48,7 @@ namespace eCAL static const std::string event_name(EVENT_SHUTDOWN_PROC + std::string("_") + std::to_string(Process::GetProcessID())); if (!gEventIsValid(evt)) { - gOpenEvent(&evt, event_name); + gOpenNamedEvent(&evt, event_name, true); } return(evt); } diff --git a/ecal/core/src/readwrite/ecal_writer_shm.cpp b/ecal/core/src/readwrite/ecal_writer_shm.cpp index fd11fd548d..fd3efdac5f 100644 --- a/ecal/core/src/readwrite/ecal_writer_shm.cpp +++ b/ecal/core/src/readwrite/ecal_writer_shm.cpp @@ -191,6 +191,9 @@ namespace eCAL for (auto& memory_file : m_memory_file_vec) { memory_file->Connect(process_id_); +#ifndef NDEBUG + Logging::Log(log_level_debug1, std::string("CDataWriterSHM::AddLocConnection - Memory FileName: ") + memory_file->GetName() + " to ProcessId " + process_id_); +#endif } } diff --git a/ecal/core/src/readwrite/ecal_writer_shm.h b/ecal/core/src/readwrite/ecal_writer_shm.h index 6dd7e72e40..daac497abf 100644 --- a/ecal/core/src/readwrite/ecal_writer_shm.h +++ b/ecal/core/src/readwrite/ecal_writer_shm.h @@ -51,7 +51,7 @@ namespace eCAL bool Write(CPayloadWriter& payload_, const SWriterAttr& attr_) override; - void AddLocConnection(const std::string& process_id_, const std::string& /*topic_id_*/, const std::string& conn_par_) override; + void AddLocConnection(const std::string& process_id_, const std::string& topic_id_, const std::string& conn_par_) override; std::string GetConnectionParameter() override; diff --git a/testing/ecal/pubsub_test/src/pubsub_test.cpp b/testing/ecal/pubsub_test/src/pubsub_test.cpp index 214412cc92..5d908e4453 100644 --- a/testing/ecal/pubsub_test/src/pubsub_test.cpp +++ b/testing/ecal/pubsub_test/src/pubsub_test.cpp @@ -869,10 +869,6 @@ TEST(IO, MultipleSendsUDP) eCAL::Finalize(); } - - - - #if 0 TEST(IO, ZeroPayloadMessageTCP) { @@ -923,8 +919,6 @@ TEST(IO, ZeroPayloadMessageTCP) } #endif -#include -#include TEST(IO, DestroyInCallback) { /* Test setup : @@ -988,4 +982,75 @@ TEST(IO, DestroyInCallback) // finalize eCAL API // without destroying any pub / sub eCAL::Finalize(); -} \ No newline at end of file +} + +TEST(IO, SubscriberReconnection) +{ + /* Test setup : + * publisher runs permanently in a thread + * subscriber start reading + * subscriber gets out of scope (destruction) + * subscriber starts again in a new scope + * Test ensures that subscriber is reconnecting and all sync mechanism are working properly again. + */ + + // initialize eCAL API + eCAL::Initialize(0, nullptr, "SubscriberReconnection"); + + // enable loop back communication in the same thread + eCAL::Util::EnableLoopback(true); + + // start publishing thread + std::atomic stop_publishing(false); + eCAL::string::CPublisher pub_foo("foo"); + std::thread pub_foo_t([&pub_foo, &stop_publishing]() { + while (!stop_publishing) + { + pub_foo.Send("Hello World"); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + std::cout << "Stopped publishing" << std::endl; + }); + + // scope 1 + { + size_t callback_received_count(0); + + eCAL::string::CSubscriber sub_foo("foo"); + auto receive_lambda = [&sub_foo, &callback_received_count](const char* /*topic_*/, const std::string& /*msg*/, long long /*time_*/, long long /*clock_*/, long long /*id_*/) { + std::cout << "Receiving in scope 1" << std::endl; + callback_received_count++; + }; + sub_foo.AddReceiveCallback(receive_lambda); + + // sleep for 2 seconds, we should receive something + std::this_thread::sleep_for(std::chrono::seconds(2)); + + EXPECT_TRUE(callback_received_count > 0); + } + + // scope 2 + { + size_t callback_received_count(0); + + eCAL::string::CSubscriber sub_foo("foo"); + auto receive_lambda = [&sub_foo, &callback_received_count](const char* /*topic_*/, const std::string& /*msg*/, long long /*time_*/, long long /*clock_*/, long long /*id_*/) { + std::cout << "Receiving in scope 2" << std::endl; + callback_received_count++; + }; + sub_foo.AddReceiveCallback(receive_lambda); + + // sleep for 2 seconds, we should receive something + std::this_thread::sleep_for(std::chrono::seconds(2)); + + EXPECT_TRUE(callback_received_count > 0); + } + + // stop publishing and join thread + stop_publishing = true; + pub_foo_t.join(); + + // finalize eCAL API + // without destroying any pub / sub + eCAL::Finalize(); +} From 2d41066b9ab0ce9dabbb5fac7b1c301359671b0f Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Thu, 26 Oct 2023 17:12:43 +0200 Subject: [PATCH 16/17] GCC 13: Added workaround for termcolor bug (missing include) (#1228) Currently (2023-10-26) termcolor does not include the cstdint header file, but it is needed for Ubuntu 23.10 / gcc 13.2: https://github.com/ikalnytskyi/termcolor/pull/72 --- app/play/play_cli/src/ecal_play_cli.cpp | 1 + app/rec/rec_server_cli/src/commands/status.cpp | 1 + app/rec/rec_server_cli/src/commands/table_printer.cpp | 1 + app/sys/sys_cli/src/commands/list.cpp | 1 + 4 files changed, 4 insertions(+) diff --git a/app/play/play_cli/src/ecal_play_cli.cpp b/app/play/play_cli/src/ecal_play_cli.cpp index e0cd11e1db..3049a1a718 100644 --- a/app/play/play_cli/src/ecal_play_cli.cpp +++ b/app/play/play_cli/src/ecal_play_cli.cpp @@ -43,6 +43,7 @@ #pragma warning(push) #pragma warning(disable: 4800 ) #endif //_MSC_VER +#include // Needed for termcolor. Currently (2023-10-26) termcolor does not include this header file, but it is needed for Ubuntu 23.10 / gcc 13.2: https://github.com/ikalnytskyi/termcolor/pull/72 #include #ifdef _MSC_VER #pragma warning(pop) diff --git a/app/rec/rec_server_cli/src/commands/status.cpp b/app/rec/rec_server_cli/src/commands/status.cpp index 09ed0a3fee..768b497e5d 100644 --- a/app/rec/rec_server_cli/src/commands/status.cpp +++ b/app/rec/rec_server_cli/src/commands/status.cpp @@ -38,6 +38,7 @@ #pragma warning(disable: 4800) // disable termcolor warnings #endif + #include // Needed for termcolor. Currently (2023-10-26) termcolor does not include this header file, but it is needed for Ubuntu 23.10 / gcc 13.2: https://github.com/ikalnytskyi/termcolor/pull/72 #include #ifdef _MSC_VER diff --git a/app/rec/rec_server_cli/src/commands/table_printer.cpp b/app/rec/rec_server_cli/src/commands/table_printer.cpp index 5b202af719..ddb1ddbf1d 100644 --- a/app/rec/rec_server_cli/src/commands/table_printer.cpp +++ b/app/rec/rec_server_cli/src/commands/table_printer.cpp @@ -30,6 +30,7 @@ #pragma warning(disable: 4800) // disable termcolor warnings #endif + #include // Needed for termcolor. Currently (2023-10-26) termcolor does not include this header file, but it is needed for Ubuntu 23.10 / gcc 13.2: https://github.com/ikalnytskyi/termcolor/pull/72 #include #ifdef _MSC_VER diff --git a/app/sys/sys_cli/src/commands/list.cpp b/app/sys/sys_cli/src/commands/list.cpp index 76fd29c4f5..8fc98c9c8f 100644 --- a/app/sys/sys_cli/src/commands/list.cpp +++ b/app/sys/sys_cli/src/commands/list.cpp @@ -32,6 +32,7 @@ #pragma warning(push) #pragma warning(disable: 4800) // disable termcolor warnings #endif +#include // Needed for termcolor. Currently (2023-10-26) termcolor does not include this header file. #include #ifdef _MSC_VER #pragma warning(pop) From 056366d7cb7c0118c85d143b39bf233cb99a82a1 Mon Sep 17 00:00:00 2001 From: KerstinKeller Date: Thu, 26 Oct 2023 13:00:44 +0200 Subject: [PATCH 17/17] Do not show eCAL deprecation warnings, when building ecal_core and ecal_core_c (#1226) --- ecal/core/CMakeLists.txt | 9 +++++++-- ecal/core/include/ecal/ecal_deprecate.h | 13 ++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/ecal/core/CMakeLists.txt b/ecal/core/CMakeLists.txt index 5c2b6e33d2..2bc7fd38ac 100644 --- a/ecal/core/CMakeLists.txt +++ b/ecal/core/CMakeLists.txt @@ -447,7 +447,10 @@ target_compile_definitions(${PROJECT_NAME}_c PUBLIC ASIO_STANDALONE ASIO_DISABLE_VISIBILITY - PRIVATE eCAL_EXPORTS) + PRIVATE + eCAL_EXPORTS + ECAL_NO_DEPRECATION_WARNINGS +) target_compile_definitions(${PROJECT_NAME} PUBLIC @@ -457,7 +460,9 @@ target_compile_definitions(${PROJECT_NAME} eCAL_EXPORTS $<$:ECAL_HAS_CLOCKLOCK_MUTEX> $<$:ECAL_HAS_ROBUST_MUTEX> - $<$:ECAL_USE_CLOCKLOCK_MUTEX>) + $<$:ECAL_USE_CLOCKLOCK_MUTEX> + ECAL_NO_DEPRECATION_WARNINGS +) if(ECAL_NPCAP_SUPPORT) target_compile_definitions(${PROJECT_NAME} diff --git a/ecal/core/include/ecal/ecal_deprecate.h b/ecal/core/include/ecal/ecal_deprecate.h index 0debb4f39c..c583fbbdd5 100644 --- a/ecal/core/include/ecal/ecal_deprecate.h +++ b/ecal/core/include/ecal/ecal_deprecate.h @@ -26,32 +26,35 @@ #include -#if ECAL_VERSION_INTEGER >= ECAL_VERSION_CALCULATE(5, 4, 0) +//uncomment this line if you do want to get deprecation warnings inside eCAL core +//#undef ECAL_NO_DEPRECATION_WARNINGS + +#if !defined(ECAL_NO_DEPRECATION_WARNINGS) && ECAL_VERSION_INTEGER >= ECAL_VERSION_CALCULATE(5, 4, 0) #define ECAL_DEPRECATE_SINCE_5_4(__message__) [[deprecated(__message__)]] //!< Deprecate the following function with eCAL Version 5.4.0 #else #define ECAL_DEPRECATE_SINCE_5_4(__message__) //!< Deprecate the following function with eCAL Version 5.4.0 #endif -#if ECAL_VERSION_INTEGER >= ECAL_VERSION_CALCULATE(5, 10, 0) +#if !defined(ECAL_NO_DEPRECATION_WARNINGS) && ECAL_VERSION_INTEGER >= ECAL_VERSION_CALCULATE(5, 10, 0) #define ECAL_DEPRECATE_SINCE_5_10(__message__) [[deprecated(__message__)]] //!< Deprecate the following function with eCAL Version 5.10.0 #else #define ECAL_DEPRECATE_SINCE_5_10(__message__) //!< Deprecate the following function with eCAL Version 5.10.0 #endif -#if ECAL_VERSION_INTEGER >= ECAL_VERSION_CALCULATE(5, 11, 0) +#if !defined(ECAL_NO_DEPRECATION_WARNINGS) && ECAL_VERSION_INTEGER >= ECAL_VERSION_CALCULATE(5, 11, 0) #define ECAL_DEPRECATE_SINCE_5_11(__message__) [[deprecated(__message__)]] //!< Deprecate the following function with eCAL Version 5.11.0 #else #define ECAL_DEPRECATE_SINCE_5_11(__message__) //!< Deprecate the following function with eCAL Version 5.11.0 #endif -#if ECAL_VERSION_INTEGER >= ECAL_VERSION_CALCULATE(5, 12, 0) +#if !defined(ECAL_NO_DEPRECATION_WARNINGS) && ECAL_VERSION_INTEGER >= ECAL_VERSION_CALCULATE(5, 12, 0) #define ECAL_DEPRECATE_SINCE_5_12(__message__) [[deprecated(__message__)]] //!< Deprecate the following function with eCAL Version 5.12.0 #else #define ECAL_DEPRECATE_SINCE_5_12(__message__) //!< Deprecate the following function with eCAL Version 5.12.0 #endif -#if ECAL_VERSION_INTEGER >= ECAL_VERSION_CALCULATE(5, 13, 0) +#if !defined(ECAL_NO_DEPRECATION_WARNINGS) && ECAL_VERSION_INTEGER >= ECAL_VERSION_CALCULATE(5, 13, 0) #define ECAL_DEPRECATE_SINCE_5_13(__message__) [[deprecated(__message__)]] //!< Deprecate the following function with eCAL Version 5.13.0 #else #define ECAL_DEPRECATE_SINCE_5_13(__message__) //!< Deprecate the following function with eCAL Version 5.13.0