Skip to content

Commit

Permalink
README: Add (almost complete) section on custom notifications.
Browse files Browse the repository at this point in the history
  • Loading branch information
attipaci committed Sep 22, 2024
1 parent 40e5667 commit 846e41a
Showing 1 changed file with 120 additions and 0 deletions.
120 changes: 120 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -693,10 +693,130 @@ all previously sumbitted requests have been collected. You can do that with:
<a name="notifications"></a>
## Custom notifications and update handling

- [Monitoring updates](#monitoring-updates)
- [Waiting for updates](#waiting-for-updates)
- [Update callbacks](#update-callbacks)


<a name="monitoring-updates"></a>
### Monitoring updates

The LUA scripts that define SMA-X interface on the Redis server send out PUB/SUB notifications for every variable on
their own dedicate PUB/SUB channel whenever the variable is updated. Byt default, lazy access methods subscribe to
these messages and use them to determine when to invalidate the chache and fetch new values from the database again.
However, you may subscribe and use these messages outside of the lazy update routines also. The only thing you need to
pay attention to is not to unsubscribe from update notifications for those variables that have multiple active monitors
(including lazy updates).

One common use case is that you want to execute some code in your application when some value changes in SMA-X. You
can do that easily, and have two choices on how you want to trigger the code execution: (1) you can block execution of
your current thread until an update notification is received for one of the variables (or patterns) of interest to
which you have subscribed, or (2) you can let __smax_clib__ call a designated function of your application when such
an update notification is captured. We'll cover these two cases separately below.

However, in either case you will have to subscribe to the variable(s) or pattern(s) of interest with `smaxSubscribe()`
before updates can be processed, e.g.

```c
int status = smaxSubscribe("some_table", "watch_point_variable");
if (status < 0) {
// Ooops something did not go to plan
...
}
```
and/or pattern(s)
```c
int status = smaxSubscribe("watch_*", "watch_[a-z]_*");
if (status < 0) {
// Ooops something did not go to plan
...
}
```

You can subscribe to any number of variables or patterns in this way. __smax_clib__ will receive and process
notifications for all of them. (So beware of creating unneccessary network traffic.)

<a name="waiting-for-updates"></a>
### Waiting for updates

The first option for executing code conditional on some variable update is to block execution in the current thread
and wait until the variable(s) of interest change(s) (or until some timeout limit is reached). There is a group of
functions `smaxWaitOn...()` that do exactly that. For example:

```c
// Wait for watch_foo:watch_h_bar to update, or wait at most 500 ms
int status = smaxWaitOnSubscribedVar("watch_foo", "watch_h_bar", 500);
if (status == X_TIMEDOUT) {
// Wait timed out, maybe we want to try again, or do something else...
...
}
else if (status < 0) {
// Oh no, some other error happened.
...
}
...

```
Similar methods allow you to wait for updates on any subscribed variable in selected tables, or the update of
select variables in all subscribed tables, or any of the subscribed variables to change for the wait to end
normally (with return value 0).
<a name="update-callbacks"></a>
### Update callbacks
Sometimes, you don't want to block execution, but you want to make sure some code executes when the a variable
or variables of interest get updated. For such cases you can designate your own `RedisSubscriberCall` callback
function, e.g.:
```c
void my_update_processor(const char *pattern, const char *channel, const char *msg, long len) {
const char *varID; // The variable aggregate ID.
// check that it's a SMA-X update -- channel should begin with SMAX_UPDATE
if (strncmp(channel, SMAX_UPDATE, SMAX_UPDATE_LENGTH) != 0) {
// Not an SMA-X variable update. It's not what we expected.
return;
}
id = &channel[SMAX_UPDATE_LENGTH];
// Now we can check the ID to see which of the variables updated, and act accordingly.
...
}
```

Once you have defined your callback function, you can activate it with `smaxAddSubscriber()`, e.g.:

```c
// Call my_update_processor on updates for any subscribed variable whose table name starts with "watch_table_"
int status = smaxAddSubscriber("watch_table_", my_update_processor);
if (status < 0) {
// Did not go to plan.
...
}
```
When you no longer need to process such updates, you can simply remove the function from being called via
`smaxRemoveSubscriber()`, and if you are absolutely sure that no other part of your code needs the subscription(s)
that could trigger it, you can also unsubscribe from the trigger variables/pattern to eliminate unnecessary network
traffic.
One word of caution on callbacks is that they are expected to:
- execute quickly
- never block for prolonged periods. (It may be OK to wait briefly on a mutex, provided nothing else can hog that
mutex for prolonged periods).
If the above two conditions cannot be guaranteed, it's best practice for your callback to place a copy of the
callback information on a queue, and then spawn or notify a separate thread to process the infomation in the
background, including discarding the copied data if it's no longer needed. Alternatively, you can launch a decicated
processor thread early on, and iside it wait for the updates before executing some complex action. The choice is
yours.
### Status / error messages
Expand Down

0 comments on commit 846e41a

Please sign in to comment.