Skip to content

Commit

Permalink
iox-eclipse-iceoryx#482 spell checking and markdown linting in exampl…
Browse files Browse the repository at this point in the history
…e docs

Signed-off-by: Dietrich Krönke <dietrich.kroenke@apex.ai>
  • Loading branch information
dkroenke authored and marthtz committed May 12, 2021
1 parent 1f72519 commit 0e88c97
Show file tree
Hide file tree
Showing 12 changed files with 385 additions and 254 deletions.
88 changes: 51 additions & 37 deletions iceoryx_examples/callbacks/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Listener (or how to use callbacks with iceoryx)

## Thread Safety

The Listener is thread-safe and can be used without restrictions.
But be aware that all provided callbacks are executed concurrently
But be aware that all provided callbacks are executed concurrently
in the background thread of the Listener. If you access structures
inside this callback you have to either ensure that you are the only
one accessing it or that it is accessed with a guard like a `std::mutex`.
Expand All @@ -12,57 +13,62 @@ one accessing it or that it is accessed with a guard like a `std::mutex`.
For an introduction into the terminology please read the Glossary in the
[WaitSet C++ example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/waitset).

The Listener is a completely thread-safe construct which reacts to events by
executing registered callbacks in a background thread. Events can be emitted by
_EventOrigins_ like a subscriber or a user trigger. Some of the _EventOrigins_
The Listener is a completely thread-safe construct that reacts to events by
executing registered callbacks in a background thread. Events can be emitted by
_EventOrigins_ like a subscriber or a user trigger. Some of the _EventOrigins_
like the subscriber can hereby emit more than one event type.

The interface of a listener consists of two methods: `attachEvent` to attach a
new event with a callback and `detachEvent`. These two methods can be called
concurrently, even from inside a callback which was triggered by an event!
The interface of a listener consists of two methods: `attachEvent` to attach a
new event with a callback and `detachEvent`. These two methods can be called
concurrently, even from inside a callback that was triggered by an event!

## Expected Output

<!-- @todo Add expected output with asciinema recording before v1.0-->

## Code Walkthrough

!!! attention
Please be aware about the thread-safety restrictions of the _Listener_ and
!!! attention
Please be aware of the thread-safety restrictions of the _Listener_ and
read the [Thread Safety](#thread-safety) chapter carefully.

Let's say we have an application which offers us two distinct services:
`Radar.FrontLeft.Counter` and `Rader.FrontRight.Counter`. Every time we have
received a sample from left and right we would like to calculate the sum with
the newest values and print it out. If we have received only one of the samples
Let's say we have an application that offers us two distinct services:
`Radar.FrontLeft.Counter` and `Rader.FrontRight.Counter`. Every time we have
received a sample from left and right we would like to calculate the sum with
the newest values and print it out. If we have received only one of the samples,
we store it until we received the other side.

### ice_callbacks_publisher.cpp

The publisher of this example does not contain any new features but if you have
some questions take a look at the
The publisher of this example does not contain any new features but if you have
some questions take a look at the
[icedelivery example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/icedelivery).

### ice_callbacks_subscriber.cpp

#### int main()
The subscriber main function starts as usual and after registering the runtime
we create the listener which starts a background thread.

The subscriber main function starts as usual and after registering the runtime
we create the listener that starts a background thread.

```cpp
iox::popo::Listener listener;
```

Because it is fun we also create a heartbeat trigger which will be triggered
Because it is fun, we also create a heartbeat trigger that will be triggered
every 4 seconds so that `heartbeat received` can be printed to the console.
Furthermore, we have to create two subscriber to receive samples for the two
Furthermore, we have to create two subscribers to receive samples for the two
services.

```cpp
iox::popo::UserTrigger heartbeat;
iox::popo::Subscriber<CounterTopic> subscriberLeft({"Radar", "FrontLeft", "Counter"});
iox::popo::Subscriber<CounterTopic> subscriberRight({"Radar", "FrontRight", "Counter"});
```
Next thing is a `heartbeatThread` which will trigger our heartbeat trigger every
Next thing is a `heartbeatThread` which will trigger our heartbeat trigger every
4 seconds.
```cpp
std::thread heartbeatThread([&] {
while (keepRunning)
Expand All @@ -74,10 +80,11 @@ std::thread heartbeatThread([&] {
```

Now that everything is setup we can attach the subscribers to the listener so that
everytime a new sample (`iox::popo::SubscriberEvent::DATA_RECEIVED`) is received our callback
(`onSampleReceivedCallback`) will be called. We also attach
our `heartbeat` user trigger to print the hearbeat message to the console via another
every time a new sample (`iox::popo::SubscriberEvent::DATA_RECEIVED`) is received our callback
(`onSampleReceivedCallback`) will be called. We also attach
our `heartbeat` user trigger to print the heartbeat message to the console via another
callback (`heartbeatCallback`).

```cpp
listener.attachEvent(heartbeat, heartbeatCallback).or_else([](auto) {
std::cerr << "unable to attach heartbeat event" << std::endl;
Expand All @@ -96,24 +103,27 @@ listener.attachEvent(subscriberRight, iox::popo::SubscriberEvent::DATA_RECEIVED,
std::terminate();
});
```
Since a user trigger has only one event we do not have to specify an event when we attach
Since a user trigger has only one event, we do not have to specify an event when we attach
it to the listener. `attachEvent` returns a `cxx::expected` to inform us if the attachment
succeeded. When this is not the case the error handling is performed in the `.or_else([](auto){` part
succeeded. When this is not the case the error handling is performed in the `.or_else([](auto){` part
after each `attachEvent` call.
In this example we choose to attach the same callback twice to make things easier
In this example, we choose to attach the same callback twice to make things easier
but you are free to attach any callback with the signature `void(iox::popo::Subscriber<CounterTopic> *)`.
The setup is complete but it would terminate right away since we have no blocker which
waits until SIGINT or SIGTERM was send. In the other examples we hadn't have that problem
since we pulled all the events in a while true loop but working only with callbacks
requires something like our `shutdownSemaphore`, a semaphore on which we wait until
The setup is complete, but it would terminate right away since we have no blocker which
waits until SIGINT or SIGTERM was send. In the other examples, we had not that problem
since we pulled all the events in a while true loop but working only with callbacks
requires something like our `shutdownSemaphore`, a semaphore on which we wait until
the signal callback increments it.
```cpp
shutdownSemaphore.wait();
```

When the `shutdownSemaphore` unblocks we clean up all resources and terminate the process
When the `shutdownSemaphore` unblocks we clean up all resources and terminate the process
gracefully.

```cpp
listener.detachEvent(heartbeat);
listener.detachEvent(subscriberLeft, iox::popo::SubscriberEvent::DATA_RECEIVED);
Expand All @@ -122,24 +132,27 @@ listener.detachEvent(subscriberRight, iox::popo::SubscriberEvent::DATA_RECEIVED)
heartbeatThread.join();
```

Hint: You do not have to detach an _EventOrigin_ like a subscriber or user trigger
Hint: You do not have to detach an _EventOrigin_ like a subscriber or user trigger
before it goes out of scope. This also goes for the _Listener_, the implemented
RAII based design takes care of the resource cleanup.
RAII-based design takes care of the resource cleanup.

#### The Callbacks

The callbacks must have a signature like `void(PointerToEventOrigin*)`.
Our `heartbeatCallback` for instance just prints the message `heartbeat received`.
Our `heartbeatCallback` for instance, just prints the message `heartbeat received`.

```cpp
void heartbeatCallback(iox::popo::UserTrigger*)
{
std::cout << "heartbeat received " << std::endl;
}
```
The `onSampleReceivedCallback` is more complex. We first acquire the received
The `onSampleReceivedCallback` is more complex. We first acquire the received
sample and check which subscriber signaled the event by acquiring the subscriber's
service description. If the instance is equal to `FrontLeft` we store the sample
in the `leftCache` otherwise in the `rightCache`.
```cpp
void onSampleReceivedCallback(iox::popo::Subscriber<CounterTopic>* subscriber)
{
Expand All @@ -157,9 +170,10 @@ void onSampleReceivedCallback(iox::popo::Subscriber<CounterTopic>* subscriber)
}
```

In a next step we check if both caches are filled. If this is the case we print
In the next step, we check if both caches are filled. If this is the case, we print
an extra message which states the result of the sum of both received values.
Afterwards we reset both caches to start fresh again.
Afterward, we reset both caches to start fresh again.

```cpp
if (leftCache && rightCache)
{
Expand Down
73 changes: 45 additions & 28 deletions iceoryx_examples/callbacks_in_c/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Listener in C (or how to use callbacks with iceoryx)

## Thread Safety

The Listener is thread-safe and can be used without restrictions.
But be aware that all provided callbacks are executed concurrently
in the background thread of the Listener. If you access structures
Expand All @@ -10,9 +11,9 @@ one accessing it or that it is accessed with a guard like a `mutex`.
## Introduction

For a general introduction into the Listener concept please take a look at
the first part of the
the first part of the
[Listener C++ example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/callbacks)
and at the Glossary of the
and at the Glossary of the
[WaitSet C++ example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/waitset).

## Expected Output
Expand All @@ -21,7 +22,7 @@ and at the Glossary of the

## Code Walkthrough

!!! attention
!!! attention
Please be aware about the thread-safety restrictions of the _Listener_ and
read the [Thread Safety](#thread-safety) chapter carefully.

Expand All @@ -30,48 +31,55 @@ C++ version. We have again an application which offers two services called
`Radar.FrontLeft.Counter` and `Radar.FrontRight.Counter`. Every time we have
received a sample from each service we calculate the sum of it.

### ice_c_callbacks_publisher.c
### ice_c_callbacks_publisher.c

The publisher contains only already known iceoryx features. If some of them
are not known to you please take a look at the
[icedelivery in c example](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/icedelivery_in_c).

### ice_c_callbacks_subscriber.c

#### int main()

The subscriber starts as usual by registering the process at the runtime.
In the next step we setup some `listenerStorage` on the stack where the listener
will be constructed in and initialize the listener which will start a background
thread for the upcoming event triggered callbacks.
In the next step, we setup some `listenerStorage` on the stack where the listener
will be constructed and initialize the listener that will start a background
thread for the upcoming event-triggered callbacks.

```c
iox_listener_storage_t listenerStorage;
iox_listener_t listener = iox_listener_init(&listenerStorage);
```

Besides the subscribers we also would like to have an event which will be triggered
by ourselfs - the `heartbeat`.
Besides the subscribers we also would like to have an event that will be triggered
by our self - the `heartbeat`.

```c
iox_user_trigger_storage_t heartbeatStorage;
heartbeat = iox_user_trigger_init(&heartbeatStorage);
```

Both subscribers are having the same options which we setup with:

```c
iox_sub_options_t options;
iox_sub_options_init(&options);
options.historyRequest = 10U;
options.queueCapacity = 5U;
options.nodeName = "iox-c-callback-subscriber-node";
```
```
and then we can construct the two subscribers `subscriberLeft` and `subscriberRight`.
```c
```c
iox_sub_t subscriberLeft = iox_sub_init(&subscriberLeftStorage, "Radar", "FrontLeft", "Counter", &options);
iox_sub_t subscriberRight = iox_sub_init(&subscriberRightStorage, "Radar", "FrontRight", "Counter", &options);
```

Now that everything is initialized we start our `heartbeatTriggerThread` which
Now that everything is initialized, we start our `heartbeatTriggerThread` which
triggers our `heartbeat` every 4 seconds.
```c

```c
pthread_t heartbeatTriggerThread;
if (pthread_create(&heartbeatTriggerThread, NULL, cyclicHeartbeatTrigger, NULL))
{
Expand All @@ -82,38 +90,43 @@ if (pthread_create(&heartbeatTriggerThread, NULL, cyclicHeartbeatTrigger, NULL))

Attaching the subscribers and the heartbeat allows the Listener to call the callbacks
whenever the event is signaled by the _EventOrigin_.
```c

```c
iox_listener_attach_user_trigger_event(listener, heartbeat, &heartbeatCallback);
iox_listener_attach_subscriber_event(listener, subscriberLeft, SubscriberEvent_DATA_RECEIVED, &onSampleReceivedCallback);
iox_listener_attach_subscriber_event(
listener, subscriberRight, SubscriberEvent_DATA_RECEIVED, &onSampleReceivedCallback);
```
A user trigger can emit only one event therefore we do not provide the event type as
an argument in the user trigger attach call.
Since we are following a push based approach - without an event loop which is pulling
the events and processing them, we require a blocker which waits until a signal
Since we are following a push-based approach - without an event loop that is pulling
the events and processing them, we require a blocker that waits until a signal
signals the process to terminate.
```c
```c
while (keepRunning)
{
sleep_for(100);
}
```

When `keepRunning` is set to false we cleanup all the resources. First we detach
When `keepRunning` is set to false, we clean up all the resources. First, we detach
the events from the Listener. This is an optional step since the Listener detaches
all events by itself when it is deinitialized. This goes also for all the _EventOrigins_,
all events by itself when it is deinitialized. This applies also for all the _EventOrigins_,
if you for instance deinitialize an attached subscriber it will automatically detach
itself from the Listener.
```c

```c
iox_listener_detach_user_trigger_event(listener, heartbeat);
iox_listener_detach_subscriber_event(listener, subscriberLeft, SubscriberEvent_DATA_RECEIVED);
iox_listener_detach_subscriber_event(listener, subscriberRight, SubscriberEvent_DATA_RECEIVED);
```
In a last step we have to release all acquired resources
```c
In a last step we have to release all acquired resources
```c
pthread_join(heartbeatTriggerThread, NULL);
iox_user_trigger_deinit(heartbeat);
Expand All @@ -123,9 +136,11 @@ iox_listener_deinit(listener);
```

#### The callbacks

Every callback must have a signature like `void (iox_event_origin_t)`. Our
`heartbeatCallback` just prints the message `heartbeat received` onto the console.
```c

```c
void heartbeatCallback(iox_user_trigger_t userTrigger)
{
(void)userTrigger;
Expand All @@ -137,7 +152,8 @@ The `onSampleReceivedCallback` is a little bit more complex. First we acquire
the chunk and then we have to find out which subscriber received the chunk. For that
we acquire the service description of the subscriber and if its instance equals
`FrontLeft` we store the chunk value in the `leftCache` otherwise in the `rightCache`.
```c
```c
void onSampleReceivedCallback(iox_sub_t subscriber)
{
const struct CounterTopic* userPayload;
Expand All @@ -156,10 +172,11 @@ void onSampleReceivedCallback(iox_sub_t subscriber)
}
```

If both caches are set we can calculate the sum of both chunks and print them to
the console. To start fresh in the next cycle we reset the `leftCache` and
the `rightCache` afterwards.
```c
If both caches are set, we can calculate the sum of both chunks and print them to
the console. To start fresh in the next cycle, we reset the `leftCache` and
the `rightCache` afterward.

```c
if (leftCache.isSet && rightCache.isSet)
{
printf("Received samples from FrontLeft and FrontRight. Sum of %d + %d = %d\n",
Expand Down
5 changes: 3 additions & 2 deletions iceoryx_examples/icecrystal/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
> This Readme.md is slightly outdated and not all functionality of the introspection is available with v1.0
This example teaches you how to make use of the introspection for debugging purposes. With the introspection you can
look into the machine room of RouDi. The introspection shows live information about the memory usage and all
look into the machine room of RouDi. It shows live information about the memory usage and all
registered processes. Additionally, it shows the publisher and subscriber ports that are created inside the shared
memory.

## Run icecrystal

We reuse the binaries from
[icedelivery](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/icedelivery).
[icedelivery](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/icedelivery).
Create four terminals and run one command in each of them.

```sh
# If installed and available in PATH environment variable
iox-roudi
Expand Down
Loading

0 comments on commit 0e88c97

Please sign in to comment.