From 7f4037a0d60bbd3645c6fe710257923f28225ad2 Mon Sep 17 00:00:00 2001 From: Margret Jaison Date: Wed, 27 Mar 2024 12:52:59 +0530 Subject: [PATCH 1/3] ARRISAPP-69 wait for application state responses (#16) It is now possible to specify the amount of time xdial server waits for rtremote status update resposes with XDIAL_WAIT_FOR_RTREMOTE_STATE_RESPONSE_MS. This can be used to make sure that up-to-date application states are returned (instead of cached ones) --- server/gdial-rest.c | 21 ++++++++++++++++++++- server/plat/rtcache.cpp | 21 +++++++++++++++++++++ server/plat/rtcache.hpp | 17 +++++++++++++++++ server/plat/rtdial.cpp | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 1 deletion(-) diff --git a/server/gdial-rest.c b/server/gdial-rest.c index 1168a33f..d5960b8f 100644 --- a/server/gdial-rest.c +++ b/server/gdial-rest.c @@ -383,6 +383,17 @@ static void gdial_rest_server_handle_DELETE(SoupMessage *msg, GHashTable *query, g_object_unref(app); } +static void refresh_app_state(GDialApp *app) { + static int xdial_is_waiting_rtremote_state_response = -1; + if (xdial_is_waiting_rtremote_state_response == -1) { + xdial_is_waiting_rtremote_state_response = getenv("XDIAL_WAIT_FOR_RTREMOTE_STATE_RESPONSE_MS") && atoi(getenv("XDIAL_WAIT_FOR_RTREMOTE_STATE_RESPONSE_MS")) > 0; + } + // trying to refresh the app state only makes sense if xdial is configured to wait for rt state responses + if (app && xdial_is_waiting_rtremote_state_response) { + gdial_app_state(app); + } +} + static void gdial_rest_server_handle_POST(GDialRestServer *gdial_rest_server, SoupMessage* msg, GHashTable *query, const gchar *app_name) { GDialAppRegistry *app_registry = gdial_rest_server_find_app_registry(gdial_rest_server, app_name); gdial_rest_server_http_return_if_fail(app_registry, msg, SOUP_STATUS_NOT_FOUND); @@ -395,6 +406,7 @@ static void gdial_rest_server_handle_POST(GDialRestServer *gdial_rest_server, So g_printerr("Starting the app with payload %.*s\n", (int)msg->request_body->length, msg->request_body->data); GDialApp *app = gdial_app_find_instance_by_name(app_registry->name); + refresh_app_state(app); gboolean new_app_instance = FALSE; gboolean first_instance_created = FALSE; GDialAppState current_state = GDIAL_APP_STATE_STOPPED; @@ -411,8 +423,11 @@ static void gdial_rest_server_handle_POST(GDialRestServer *gdial_rest_server, So } else { app = gdial_app_new(app_registry->name); + refresh_app_state(app); new_app_instance = TRUE; - first_instance_created = TRUE; + // we did not have GDialApp instance created yet, but the application might've already been running + // so check the state before assuming that we've actually created the app + first_instance_created = (app->state == GDIAL_APP_STATE_STOPPED); } GDialAppError start_error = GDIAL_APP_ERROR_NONE; @@ -590,10 +605,12 @@ static void gdial_rest_server_handle_POST_dial_data(GDialRestServer *gdial_rest_ * Cache dial_data so as to use on future queries. */ GDialApp *app = gdial_app_find_instance_by_name(app_name); + refresh_app_state(app); if(app == NULL) { g_print("gdial_rest_server_handle_POST_dial_data creating app instance \n"); app = gdial_app_new(app_name); + refresh_app_state(app); } gdial_rest_server_http_return_if_fail(app, msg, SOUP_STATUS_NOT_FOUND); /* @@ -869,6 +886,7 @@ static void gdial_rest_http_server_apps_callback(SoupServer *server, } else if (msg->method == SOUP_METHOD_DELETE) { GDialApp *app = gdial_app_find_instance_by_name(app_name); + refresh_app_state(app); GDialApp *app_by_instance = gdial_rest_server_check_instance(app, instance); if (app_by_instance) { gdial_rest_server_handle_DELETE(msg, query, app); @@ -896,6 +914,7 @@ static void gdial_rest_http_server_apps_callback(SoupServer *server, else if (msg->method == SOUP_METHOD_POST) { GDialApp *app = gdial_app_find_instance_by_name(app_name); + refresh_app_state(app); GDialApp *app_by_instance = gdial_rest_server_check_instance(app, instance); if (app_by_instance) { gdial_rest_server_handle_POST_hide(msg, app); diff --git a/server/plat/rtcache.cpp b/server/plat/rtcache.cpp index 0f82f938..59ce5deb 100644 --- a/server/plat/rtcache.cpp +++ b/server/plat/rtcache.cpp @@ -76,9 +76,30 @@ rtError rtAppStatusCache::UpdateAppStatusCache(rtValue app_status) } err = ObjectCache->insert(id,temp); + notifyStateChanged(App_name); return err; } +rtAppStatusCache::StateChangedCallbackHandle rtAppStatusCache::registerStateChangedCallback(StateChangedCallback callback) { + std::unique_lock lock(state_changed_listeners_mutex); + const auto handle = ++next_handle; + state_changed_listeners[handle] = callback; + return handle; +} + +void rtAppStatusCache::unregisterStateChangedCallback(rtAppStatusCache::StateChangedCallbackHandle callbackId) { + std::unique_lock lock(state_changed_listeners_mutex); + state_changed_listeners.erase(callbackId); +} + +void rtAppStatusCache::notifyStateChanged(std::string& id) { + std::unique_lock lock(state_changed_listeners_mutex); + for (auto& it : state_changed_listeners) { + it.second(id); + } +} + + std::string rtAppStatusCache::SearchAppStatusInCache(const char *app_name) { printf("RTCACHE : %s\n",__FUNCTION__); diff --git a/server/plat/rtcache.hpp b/server/plat/rtcache.hpp index c5c7ff98..c42cbb8d 100644 --- a/server/plat/rtcache.hpp +++ b/server/plat/rtcache.hpp @@ -29,11 +29,19 @@ #include #include +#include +#include +#include + using namespace std; class rtAppStatusCache : public rtObject { public: + + using StateChangedCallbackHandle = size_t; + using StateChangedCallback = std::function; + rtAppStatusCache(rtRemoteEnvironment* env) {ObjectCache = new rtRemoteObjectCache(env);}; ~rtAppStatusCache() {delete(ObjectCache); }; std::string getAppCacheId(const char *app_name); @@ -42,10 +50,19 @@ class rtAppStatusCache : public rtObject std::string SearchAppStatusInCache(const char *app_name); bool doIdExist(std::string id); + StateChangedCallbackHandle registerStateChangedCallback(StateChangedCallback callback); + void unregisterStateChangedCallback(StateChangedCallbackHandle callbackId); + private: + + void notifyStateChanged(std::string& id); + rtRemoteObjectCache* ObjectCache; static std::string Netflix_AppCacheId; static std::string Youtube_AppCacheId; + StateChangedCallbackHandle next_handle = 0; + std::map state_changed_listeners; + std::mutex state_changed_listeners_mutex; }; #endif diff --git a/server/plat/rtdial.cpp b/server/plat/rtdial.cpp index 06e54ca1..dbd429b9 100644 --- a/server/plat/rtdial.cpp +++ b/server/plat/rtdial.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "Module.h" @@ -40,6 +41,7 @@ #include "rtcache.hpp" #include "rtdial.hpp" #include "gdial_app_registry.h" +#include rtRemoteEnvironment* env; static GSource *remoteSource = nullptr; @@ -450,6 +452,8 @@ void rtdial_term() { #define DIAL_MAX_ADDITIONALURL (1024) +static bool await_application_state_update(const char *app_name); + map parse_query(const char* query_string) { if (!query_string) return {}; char *unescaped = g_uri_unescape_string(query_string, nullptr); @@ -661,6 +665,10 @@ int gdial_os_application_state(const char *app_name, int instance_id, GDialAppSt if (RTCAST_ERROR_RT(ret) != RT_OK) { printf("RTDIAL: DialObj.getApplicationState failed!!! Error: %s\n",rtStrError(RTCAST_ERROR_RT(ret))); return GDIAL_APP_ERROR_INTERNAL; + } else { + if (await_application_state_update(app_name)) { + State = AppCache->SearchAppStatusInCache(app_name); + } } } @@ -696,3 +704,32 @@ int gdial_os_application_state(const char *app_name, int instance_id, GDialAppSt return GDIAL_APP_ERROR_NONE; } + +static bool await_application_state_update(const char *app_name) { + static int xdial_wait_for_rtremote_state_response_ms = -1; + if (xdial_wait_for_rtremote_state_response_ms == -1) { + const char* waitstr = getenv("XDIAL_WAIT_FOR_RTREMOTE_STATE_RESPONSE_MS"); + xdial_wait_for_rtremote_state_response_ms = waitstr ? atoi(waitstr) : 0; + } + std::atomic_bool updated {false}; + if (xdial_wait_for_rtremote_state_response_ms > 0) { + // the cached status could be wrong; rtremote state update request has already been launched + // so lets give it some time & report the updated value, if possible + using namespace std::chrono; + + auto handlerid = AppCache->registerStateChangedCallback([&](const std::string& application){ + if (application == app_name) { + updated = true; + } + }); + const auto timemax = steady_clock::now() + milliseconds(xdial_wait_for_rtremote_state_response_ms); + while (steady_clock::now() < timemax && !updated) { + auto time_left = duration_cast(timemax - steady_clock::now()); + if (time_left.count() > 0) { + rtEnvironmentGetGlobal()->processSingleWorkItem(time_left, true, nullptr); + } + } + AppCache->unregisterStateChangedCallback(handlerid); + } + return updated; +} From 4afd94cd2a1412b171b7a04d7bde60f454d9fa39 Mon Sep 17 00:00:00 2001 From: Margret Jaison Date: Wed, 27 Mar 2024 14:26:58 +0530 Subject: [PATCH 2/3] OMWAPPI-1342 xdial: create GDialApp if app exists (#23) - if app instances are created outside of xdial, we might still be missing GDialApp instance. - do not fetch app state asynchronously in case xdial is configured to wait for remote state responses anyway - if current app state is 'stopped' or 'hidden' always return 201 on POST requests --- server/gdial-app.c | 7 ++++- server/gdial-rest.c | 66 ++++++++++++++++++++++++++------------------- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/server/gdial-app.c b/server/gdial-app.c index a3d3e2a9..6147cd12 100644 --- a/server/gdial-app.c +++ b/server/gdial-app.c @@ -189,6 +189,8 @@ GDialApp *gdial_app_new(const gchar *app_name) { return app; }; +int gdial_get_wait_for_rtremote_state_response(); + GDialAppError gdial_app_start(GDialApp *app, const gchar *payload, const gchar *query, const gchar *additional_data_url, gpointer state_cb_data) { g_return_val_if_fail (GDIAL_IS_APP (app), GDIAL_APP_ERROR_BAD_REQUEST); @@ -196,7 +198,10 @@ GDialAppError gdial_app_start(GDialApp *app, const gchar *payload, const gchar * priv->state_cb_data = state_cb_data; GDialAppError app_err = gdial_plat_application_start(app->name, payload, query, additional_data_url, &app->instance_id); if (app_err == GDIAL_APP_ERROR_NONE || (strcmp("system", app->name) != 0 && app->instance_id != GDIAL_APP_INSTANCE_NONE)) { - gdial_plat_application_state_async(app->name, app->instance_id, app); + // don't need to ask for state asynchronously if we're configured to refresh the state at each query anyway + if (gdial_get_wait_for_rtremote_state_response() < 1) { + gdial_plat_application_state_async(app->name, app->instance_id, app); + } app_err = gdial_plat_application_state(app->name, app->instance_id, &app->state); g_warn_if_fail(app->state == GDIAL_APP_STATE_RUNNING); } diff --git a/server/gdial-rest.c b/server/gdial-rest.c index d5960b8f..3d5c34b0 100644 --- a/server/gdial-rest.c +++ b/server/gdial-rest.c @@ -383,15 +383,38 @@ static void gdial_rest_server_handle_DELETE(SoupMessage *msg, GHashTable *query, g_object_unref(app); } -static void refresh_app_state(GDialApp *app) { +int gdial_get_wait_for_rtremote_state_response() { static int xdial_is_waiting_rtremote_state_response = -1; if (xdial_is_waiting_rtremote_state_response == -1) { xdial_is_waiting_rtremote_state_response = getenv("XDIAL_WAIT_FOR_RTREMOTE_STATE_RESPONSE_MS") && atoi(getenv("XDIAL_WAIT_FOR_RTREMOTE_STATE_RESPONSE_MS")) > 0; } + return xdial_is_waiting_rtremote_state_response; +} + +// returns TRUE if new application instance has been created; false otherwise +static gboolean refresh_app_state(const gchar *app_name) { + const int xdial_is_waiting_rtremote_state_response = gdial_get_wait_for_rtremote_state_response(); // trying to refresh the app state only makes sense if xdial is configured to wait for rt state responses - if (app && xdial_is_waiting_rtremote_state_response) { - gdial_app_state(app); + if (xdial_is_waiting_rtremote_state_response > 0) { + GDialApp *app = gdial_app_find_instance_by_name(app_name); + if (!app) { + printf("%s:%d app instance for '%s' not found, checking remote state\n", __FUNCTION__, __LINE__, app_name); + // try to fetch the remote state, maybe the app was started externally + GDialAppState state; + if (GDIAL_APP_ERROR_NONE == gdial_plat_application_state(app_name, 0, &state)) { + printf("%s:%d app instance for: '%s' remote state returned: %d; creating a new instance\n", __FUNCTION__, __LINE__, app_name, state); + // create app instance + app = gdial_app_new(app_name); + app->state = state; + return TRUE; + } else { + printf("%s:%d app instance for: '%s' no remote state returned\n", __FUNCTION__, __LINE__, app_name); + } + } else { + gdial_app_state(app); + } } + return FALSE; } static void gdial_rest_server_handle_POST(GDialRestServer *gdial_rest_server, SoupMessage* msg, GHashTable *query, const gchar *app_name) { @@ -405,30 +428,18 @@ static void gdial_rest_server_handle_POST(GDialRestServer *gdial_rest_server, So gdial_rest_server_http_return_if_fail(listening_port != 0, msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); g_printerr("Starting the app with payload %.*s\n", (int)msg->request_body->length, msg->request_body->data); + refresh_app_state(app_registry->name); GDialApp *app = gdial_app_find_instance_by_name(app_registry->name); - refresh_app_state(app); - gboolean new_app_instance = FALSE; - gboolean first_instance_created = FALSE; - GDialAppState current_state = GDIAL_APP_STATE_STOPPED; - - if (app != NULL && app_registry->is_singleton) { - /* - * Reuse app instance as is, but do not update refcnt - * per DIAL 2.1 recommendation, push relaunch decision to application platform, - */ - g_printerr("POST request received for running app [%s]\r\n", app->name); - new_app_instance = TRUE; - first_instance_created = FALSE; - current_state = GDIAL_APP_GET_STATE(app); - } - else { + if (!app) { app = gdial_app_new(app_registry->name); refresh_app_state(app); - new_app_instance = TRUE; - // we did not have GDialApp instance created yet, but the application might've already been running - // so check the state before assuming that we've actually created the app - first_instance_created = (app->state == GDIAL_APP_STATE_STOPPED); } + // new_app_instance is always effectively TRUE, due to some accumulated changes to gdial_rest_server_handle_POST logic + const gboolean new_app_instance = TRUE; + GDialAppState current_state = GDIAL_APP_GET_STATE(app); + // first_instance_created value determines if we're going to get 201(created) or 200(ok) response + // we want to return 201 in case the app state is 'Not running or hidden' and 200 in case the app is 'starting' or 'running' (from dial 2.2.1 spec) + gboolean first_instance_created = current_state == GDIAL_APP_STATE_STOPPED || current_state == GDIAL_APP_STATE_HIDE; GDialAppError start_error = GDIAL_APP_ERROR_NONE; @@ -604,13 +615,13 @@ static void gdial_rest_server_handle_POST_dial_data(GDialRestServer *gdial_rest_ /* * Cache dial_data so as to use on future queries. */ + refresh_app_state(app_name); GDialApp *app = gdial_app_find_instance_by_name(app_name); - refresh_app_state(app); if(app == NULL) { g_print("gdial_rest_server_handle_POST_dial_data creating app instance \n"); app = gdial_app_new(app_name); - refresh_app_state(app); + refresh_app_state(app_name); } gdial_rest_server_http_return_if_fail(app, msg, SOUP_STATUS_NOT_FOUND); /* @@ -885,8 +896,8 @@ static void gdial_rest_http_server_apps_callback(SoupServer *server, gdial_rest_server_handle_OPTIONS(msg, "DELETE, OPTIONS"); } else if (msg->method == SOUP_METHOD_DELETE) { + refresh_app_state(app_name); GDialApp *app = gdial_app_find_instance_by_name(app_name); - refresh_app_state(app); GDialApp *app_by_instance = gdial_rest_server_check_instance(app, instance); if (app_by_instance) { gdial_rest_server_handle_DELETE(msg, query, app); @@ -912,9 +923,8 @@ static void gdial_rest_http_server_apps_callback(SoupServer *server, gdial_rest_server_handle_OPTIONS(msg, "POST, OPTIONS"); } else if (msg->method == SOUP_METHOD_POST) { - + refresh_app_state(app_name); GDialApp *app = gdial_app_find_instance_by_name(app_name); - refresh_app_state(app); GDialApp *app_by_instance = gdial_rest_server_check_instance(app, instance); if (app_by_instance) { gdial_rest_server_handle_POST_hide(msg, app); From 3b54fbbc60c40f74b9930d572e9439b5de45d1e1 Mon Sep 17 00:00:00 2001 From: Margret Jaison Date: Wed, 27 Mar 2024 16:12:12 +0530 Subject: [PATCH 3/3] RDKDEV-1014:OMWAPPI-1545-XDialServer-patches-2401_sprint --- server/plat/rtcache.cpp | 17 +++++++++++++++++ server/plat/rtcache.hpp | 3 +++ server/plat/rtdial.cpp | 12 ++++++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/server/plat/rtcache.cpp b/server/plat/rtcache.cpp index 59ce5deb..9a7478f4 100644 --- a/server/plat/rtcache.cpp +++ b/server/plat/rtcache.cpp @@ -60,6 +60,7 @@ void rtAppStatusCache :: setAppCacheId(const char *app_name,std::string id) rtError rtAppStatusCache::UpdateAppStatusCache(rtValue app_status) { + const auto now = std::chrono::steady_clock::now(); printf("RTCACHE : %s\n",__FUNCTION__); rtError err; @@ -77,6 +78,10 @@ rtError rtAppStatusCache::UpdateAppStatusCache(rtValue app_status) err = ObjectCache->insert(id,temp); notifyStateChanged(App_name); + if (err == RT_OK) { + err = ObjectCache->markUnevictable(id, true); + last_updated[id] = now; + } return err; } @@ -131,3 +136,15 @@ bool rtAppStatusCache::doIdExist(std::string id) printf("True\n"); return true; } + +std::chrono::milliseconds rtAppStatusCache::getUpdateAge(const char *app_name) +{ + const auto now = std::chrono::steady_clock::now(); + std::string id = getAppCacheId(app_name); + auto it = last_updated.find(id); + if (it != last_updated.end()) { + return std::chrono::duration_cast(now - it->second); + } else { + return std::chrono::milliseconds::max(); + } +} diff --git a/server/plat/rtcache.hpp b/server/plat/rtcache.hpp index c42cbb8d..14049c10 100644 --- a/server/plat/rtcache.hpp +++ b/server/plat/rtcache.hpp @@ -53,6 +53,8 @@ class rtAppStatusCache : public rtObject StateChangedCallbackHandle registerStateChangedCallback(StateChangedCallback callback); void unregisterStateChangedCallback(StateChangedCallbackHandle callbackId); + std::chrono::milliseconds getUpdateAge(const char *app_name); + private: void notifyStateChanged(std::string& id); @@ -63,6 +65,7 @@ class rtAppStatusCache : public rtObject StateChangedCallbackHandle next_handle = 0; std::map state_changed_listeners; std::mutex state_changed_listeners_mutex; + std::map last_updated; }; #endif diff --git a/server/plat/rtdial.cpp b/server/plat/rtdial.cpp index dbd429b9..0486c308 100644 --- a/server/plat/rtdial.cpp +++ b/server/plat/rtdial.cpp @@ -706,17 +706,25 @@ int gdial_os_application_state(const char *app_name, int instance_id, GDialAppSt } static bool await_application_state_update(const char *app_name) { + using namespace std::chrono; static int xdial_wait_for_rtremote_state_response_ms = -1; if (xdial_wait_for_rtremote_state_response_ms == -1) { const char* waitstr = getenv("XDIAL_WAIT_FOR_RTREMOTE_STATE_RESPONSE_MS"); xdial_wait_for_rtremote_state_response_ms = waitstr ? atoi(waitstr) : 0; } + static auto xdial_max_state_value_age = milliseconds::max(); + if (xdial_max_state_value_age == milliseconds::max()) { + const char* str = getenv("XDIAL_MAX_STATE_VALUE_AGE_MS"); + xdial_max_state_value_age = milliseconds(str ? atoi(str) : 0); + } + // do not poll for the state update if currently held value is younger than XDIAL_MAX_STATE_VALUE_AGE_MS + if (xdial_max_state_value_age > milliseconds(0) && AppCache->getUpdateAge(app_name) < xdial_max_state_value_age) { + return false; + } std::atomic_bool updated {false}; if (xdial_wait_for_rtremote_state_response_ms > 0) { // the cached status could be wrong; rtremote state update request has already been launched // so lets give it some time & report the updated value, if possible - using namespace std::chrono; - auto handlerid = AppCache->registerStateChangedCallback([&](const std::string& application){ if (application == app_name) { updated = true;