Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iox-#743 Validate callbacks in c example #1188

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 58 additions & 35 deletions iceoryx_examples/callbacks_in_c/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ one accessing it or that it is accessed with a guard like a `mutex`.

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

Expand All @@ -34,18 +34,19 @@ received a sample from each service we calculate the sum of it.
### 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).
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
In the next step, we set up 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.

<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_subscriber.c][create listener]-->
```c
iox_listener_storage_t listenerStorage;
iox_listener_t listener = iox_listener_init(&listenerStorage);
Expand All @@ -54,13 +55,15 @@ iox_listener_t listener = iox_listener_init(&listenerStorage);
Besides the subscribers we also would like to have an event that will be triggered
by our self - the `heartbeat`.

<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_subscriber.c][create 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:
Both subscribers use the same options which we set up with:

<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_subscriber.c][set subscriber options]-->
```c
iox_sub_options_t options;
iox_sub_options_init(&options);
Expand All @@ -71,6 +74,7 @@ options.nodeName = "iox-c-callback-subscriber-node";

and then we can construct the two subscribers `subscriberLeft` and `subscriberRight`.

<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_subscriber.c][create subscribers]-->
```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);
Expand All @@ -79,6 +83,7 @@ iox_sub_t subscriberRight = iox_sub_init(&subscriberRightStorage, "Radar", "Fron
Now that everything is initialized, we start our `heartbeatTriggerThread` which
triggers our `heartbeat` every 4 seconds.

<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_subscriber.c][send a heartbeat every 4 seconds]-->
```c
pthread_t heartbeatTriggerThread;
if (pthread_create(&heartbeatTriggerThread, NULL, cyclicHeartbeatTrigger, NULL))
Expand All @@ -91,20 +96,24 @@ 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_.

<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_subscriber.c][attach everything to the listener]-->
```c
// from here on the callbacks are called when an event occurs
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, 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 that is pulling
the events and processing them, we require a blocker that waits until a signal
signals the process to terminate.
Since we are following a push-based approach, i.e. without an event loop that is pulling
the events and processing them, we require a blocking call that waits until the process is
signaled to terminate.

<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_subscriber.c][wait until someone presses CTRL+c]-->
```c
while (keepRunning)
{
Expand All @@ -118,6 +127,7 @@ all events by itself when it is deinitialized. This applies also for all the _Ev
if you for instance deinitialize an attached subscriber it will automatically detach
itself from the Listener.

<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_subscriber.c][optional detachEvent, but not required]-->
```c
iox_listener_detach_user_trigger_event(listener, heartbeat);
iox_listener_detach_subscriber_event(listener, subscriberLeft, SubscriberEvent_DATA_RECEIVED);
Expand All @@ -126,9 +136,8 @@ iox_listener_detach_subscriber_event(listener, subscriberRight, SubscriberEvent_

In a last step we have to release all acquired resources

<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_subscriber.c][cleanup]-->
```c
pthread_join(heartbeatTriggerThread, NULL);

iox_user_trigger_deinit(heartbeat);
iox_sub_deinit(subscriberLeft);
iox_sub_deinit(subscriberRight);
Expand All @@ -140,11 +149,13 @@ iox_listener_deinit(listener);
Every callback must have a signature like `void (iox_event_origin_t)`. Our
`heartbeatCallback` just prints the message `heartbeat received` onto the console.

<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_subscriber.c][heartbeat callback]-->
```c
void heartbeatCallback(iox_user_trigger_t userTrigger)
{
(void)userTrigger;
printf("heartbeat received\n");
fflush(stdout);
}
```

Expand All @@ -153,6 +164,7 @@ the chunk and then we have to find out which subscriber received the chunk. For
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`.

<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_subscriber.c][[subscriber callback][get data]]-->
```c
void onSampleReceivedCallback(iox_sub_t subscriber)
{
Expand All @@ -170,74 +182,85 @@ void onSampleReceivedCallback(iox_sub_t subscriber)
rightCache.value = *userPayload;
rightCache.isSet = true;
}
printf("received: %d\n", userPayload->counter);
fflush(stdout);
}
// ...
}
```

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.

<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_subscriber.c][[subscriber callback][process data]]-->
```c
void onSampleReceivedCallback(iox_sub_t subscriber)
{
// ...
if (leftCache.isSet && rightCache.isSet)
{
printf("Received samples from FrontLeft and FrontRight. Sum of %d + %d = %d\n",
leftCache.value.counter,
rightCache.value.counter,
leftCache.value.counter + rightCache.value.counter);
fflush(stdout);
leftCache.isSet = false;
rightCache.isSet = false;
}
}
```

### Additional context data for callbacks (ice_c_callbacks_with_context_data.c)

Sometimes you would like to modify data structures within the callback which
are not globally available. To facilitate this we implemented functions called
`iox_listener_attach_***_event_with_context_data` which are enabling you to
to provide an additional void pointer to the callback as second argument.
Sometimes we would like to modify data structures which are not globally available
within the callback. To facilitate this we provide the functions called
`iox_listener_attach_***_event_with_context_data` which allow to provide an
additional void pointer for the callback as second argument.

The following example is a simplified version of the
[ice_c_callbacks_subscriber.c](#ice_c_callbacks_subscriber.c) example where we
removed the cyclic heartbeat trigger. The key difference is that we have
a local variable called `counterService` in which we store the `leftCache`
The following example is a simplified version of the
[ice_c_callbacks_subscriber.c](#ice_c_callbacks_subscriberc) example where we
removed the cyclic heartbeat trigger. The key difference is that we have
a local variable called `counterService` in which we store the `leftCache`
and `rightCache` and we let the callback update that variable directly.

<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_with_context_data.c][local variable for caches]-->
```c
int main()
{
//...

CounterService counterService;
counterService.leftCache.isSet = false;
counterService.rightCache.isSet = false;
CounterService counterService;
counterService.leftCache.isSet = false;
counterService.rightCache.isSet = false;
```

The callback gets an additional void pointer argument which we cast then to
our CounterService to perform the same tasks as in the previous example but now
The callback takes an additional void pointer argument which we cast then to
our CounterService to perform the same tasks as in the previous example but now
on `CounterService * self`.

```c
<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_with_context_data.c][[subscriber callback][context data]]-->
```c
void onSampleReceivedCallback(iox_sub_t subscriber, void* contextData)
{
if (contextData == NULL)
{
fprintf(stderr, "aborting subscriberCallback since contextData is a null pointer\n");
fprintf(stderr, "aborting onSampleReceivedCallback since contextData is a null pointer\n");
return;
}

CounterService* self = (CounterService*)contextData;
// ...
}
```

Finally, we have to attach both subscribers and provide the pointer to `counterService`
as additional argument so that we can access it in the callback.

!!! attention
The user has to ensure that the contextData (`&counterService`) in
`iox_listener_attach_subscriber_event_with_context_data`
lives as long as the attachment, with its callback is, attached otherwise
!!! attention
The user has to ensure that the contextData (`&counterService`) in
`iox_listener_attach_subscriber_event_with_context_data`
lives as long as the attachment with its callback is attached, otherwise
the callback context data pointer is dangling.

```c
<!--[geoffrey][iceoryx_examples/callbacks_in_c/ice_c_callbacks_with_context_data.c][attach everything to the listener]-->
```c
iox_listener_attach_subscriber_event_with_context_data(
listener, subscriberLeft, SubscriberEvent_DATA_RECEIVED, &onSampleReceivedCallback, &counterService);
iox_listener_attach_subscriber_event_with_context_data(
Expand Down
37 changes: 30 additions & 7 deletions iceoryx_examples/callbacks_in_c/ice_c_callbacks_subscriber.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,14 @@ struct cache_t
struct cache_t leftCache = {.isSet = false};
struct cache_t rightCache = {.isSet = false};

//! [heartbeat callback]
void heartbeatCallback(iox_user_trigger_t userTrigger)
{
(void)userTrigger;
printf("heartbeat received\n");
fflush(stdout);
}
//! [heartbeat callback]

void* cyclicHeartbeatTrigger(void* dontCare)
{
Expand All @@ -69,8 +71,10 @@ void* cyclicHeartbeatTrigger(void* dontCare)
return NULL;
}

//! [subscriber callback]
void onSampleReceivedCallback(iox_sub_t subscriber)
{
//! [get data]
const struct CounterTopic* userPayload;
if (iox_sub_take_chunk(subscriber, (const void**)&userPayload) == ChunkReceiveResult_SUCCESS)
{
Expand All @@ -88,7 +92,9 @@ void onSampleReceivedCallback(iox_sub_t subscriber)
printf("received: %d\n", userPayload->counter);
fflush(stdout);
}
//! [get data]

//! [process data]
if (leftCache.isSet && rightCache.isSet)
{
printf("Received samples from FrontLeft and FrontRight. Sum of %d + %d = %d\n",
Expand All @@ -99,7 +105,9 @@ void onSampleReceivedCallback(iox_sub_t subscriber)
leftCache.isSet = false;
rightCache.isSet = false;
}
//! [process data]
}
//! [subscriber callback]

int main()
{
Expand All @@ -110,60 +118,75 @@ int main()

// the listener starts a background thread and the callbacks of the attached events
// will be called in this background thread when they are triggered
//! [create listener]
iox_listener_storage_t listenerStorage;
iox_listener_t listener = iox_listener_init(&listenerStorage);
//! [create listener]

//! [create heartbeat]
iox_user_trigger_storage_t heartbeatStorage;
heartbeat = iox_user_trigger_init(&heartbeatStorage);
//! [create heartbeat]

//! [set subscriber options]
iox_sub_options_t options;
iox_sub_options_init(&options);
options.historyRequest = 10U;
options.queueCapacity = 5U;
options.nodeName = "iox-c-callback-subscriber-node";
iox_sub_storage_t subscriberLeftStorage, subscriberRightStorage;
//! [set subscriber options]

iox_sub_storage_t subscriberLeftStorage, subscriberRightStorage;
//! [create subscribers]
iox_sub_t subscriberLeft = iox_sub_init(&subscriberLeftStorage, "Radar", "FrontLeft", "Counter", &options);
iox_sub_t subscriberRight = iox_sub_init(&subscriberRightStorage, "Radar", "FrontRight", "Counter", &options);
//! [create subscribers]

// send a heartbeat every 4 seconds
#if !defined(_WIN32)
//! [send a heartbeat every 4 seconds]
pthread_t heartbeatTriggerThread;
if (pthread_create(&heartbeatTriggerThread, NULL, cyclicHeartbeatTrigger, NULL))
{
printf("failed to create thread\n");
return -1;
}
//! [send a heartbeat every 4 seconds]
#endif

// attach everything to the listener, from here one the callbacks are called when an event occurs
//! [attach everything to the listener]
// from here on the callbacks are called when an event occurs
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);
//! [attach everything to the listener]

// wait until someone presses CTRL+c
//! [wait until someone presses CTRL+c]
while (keepRunning)
{
sleep_for(100);
}
//! [wait until someone presses CTRL+c]

// optional detachEvent, but not required.
// when the listener goes out of scope it will detach all events and when a
// subscriber goes out of scope it will detach itself from the listener
// when the listener goes out of scope it will detach all events and when a
// subscriber goes out of scope it will detach itself from the listener
//! [optional detachEvent, but not required]
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);
//! [optional detachEvent, but not required]

#if !defined(_WIN32)
pthread_join(heartbeatTriggerThread, NULL);
#endif

//! [cleanup]
iox_user_trigger_deinit(heartbeat);
iox_sub_deinit(subscriberLeft);
iox_sub_deinit(subscriberRight);
iox_listener_deinit(listener);
//! [cleanup]

return 0;
}
Loading