Skip to content
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
1 change: 1 addition & 0 deletions include/IJavaScriptContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
class IJavaScriptContext
{
public:
virtual ~IJavaScriptContext() = default;
virtual bool runScript(const char *script, bool isModule=true, std::string name="", const char *args = nullptr, bool isApplication=false) = 0;
virtual bool runFile(const char *file, const char* args, bool isApplication=false) = 0;
virtual std::string getUrl() = 0;
Expand Down
16 changes: 12 additions & 4 deletions include/JSRuntimeClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,23 @@ template <typename Derived>
class CommandInterface
{
public:
CommandInterface() = default;
CommandInterface() : mResponseReceived(false) {}

bool sendCommand(std::string command, std::string &response)
{
Derived &derived = static_cast<Derived &>(*this);
if (derived.send(command))
{
std::unique_lock<std::mutex> lock(mResponseMutex);
mResponseCondition.wait_for(lock, std::chrono::seconds(5));
response = mLastResponse;
return true;
mResponseReceived = false;
bool gotResponse = mResponseCondition.wait_for(lock, std::chrono::seconds(5),
Comment on lines 48 to +52
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In CommandInterface::sendCommand(), derived.send(command) happens before mResponseReceived is reset under mResponseMutex. If a response arrives quickly, onMessage() can set mResponseReceived=true and notify before sendCommand() takes the lock, and then sendCommand() overwrites the flag back to false and waits the full timeout (missed wakeup). Reset the flag (or increment a sequence counter) while holding mResponseMutex before sending, and consider clearing/consuming mLastResponse in the same critical section.

Copilot uses AI. Check for mistakes.
[this]() { return mResponseReceived; });

if (gotResponse)
{
response = mLastResponse;
return true;
}
}

return false;
Expand All @@ -63,6 +69,7 @@ class CommandInterface
{
std::lock_guard<std::mutex> lock(mResponseMutex);
mLastResponse = message;
mResponseReceived = true;
mResponseCondition.notify_one();
}

Expand All @@ -71,6 +78,7 @@ class CommandInterface
CommandInterface &operator=(const CommandInterface &) = delete;

std::string mLastResponse;
bool mResponseReceived;
std::mutex mResponseMutex;
std::condition_variable mResponseCondition;
};
Expand Down
62 changes: 36 additions & 26 deletions src/JSRuntimeClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ bool JSRuntimeClient::run()
t.detach();

std::unique_lock<std::mutex> lock(mStateMutex);
mStateCondition.wait_for(lock, std::chrono::seconds(5));
mStateCondition.wait_for(lock, std::chrono::seconds(5), [this]() {
return mState != "none";
});
return mState == "open";
}

Expand Down Expand Up @@ -152,37 +154,45 @@ void JSRuntimeClient::onClose(websocketpp::connection_hdl hdl)
#ifndef UNIT_TEST_BUILD
int main(int argc, char **argv)
{
std::string command;
std::string response;
try {
std::string command;
std::string response;

if (argc > 1)
{
NativeJSLogger::log(INFO, "Send input commands at ws://localhost:%s\n", std::to_string(WS_SERVER_PORT).c_str());
return -1;
}

JSRuntimeClient *client = JSRuntimeClient::getInstance();
client->initialize(WS_SERVER_PORT);
if (!client->run())
{
NativeJSLogger::log(ERROR, "Unable to connect to server\n");
return -1;
}
if (argc > 1)
{
NativeJSLogger::log(INFO, "Send input commands at ws://localhost:%s\n", std::to_string(WS_SERVER_PORT).c_str());
return -1;
}

while (client->getState() == "open" && std::getline(std::cin, command))
{
client->sendCommand(command, response);
if (!response.empty())
JSRuntimeClient *client = JSRuntimeClient::getInstance();
client->initialize(WS_SERVER_PORT);
if (!client->run())
{
NativeJSLogger::log(INFO, "Response: %s\n", response.c_str());
NativeJSLogger::log(ERROR, "Unable to connect to server\n");
return -1;
}
else

while (client->getState() == "open" && std::getline(std::cin, command))
{
NativeJSLogger::log(WARN, "Missing response\n");
break;
client->sendCommand(command, response);
if (!response.empty())
{
NativeJSLogger::log(INFO, "Response: %s\n", response.c_str());
}
else
{
NativeJSLogger::log(WARN, "Missing response\n");
break;
}
}
}

return 0;
return 0;
} catch (const std::exception& e) {
NativeJSLogger::log(ERROR, "Uncaught exception in main: %s\n", e.what());
return -1;
} catch (...) {
NativeJSLogger::log(ERROR, "Unknown exception caught in main\n");
return -1;
}
}
#endif
46 changes: 28 additions & 18 deletions src/JSRuntimeClientContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,36 @@
#include "NativeJSLogger.h"
int main()
{
std::string containerId = "com.sky.as.apps_TestApp";
const std::string basePath = "/opt/twocontext"; // constant base path
const std::vector<std::string> apps = {"app1", "app2"};

std::string ipAddress = JSRuntimeContainer::getContainerIpAddress(containerId);
if (ipAddress.empty()) {
NativeJSLogger::log(ERROR, "Failed to retrieve IP address for container");
return 1;
}
try {
std::string containerId = "com.sky.as.apps_TestApp";
const std::string basePath = "/opt/twocontext"; // constant base path
const std::vector<std::string> apps = {"app1", "app2"};

std::string ipAddress = JSRuntimeContainer::getContainerIpAddress(containerId);
if (ipAddress.empty()) {
NativeJSLogger::log(ERROR, "Failed to retrieve IP address for container\n");
return 1;
}

for (const auto &app : apps) {
std::string url = basePath + std::string("/") + app + std::string("/index.html");
if (access(url.c_str(), F_OK) == 0) {
std::string pathAppConfig = basePath + std::string("/") + app + std::string("/app.config");
std::string options = JSRuntimeContainer::parseAppConfig(pathAppConfig);
std::string message = JSRuntimeContainer::buildLaunchMessage(url, options);
JSRuntimeContainer::connectAndSend(ipAddress, message);
for (const auto &app : apps) {
std::string url = basePath + std::string("/") + app + std::string("/index.html");
if (access(url.c_str(), F_OK) == 0) {
std::string pathAppConfig = basePath + std::string("/") + app + std::string("/app.config");
std::string options = JSRuntimeContainer::parseAppConfig(pathAppConfig);
std::string message = JSRuntimeContainer::buildLaunchMessage(url, options);
JSRuntimeContainer::connectAndSend(ipAddress, message);
}
}
}

return 0;
return 0;
}
catch (const std::exception& e) {
NativeJSLogger::log(ERROR, "Exception in main: %s\n", e.what());
return 1;
}
catch (...) {
NativeJSLogger::log(ERROR, "Unknown exception in main\n");
return 1;
}
}

2 changes: 1 addition & 1 deletion src/JSRuntimeServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class JsonWrap

uint32_t getUint32(const char *name, bool &err)
{
uint32_t res;
uint32_t res = 0;
cJSON *itm = cJSON_GetObjectItem(mPtr, name);
if (!itm || !cJSON_IsNumber(itm))
{
Expand Down
75 changes: 49 additions & 26 deletions src/NativeJSRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <string.h>
#include <vector>
#include <thread>
#include <atomic>
#include <signal.h>
#ifndef __APPLE__
#include <linux/input.h>
Expand Down Expand Up @@ -225,10 +226,8 @@ void NativeJSRenderer::setEnvForConsoleMode(ModuleSettings& moduleSettings)

uint32_t NativeJSRenderer::createApplicationIdentifier()
{
static uint32_t id = 1;
uint32_t ret = id;
id++;
return ret;
static std::atomic<uint32_t> id{1};
return id++;
}

uint32_t NativeJSRenderer::createApplication(ModuleSettings& moduleSettings, std::string userAgent)
Expand Down Expand Up @@ -292,6 +291,7 @@ bool NativeJSRenderer::terminateApplication(uint32_t id)
return true;
}

// REQUIRES: Caller must hold mUserMutex
void NativeJSRenderer::createApplicationInternal(ApplicationRequest& appRequest)
{
double startTime = getTimeInMilliSec();
Expand Down Expand Up @@ -330,9 +330,9 @@ void NativeJSRenderer::createApplicationInternal(ApplicationRequest& appRequest)
context->setCreateApplicationEndTime(endTime, id);

mContextMap[id].context=context;
mUserMutex.unlock();
}

// REQUIRES: Caller must hold mUserMutex
void NativeJSRenderer::runApplicationInternal(ApplicationRequest& appRequest)
{
uint32_t id = appRequest.mId;
Expand Down Expand Up @@ -365,7 +365,7 @@ void NativeJSRenderer::runApplicationInternal(ApplicationRequest& appRequest)
NativeJSLogger::log(INFO, "Adding the window location: %s to js file\n", window.str().c_str());
context->runScript(window.str().c_str(),true, url, nullptr, true);
}
NativeJSLogger::log(INFO, "nativeJS application thunder execution url: %s, result: %d\n", url.c_str(), ret ? 1 : 0);
NativeJSLogger::log(INFO, "nativeJS application thunder execution url: %s\n", url.c_str());
ret = context->runScript(chunk.contentsBuffer, true, url, nullptr, true);
NativeJSLogger::log(INFO, "nativeJS application execution result: %d\n", ret ? 1 : 0);
double duration = context->getExecutionDuration();
Expand Down Expand Up @@ -398,6 +398,7 @@ void NativeJSRenderer::runApplicationInternal(ApplicationRequest& appRequest)
}
}

// REQUIRES: Caller must hold mUserMutex
void NativeJSRenderer::runJavaScriptInternal(ApplicationRequest& appRequest)
{
uint32_t id = appRequest.mId;
Expand Down Expand Up @@ -427,6 +428,7 @@ void NativeJSRenderer::runJavaScriptInternal(ApplicationRequest& appRequest)
}
}

// REQUIRES: Caller must hold mUserMutex
void NativeJSRenderer::terminateApplicationInternal(ApplicationRequest& AppRequest)
{
uint32_t id = AppRequest.mId;
Expand All @@ -445,7 +447,7 @@ void NativeJSRenderer::terminateApplicationInternal(ApplicationRequest& AppReque

else
{
NativeJSLogger::log(ERROR, "Unable to find application with id: %d and url: %s\n", id, mContextMap[id].url);
NativeJSLogger::log(ERROR, "Unable to find application with id: %d\n", id);
return ;
}

Expand All @@ -468,11 +470,10 @@ void NativeJSRenderer::run()
{
while(mRunning)
{
uint32_t id;
mUserMutex.lock();
if (mConsoleMode) {
processDevConsoleRequests();
}
mUserMutex.lock();
for (int i=0; i<gPendingRequests.size(); i++)
{

Expand Down Expand Up @@ -506,10 +507,13 @@ void NativeJSRenderer::run()
if(!mTestFileName.empty())
{
ModuleSettings settings;
uint32_t id = createApplicationIdentifier();
settings.enableJSDOM = mEnableTestFileDOMSupport;
ApplicationRequest appRequest(id, RUN, mTestFileName, settings.enableHttp, settings.enableXHR, settings.enableWebSocket, settings.enableWebSocketEnhanced, settings.enableFetch, settings.enableJSDOM, settings.enableWindow, settings.enablePlayer);
mUserMutex.lock();
NativeJSRenderer::createApplicationInternal(appRequest);
NativeJSRenderer::runApplicationInternal(appRequest);
mUserMutex.unlock();
mTestFileName = "";
}

Expand All @@ -531,27 +535,30 @@ void NativeJSRenderer::run()

void NativeJSRenderer::processDevConsoleRequests()
{
std::deque<std::string> localQueue;

// Move items from shared queue to local queue while holding the lock
mConsoleState->inputMutex.lock();

if (mConsoleState->codeToExecute.empty()) {
mConsoleState->inputMutex.unlock();
return;
}
localQueue.swap(mConsoleState->codeToExecute);
mConsoleState->inputMutex.unlock();

std::lock_guard<std::mutex> lockg(mConsoleState->isProcessing_cv_m);
bool dataProcessed = false;

for (; !mConsoleState->codeToExecute.empty(); mConsoleState->codeToExecute.pop_front()) {
bool ret = mConsoleState->consoleContext->runScript(mConsoleState->codeToExecute.front().c_str(), false);
// Process items from local queue (no race condition)
for (const auto& code : localQueue) {
bool ret = mConsoleState->consoleContext->runScript(code.c_str(), false);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

processDevConsoleRequests() assigns the return value of runScript() to ret, but ret is never used. This will trigger an unused-variable warning (and may fail builds if warnings are treated as errors). Either remove ret or use it (e.g., log/report execution failures).

Suggested change
bool ret = mConsoleState->consoleContext->runScript(code.c_str(), false);
mConsoleState->consoleContext->runScript(code.c_str(), false);

Copilot uses AI. Check for mistakes.
dataProcessed = true;
}

if (dataProcessed) {
mConsoleState->isProcessing = false;
mConsoleState->isProcessing_cv.notify_one();
}

mConsoleState->inputMutex.unlock();
}

std::atomic_bool NativeJSRenderer::consoleLoop = true;
Expand Down Expand Up @@ -620,18 +627,34 @@ bool NativeJSRenderer::downloadFile(std::string& url, MemoryStruct& chunk)
curl = curl_easy_init();
if (curl)
{
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, HeaderCallback);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)&chunk);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
curl_easy_setopt(curl, CURLOPT_PROXY, "");
// Helper lambda to check curl_easy_setopt results
auto setOptWithCheck = [curl](CURLoption option, auto value, const char* optionName) -> bool {
CURLcode optRes = curl_easy_setopt(curl, option, value);
if (optRes != CURLE_OK) {
NativeJSLogger::log(ERROR, "Failed to set %s: %s\n", optionName, curl_easy_strerror(optRes));
return false;
}
return true;
};

// Set options; cleanup and return if any fail
if (
!setOptWithCheck(CURLOPT_URL, url.c_str(), "CURLOPT_URL") ||
!setOptWithCheck(CURLOPT_FOLLOWLOCATION, 1L, "CURLOPT_FOLLOWLOCATION") ||
!setOptWithCheck(CURLOPT_HEADERFUNCTION, HeaderCallback, "CURLOPT_HEADERFUNCTION") ||
!setOptWithCheck(CURLOPT_HEADERDATA, (void *)&chunk, "CURLOPT_HEADERDATA") ||
!setOptWithCheck(CURLOPT_WRITEFUNCTION, WriteMemoryCallback, "CURLOPT_WRITEFUNCTION") ||
!setOptWithCheck(CURLOPT_WRITEDATA, (void *)&chunk, "CURLOPT_WRITEDATA") ||
!setOptWithCheck(CURLOPT_TIMEOUT, 30L, "CURLOPT_TIMEOUT") ||
!setOptWithCheck(CURLOPT_NOSIGNAL, 1L, "CURLOPT_NOSIGNAL") ||
!setOptWithCheck(CURLOPT_SSL_VERIFYHOST, 2L, "CURLOPT_SSL_VERIFYHOST") ||
!setOptWithCheck(CURLOPT_SSL_VERIFYPEER, 1L, "CURLOPT_SSL_VERIFYPEER") ||
!setOptWithCheck(CURLOPT_USERAGENT, "libcurl-agent/1.0", "CURLOPT_USERAGENT") ||
!setOptWithCheck(CURLOPT_PROXY, "", "CURLOPT_PROXY")
) {
curl_easy_cleanup(curl);
return ret;
}


//curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
Expand Down
Loading
Loading