profile
viewpoint

Ask questionsCannot use implicit session after returning SR_ERR_CALLBACK_SHELVE

Hi Michal,

we finally have time to start integrating the new sysrepo API. Doing so, I encountered an issue with using SR_SUBSCR_NO_THREAD along with our own event loop.

In a nutshell, when using an event loop, potentially blocking operations are deferred to allow other tasks to run. However, the "implicit session" provided as argument in the module change callback cannot be used outside of the callback scope which prevents from scheduling the processing of changes to later.

Here is a patch on examples/application_changes_example.c to illustrate the problem. I used libevent to schedule sr_process_events when the subscription event pipe becomes readable. In the module change callback, I simulate an operation that takes 1 second with a timer event which schedules another callback in the event loop with a copy of the arguments (including the "implicit session").

diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index cde805f55ba4..937858055cd1 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -9,7 +9,7 @@ set(examples application_changes_example sr_set_item_example sr_get_items_exampl
 
 foreach(app_name IN LISTS examples)
     add_executable(${app_name} ${app_name}.c)
-    target_link_libraries(${app_name} sysrepo)
+    target_link_libraries(${app_name} sysrepo event)
 endforeach(app_name)
 
 # oven plugin
diff --git a/examples/application_changes_example.c b/examples/application_changes_example.c
index 5c9b6d8bf85a..f5a45a78def8 100644
--- a/examples/application_changes_example.c
+++ b/examples/application_changes_example.c
@@ -20,11 +20,10 @@
 #include <string.h>
 #include <signal.h>
 #include <inttypes.h>
+#include <event.h>
 
 #include "sysrepo.h"
 
-volatile int exit_application = 0;
-
 static void
 print_val(const sr_val_t *value)
 {
@@ -172,28 +171,43 @@ ev_to_str(sr_event_t ev)
     }
 }
 
-static int
-module_change_cb(sr_session_ctx_t *session, const char *module_name, const char *xpath, sr_event_t event,
-        uint32_t request_id, void *private_data)
+struct cb_args {
+    sr_session_ctx_t *session;
+    const char *module_name;
+    const char *xpath;
+    sr_event_t event;
+    uint32_t request_id;
+};
+
+struct subscr_info {
+    struct event_base *evt_base;
+    sr_subscription_ctx_t *subscription;
+    struct cb_args *callback_args;
+    int callback_done;
+    int callback_res;
+};
+
+static void
+async_module_change_cb(evutil_socket_t fd, short what, void *arg)
 {
+    struct subscr_info *info = arg;
+    struct cb_args *args = info->callback_args;
     sr_change_iter_t *it = NULL;
     int rc = SR_ERR_OK;
     sr_change_oper_t oper;
     sr_val_t *old_value = NULL;
     sr_val_t *new_value = NULL;
+    (void)fd;
+    (void)what;
 
-    (void)xpath;
-    (void)request_id;
-    (void)private_data;
+    printf("\n\n ========== EVENT %s CHANGES: ====================================\n\n", ev_to_str(args->event));
 
-    printf("\n\n ========== EVENT %s CHANGES: ====================================\n\n", ev_to_str(event));
-
-    rc = sr_get_changes_iter(session, "//." , &it);
+    rc = sr_get_changes_iter(args->session, "//." , &it);
     if (rc != SR_ERR_OK) {
         goto cleanup;
     }
 
-    while ((rc = sr_get_change_next(session, it, &oper, &old_value, &new_value)) == SR_ERR_OK) {
+    while ((rc = sr_get_change_next(args->session, it, &oper, &old_value, &new_value)) == SR_ERR_OK) {
         print_change(oper, old_value, new_value);
         sr_free_val(old_value);
         sr_free_val(new_value);
@@ -201,22 +215,65 @@ module_change_cb(sr_session_ctx_t *session, const char *module_name, const char
 
     printf("\n ========== END OF CHANGES =======================================");
 
-    if (event == SR_EV_DONE) {
+    if (args->event == SR_EV_DONE) {
         printf("\n\n ========== CONFIG HAS CHANGED, CURRENT RUNNING CONFIG: ==========\n\n");
-        print_current_config(session, module_name);
+        print_current_config(args->session, args->module_name);
     }
 
 cleanup:
     sr_free_change_iter(it);
-    return SR_ERR_OK;
+    info->callback_done = 1;
+    info->callback_res = rc;
+    sr_process_events(info->subscription, NULL, NULL);
+}
+
+static int
+module_change_cb(sr_session_ctx_t *session, const char *module_name, const char *xpath, sr_event_t event,
+        uint32_t request_id, void *private_data)
+{
+    struct subscr_info *info = private_data;
+    struct timeval one_sec = {1, 0};
+
+    if (info->callback_done) {
+        info->callback_done = 0;
+        free(info->callback_args);
+        info->callback_args = NULL;
+        return info->callback_res;
+    }
+
+    info->callback_args = malloc(sizeof(struct cb_args));
+    info->callback_args->session = session;
+    info->callback_args->module_name = module_name;
+    info->callback_args->xpath = xpath;
+    info->callback_args->event = event;
+    info->callback_args->request_id = request_id;
+
+    evtimer_add(evtimer_new(
+        info->evt_base, async_module_change_cb, info), &one_sec);
+
+    return SR_ERR_CALLBACK_SHELVE;
 }
 
 static void
-sigint_handler(int signum)
+signal_cb(evutil_socket_t fd, short what, void *arg)
 {
-    (void)signum;
+    struct subscr_info *info = arg;
+    (void)fd;
+    (void)what;
+    event_base_loopbreak(info->evt_base);
+}
 
-    exit_application = 1;
+static void
+subscription_ready_cb(evutil_socket_t fd, short what, void *arg)
+{
+    struct subscr_info *info = arg;
+    int rc;
+    (void)fd;
+    (void)what;
+    rc = sr_process_events(info->subscription, NULL, NULL);
+    if (rc != SR_ERR_OK) {
+        printf("sr_process_events error: %s\n", sr_strerror(rc));
+    }
 }
 
 int
@@ -226,7 +283,9 @@ main(int argc, char **argv)
     sr_session_ctx_t *session = NULL;
     sr_subscription_ctx_t *subscription = NULL;
     int rc = SR_ERR_OK;
+    int subscription_fd = -1;
     const char *mod_name, *xpath = NULL;
+    struct subscr_info info;
 
     if ((argc < 2) || (argc > 3)) {
         printf("%s <module-to-subscribe> [<xpath-to-subscribe>]\n", argv[0]);
@@ -259,19 +318,31 @@ main(int argc, char **argv)
     print_current_config(session, mod_name);
 
     /* subscribe for changes in running config */
-    rc = sr_module_change_subscribe(session, mod_name, xpath, module_change_cb, NULL, 0, 0, &subscription);
+    rc = sr_module_change_subscribe(
+        session, mod_name, xpath, module_change_cb, &info, 0,
+        SR_SUBSCR_NO_THREAD, &subscription);
     if (rc != SR_ERR_OK) {
         goto cleanup;
     }
 
+    rc = sr_get_event_pipe(subscription, &subscription_fd);
+    if (rc != SR_ERR_OK) {
+        goto cleanup;
+    }
+
+    memset(&info, 0, sizeof(info));
+    info.evt_base = event_base_new();
+    info.subscription = subscription;
+
+    event_add(event_new(
+        info.evt_base, SIGINT, EV_SIGNAL | EV_PERSIST, signal_cb, &info), NULL);
+    event_add(event_new(
+        info.evt_base, subscription_fd, EV_READ | EV_PERSIST, subscription_ready_cb, &info), NULL);
+
     printf("\n\n ========== LISTENING FOR CHANGES ==========\n\n");
 
     /* loop until ctrl-c is pressed / SIGINT is received */
-    signal(SIGINT, sigint_handler);
-    signal(SIGPIPE, SIG_IGN);
-    while (!exit_application) {
-        sleep(1000);
-    }
+    event_base_dispatch(info.evt_base);
 
     printf("Application exit requested, exiting.\n");
 

When running this modified example, we can see that the session cannot be used:

root@6d94624f6003:/tmp/sysrepo-build# ./examples/application_changes_example example /example:conf &
[1] 1199
root@6d94624f6003:/tmp/sysrepo-build# Application will watch for changes in "/example:conf".

 ========== READING RUNNING CONFIG: ==========

/example:conf (container)
/example:conf/system (container)
/example:conf/routing (container)


 ========== LISTENING FOR CHANGES ==========


root@6d94624f6003:/tmp/sysrepo-build# sysrepocfg --import=/home/dev/mgmt/aiosysrepo/examples/example.json -f json -m example


 ========== EVENT change CHANGES: ====================================

[ERR]: Session without changes.
[ERR]: Callback event "change" with ID 1 processing timed out.
sysrepocfg error: Replace config failed (User callback failed)
[1]+  Segmentation fault      (core dumped) ./examples/application_changes_example example /example:conf
root@6d94624f6003:/tmp/sysrepo-build# gdb ./examples/application_changes_example
GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./examples/application_changes_example...done.
(gdb) core-file !tmp!sysrepo-build!examples!application_changes_example.core 
warning: core file may not match specified executable file.
[New LWP 1199]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `./examples/application_changes_example example /example:conf'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00007f7c085102fa in sr_errinfo_free (err_info=0x7ffeec8e4110) at /home/dev/mgmt/sysrepo/src/log.c:281
281                     free((*err_info)->err[i].message);
(gdb) bt
#0  0x00007f7c085102fa in sr_errinfo_free (err_info=0x7ffeec8e4110) at /home/dev/mgmt/sysrepo/src/log.c:281
#1  0x00007f7c0850fb15 in sr_api_ret (session=0x7ffeec8e40f0, err_info=0x5563f984c8b0)
    at /home/dev/mgmt/sysrepo/src/log.c:83
#2  0x00007f7c085010ab in sr_get_changes_iter (session=0x7ffeec8e40f0, xpath=0x5563f93fed47 "//.", 
    iter=0x7ffeec8e4290) at /home/dev/mgmt/sysrepo/src/sysrepo.c:3417
#3  0x00005563f93fe629 in async_module_change_cb (fd=-1, what=1, arg=0x7ffeec8e4430)
    at /home/dev/mgmt/sysrepo/examples/application_changes_example.c:205
#4  0x00007f7c082c0a11 in ?? () from /usr/lib/x86_64-linux-gnu/libevent-2.1.so.6
#5  0x00007f7c082c133f in event_base_loop () from /usr/lib/x86_64-linux-gnu/libevent-2.1.so.6
#6  0x00005563f93feb1b in main (argc=3, argv=0x7ffeec8e4548)
    at /home/dev/mgmt/sysrepo/examples/application_changes_example.c:345

For now, I have worked around this problem by gathering the configuration and changes before scheduling the async callback. However, this is not very practical and ideally, the session should remain usable if the callback returns SR_ERR_CALLBACK_SHELVE.

I'm not sure if that would be possible. And if so, how it would be implemented to avoid stray sessions to remain in the wild if the callback never returns an actual value.

Thanks in advance for your help.

sysrepo/sysrepo

Answer questions rjarry

Hi Michal,

I understand that it is a complex problem which may require an extensive design modification and/or API change. I think we can live with getting the config/changes inside the callback.

Thanks again for taking the time.

useful!

Related questions

why "There is a not enabled node in ietf-system module, it can not be committed to the running" occured when I try to call "sr_set_item" in running datastore? hot 1
sysrepocfg error using config false in a YANG file hot 1
New sysrepo: leafref can't be used as a list key hot 1
possibility send notification in another thread during subtree_change callback hot 1
Can't connect to netopeer2-server via a unix socket when sysrepo is run as an unprivilieged process hot 1
Filling up startup datastore hot 1
[New-Sysrepo] sr_main_lock not released when install new netopeer2-server hot 1
Can't connect to netopeer2-server via a unix socket when sysrepo is run as an unprivilieged process hot 1
Sysrepo Api to lock DB hot 1
Python global_loop() replacement hot 1
Github User Rank List