From e5ecd4adb474a26f69718bc15d4e4bfd2d12d5b1 Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 31 Oct 2017 16:11:22 +0200 Subject: [PATCH 01/13] make wait_for_download call the rpc backend --- src/object-store | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/object-store b/src/object-store index 3e333d8f..7c12b340 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit 3e333d8fa081aa88fc2c435a1e328a106d4a1b7e +Subproject commit 7c12b340e078aa4ce371347e235eba51f37f84c4 From 7b49cf89efde58c87b53900f87c6363a7f7a0b40 Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 31 Oct 2017 16:54:13 +0200 Subject: [PATCH 02/13] android debugger looper fixes --- lib/browser/rpc.js | 55 +++++++++++-------- .../java/io/realm/react/RealmReactModule.java | 46 ++++++++++++++-- .../io_realm_react_RealmReactModule.cpp | 7 +++ src/android/io_realm_react_RealmReactModule.h | 7 +++ src/js_realm.hpp | 1 + src/rpc.cpp | 33 ++++++++--- src/rpc.hpp | 5 +- 7 files changed, 118 insertions(+), 36 deletions(-) diff --git a/lib/browser/rpc.js b/lib/browser/rpc.js index 37c219f1..5c04897a 100644 --- a/lib/browser/rpc.js +++ b/lib/browser/rpc.js @@ -212,6 +212,8 @@ function makeRequest(url, data) { return JSON.parse(responseText); } +let pollTimeoutId; + //returns an object from rpc serialized json value function deserialize_json_value(value) { let result = {}; @@ -230,17 +232,19 @@ function deserialize_json_value(value) { } function sendRequest(command, data, host = sessionHost) { - if (!host) { - throw new Error('Must first create RPC session with a valid host'); - } + clearTimeout(pollTimeoutId); + try { + if (!host) { + throw new Error('Must first create RPC session with a valid host'); + } - data = Object.assign({}, data, sessionId ? {sessionId} : null); + data = Object.assign({}, data, sessionId ? {sessionId} : null); - let url = 'http://' + host + '/' + command; - let response = makeRequest(url, data); + let url = 'http://' + host + '/' + command; + let response = makeRequest(url, data); - if (!response || response.error) { - let error = response && response.error; + if (!response || response.error) { + let error = response && response.error; // Remove the type prefix from the error message (e.g. "Error: "). if (error && error.replace) { @@ -261,22 +265,25 @@ function sendRequest(command, data, host = sessionHost) { throw new Error(error || `Invalid response for "${command}"`); } - - let callback = response.callback; - if (callback != null) { - let result; - let error; - try { - let realmId = data.realmId; - let thisObject = deserialize(realmId, response.this); - let args = deserialize(realmId, response.arguments); - result = registeredCallbacks[callback].apply(thisObject, args); - result = serialize(realmId, result); - } catch (e) { - error = e.message || ('' + e); + let callback = response.callback; + if (callback != null) { + let result; + let error; + try { + let realmId = data.realmId; + let thisObject = deserialize(realmId, response.this); + let args = deserialize(realmId, response.arguments); + result = registeredCallbacks[callback].apply(thisObject, args); + result = serialize(realmId, result); + } catch (e) { + error = e.message || ('' + e); + } + return sendRequest('callback_result', {callback, result, error}); } - return sendRequest('callback_result', {callback, result, error}); - } - return response.result; + return response.result; + } + finally { + pollTimeoutId = setTimeout(() => sendRequest('callbacks_poll'), 50); + } } diff --git a/react-native/android/src/main/java/io/realm/react/RealmReactModule.java b/react-native/android/src/main/java/io/realm/react/RealmReactModule.java index 18dc5d1f..f09f65ec 100644 --- a/react-native/android/src/main/java/io/realm/react/RealmReactModule.java +++ b/react-native/android/src/main/java/io/realm/react/RealmReactModule.java @@ -2,6 +2,9 @@ package io.realm.react; import android.content.res.AssetManager; import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; import android.util.Log; import com.facebook.react.bridge.ReactApplicationContext; @@ -19,6 +22,9 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RunnableFuture; import fi.iki.elonen.NanoHTTPD; @@ -37,6 +43,9 @@ class RealmReactModule extends ReactContextBaseJavaModule { SoLoader.loadLibrary("realmreact"); } + private Handler worker; + private HandlerThread workerThread; + public RealmReactModule(ReactApplicationContext reactContext) { super(reactContext); @@ -121,7 +130,9 @@ class RealmReactModule extends ReactContextBaseJavaModule { private void startWebServer() { setupChromeDebugModeRealmJsContext(); - webServer = new AndroidWebServer(DEFAULT_PORT); + startWorker(); + + webServer = new AndroidWebServer(DEFAULT_PORT, getReactApplicationContext()); try { webServer.start(); Log.i("Realm", "Starting the debugging WebServer, Host: " + webServer.getHostname() + " Port: " + webServer.getListeningPort()); @@ -130,20 +141,44 @@ class RealmReactModule extends ReactContextBaseJavaModule { } } + private void startWorker() { + workerThread = new HandlerThread("MyHandlerThread"); + workerThread.start(); + worker = new Handler(workerThread.getLooper()); + worker.postDelayed(new Runnable() { + @Override + public void run() { + boolean stop = tryRunTask(); + if (!stop) { + worker.postDelayed(this, 10); + } + } + }, 10); + } + private void stopWebServer() { if (webServer != null) { Log.i("Realm", "Stopping the webserver"); webServer.stop(); } + + if (workerThread != null) { + workerThread.quit(); + workerThread = null; + } } class AndroidWebServer extends NanoHTTPD { - public AndroidWebServer(int port) { + private ReactApplicationContext reactApplicationContext; + + public AndroidWebServer(int port, ReactApplicationContext reactApplicationContext) { super(port); + this.reactApplicationContext = reactApplicationContext; } - public AndroidWebServer(String hostname, int port) { + public AndroidWebServer(String hostname, int port, ReactApplicationContext reactApplicationContext) { super(hostname, port); + this.reactApplicationContext = reactApplicationContext; } @Override @@ -164,7 +199,7 @@ class RealmReactModule extends ReactContextBaseJavaModule { return response; } final String jsonResponse = processChromeDebugCommand(cmdUri, json); - + Response response = newFixedLengthResponse(jsonResponse); response.addHeader("Access-Control-Allow-Origin", "http://localhost:8081"); return response; @@ -185,4 +220,7 @@ class RealmReactModule extends ReactContextBaseJavaModule { // this receives one command from Chrome debug then return the processing we should post back private native String processChromeDebugCommand(String cmd, String args); + + // this receives one command from Chrome debug then return the processing we should post back + private native boolean tryRunTask(); } diff --git a/src/android/io_realm_react_RealmReactModule.cpp b/src/android/io_realm_react_RealmReactModule.cpp index 8fcd4b21..e776bca0 100644 --- a/src/android/io_realm_react_RealmReactModule.cpp +++ b/src/android/io_realm_react_RealmReactModule.cpp @@ -77,6 +77,13 @@ JNIEXPORT jstring JNICALL Java_io_realm_react_RealmReactModule_processChromeDebu return env->NewStringUTF(response.dump().c_str()); } +JNIEXPORT jboolean JNICALL Java_io_realm_react_RealmReactModule_tryRunTask +(JNIEnv *env, jclass) +{ + jboolean result = s_rpc_server->try_run_task(); + return result; +} + JNIEXPORT jboolean JNICALL Java_io_realm_react_RealmReactModule_isContextInjected (JNIEnv *env, jclass) { diff --git a/src/android/io_realm_react_RealmReactModule.h b/src/android/io_realm_react_RealmReactModule.h index f285d404..763c8086 100644 --- a/src/android/io_realm_react_RealmReactModule.h +++ b/src/android/io_realm_react_RealmReactModule.h @@ -29,6 +29,13 @@ JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactModule_setupChromeDebugMod JNIEXPORT jstring JNICALL Java_io_realm_react_RealmReactModule_processChromeDebugCommand (JNIEnv *, jclass, jstring, jstring); +/* + * Class: io_realm_react_RealmReactModule + * Method: tryRunTask + */ +JNIEXPORT jboolean JNICALL Java_io_realm_react_RealmReactModule_tryRunTask +(JNIEnv *, jclass); + /* * Class: io_realm_react_RealmReactModule * Method: isContextInjected diff --git a/src/js_realm.hpp b/src/js_realm.hpp index db192cbf..d7584b21 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -738,6 +738,7 @@ void RealmClass::wait_for_download_completion(ContextType ctx, ObjectType thi ValueType callback_arguments[1]; callback_arguments[0] = object; + Function::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments); } diff --git a/src/rpc.cpp b/src/rpc.cpp index 5f6b9b2b..5917e7e8 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -28,6 +28,7 @@ #include "object_accessor.hpp" #include "shared_realm.hpp" #include "results.hpp" +#include using namespace realm; using namespace realm::rpc; @@ -83,12 +84,14 @@ RPCServer*& get_rpc_server(JSGlobalContextRef ctx) { } RPCWorker::RPCWorker() { - m_thread = std::thread([this]() { + //m_thread = std::thread([this]() { + //m_looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); + // TODO: Create ALooper/CFRunLoop to support async calls. - while (!m_stop) { - try_run_task(); - } - }); + //while (!m_stop) { + //try_run_task(); + //} + //}); } RPCWorker::~RPCWorker() { @@ -107,23 +110,30 @@ json RPCWorker::pop_task_result() { return future.get(); } -void RPCWorker::try_run_task() { +bool RPCWorker::try_run_task() { + if (m_stop) { + return true; + } + // Use a 10 millisecond timeout to keep this thread unblocked. auto task = m_tasks.try_pop_back(10); if (!task) { - return; + return false; } (*task)(); // Since this can be called recursively, it must be pushed to the front of the queue *after* running the task. m_futures.push_front(task->get_future()); + + return m_stop; } void RPCWorker::stop() { if (!m_stop) { m_stop = true; m_thread.join(); + //m_looper = nullptr; } } @@ -288,6 +298,10 @@ RPCServer::RPCServer() { return json::object(); }; + + m_requests["/callbacks_poll"] = [this](const json dict) { + return json::object(); + }; } RPCServer::~RPCServer() { @@ -340,6 +354,7 @@ void RPCServer::run_callback(JSContextRef ctx, JSObjectRef function, JSObjectRef } return_value.set(server->deserialize_json_value(results["result"])); + } json RPCServer::perform_request(std::string name, const json &args) { @@ -388,6 +403,10 @@ json RPCServer::perform_request(std::string name, const json &args) { } } +bool RPCServer::try_run_task() { + return m_worker.try_run_task(); +} + RPCObjectID RPCServer::store_object(JSObjectRef object) { static RPCObjectID s_next_id = 1; diff --git a/src/rpc.hpp b/src/rpc.hpp index ed286d05..38c2a0d3 100644 --- a/src/rpc.hpp +++ b/src/rpc.hpp @@ -26,6 +26,7 @@ #include "json.hpp" #include "jsc_types.hpp" #include "jsc_protected.hpp" +#include namespace realm { @@ -45,11 +46,12 @@ class RPCWorker { void add_task(std::function); json pop_task_result(); - void try_run_task(); + bool try_run_task(); void stop(); private: bool m_stop = false; + //ALooper* m_looper; std::thread m_thread; ConcurrentDeque> m_tasks; ConcurrentDeque> m_futures; @@ -60,6 +62,7 @@ class RPCServer { RPCServer(); ~RPCServer(); json perform_request(std::string name, const json &args); + bool try_run_task(); private: JSGlobalContextRef m_context; From f10df2164c860c9221bd9f57f8bab8d1ec42f16e Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 5 Nov 2017 15:57:44 +0200 Subject: [PATCH 03/13] fix RN Android debugger --- lib/browser/rpc.js | 16 ++++++--- lib/extensions.js | 3 +- src/concurrent_deque.hpp | 19 ++++++++++- src/js_realm.hpp | 4 +-- src/rpc.cpp | 71 +++++++++++++++++++++++++++++++++------- src/rpc.hpp | 5 ++- 6 files changed, 97 insertions(+), 21 deletions(-) diff --git a/lib/browser/rpc.js b/lib/browser/rpc.js index 5c04897a..95d75c90 100644 --- a/lib/browser/rpc.js +++ b/lib/browser/rpc.js @@ -54,7 +54,6 @@ export function createSession(refreshAccessToken, host) { refreshAccessToken[persistentCallback] = true; sessionId = sendRequest('create_session', { refreshAccessToken: serialize(undefined, refreshAccessToken) }, host); sessionHost = host; - return sessionId; } @@ -239,9 +238,12 @@ function sendRequest(command, data, host = sessionHost) { } data = Object.assign({}, data, sessionId ? {sessionId} : null); - + + commandCounter++; let url = 'http://' + host + '/' + command; + console.log(`\n command ${commandCounter} ${command} data ${JSON.stringify(data)}`); let response = makeRequest(url, data); + console.log(`\n command ${commandCounter} response ${JSON.stringify(response)}`); if (!response || response.error) { let error = response && response.error; @@ -278,12 +280,18 @@ function sendRequest(command, data, host = sessionHost) { } catch (e) { error = e.message || ('' + e); } - return sendRequest('callback_result', {callback, result, error}); + + let callbackCommand = "callback_result"; + if (command == 'callbacks_poll') { + callbackCommand = "callback_poll_result"; + } + + return sendRequest(callbackCommand, {callback, result, error, "callback_call_counter" : response.callback_call_counter}); } return response.result; } finally { - pollTimeoutId = setTimeout(() => sendRequest('callbacks_poll'), 50); + pollTimeoutId = setTimeout(() => sendRequest('callbacks_poll'), 100); } } diff --git a/lib/extensions.js b/lib/extensions.js index 3233ae4d..9b1757da 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -76,8 +76,7 @@ module.exports = function(realmConstructor) { else { try { let syncedRealm = new realmConstructor(config); - //FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented - setTimeout(() => { resolve(syncedRealm); }, 1); + resolve(syncedRealm); } catch (e) { reject(e); } diff --git a/src/concurrent_deque.hpp b/src/concurrent_deque.hpp index 260ff107..bdd52dae 100644 --- a/src/concurrent_deque.hpp +++ b/src/concurrent_deque.hpp @@ -35,6 +35,23 @@ public: return do_pop_back(); } + T pop_if(std::function predicate) { + std::unique_lock lock(m_mutex); + + for (auto it = m_deque.begin(); it != m_deque.end();) { + if (predicate(*it)) { + T item = std::move(*it); + m_deque.erase(it); + return item; + } + else { + ++it; + } + } + + return nullptr; + } + util::Optional try_pop_back(size_t timeout) { std::unique_lock lock(m_mutex); m_condition.wait_for(lock, std::chrono::milliseconds(timeout), @@ -57,7 +74,7 @@ public: } bool empty() { - std::lock_guard lock(m_mutex); + std::lock_guard lock(m_mutex); return m_deque.empty(); } diff --git a/src/js_realm.hpp b/src/js_realm.hpp index d7584b21..26a28f93 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -728,7 +728,7 @@ void RealmClass::wait_for_download_completion(ContextType ctx, ObjectType thi HANDLESCOPE if (!error_code) { //success - Function::callback(protected_ctx, protected_callback, protected_this, 0, nullptr); + Function::callback(protected_ctx, protected_callback, typename T::Object(), 0, nullptr); } else { //fail @@ -739,7 +739,7 @@ void RealmClass::wait_for_download_completion(ContextType ctx, ObjectType thi ValueType callback_arguments[1]; callback_arguments[0] = object; - Function::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments); + Function::callback(protected_ctx, protected_callback, typename T::Object(), 1, callback_arguments); } // We keep our Realm instance alive until the callback has had a chance to open its own instance. diff --git a/src/rpc.cpp b/src/rpc.cpp index 5917e7e8..33f2105d 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -110,6 +110,16 @@ json RPCWorker::pop_task_result() { return future.get(); } +json RPCWorker::try_pop_task_result() { + // This might block until a future has been added. + auto future = m_futures.try_pop_back(0); + if (!future) { + return json::object(); + } + // This will block until a return value (or exception) is available. + return (*future).get(); +} + bool RPCWorker::try_run_task() { if (m_stop) { return true; @@ -132,7 +142,7 @@ bool RPCWorker::try_run_task() { void RPCWorker::stop() { if (!m_stop) { m_stop = true; - m_thread.join(); + //m_thread.join(); //m_looper = nullptr; } } @@ -140,6 +150,7 @@ void RPCWorker::stop() { RPCServer::RPCServer() { m_context = JSGlobalContextCreate(NULL); get_rpc_server(m_context) = this; + m_callback_call_counter = 1; // JavaScriptCore crashes when trying to walk up the native stack to print the stacktrace. // FIXME: Avoid having to do this! @@ -299,9 +310,9 @@ RPCServer::RPCServer() { return json::object(); }; - m_requests["/callbacks_poll"] = [this](const json dict) { - return json::object(); - }; +// m_requests["/callbacks_poll"] = [this](const json dict) { +// return json::object(); +// }; } RPCServer::~RPCServer() { @@ -321,6 +332,7 @@ void RPCServer::run_callback(JSContextRef ctx, JSObjectRef function, JSObjectRef return; } + u_int64_t counter = server->m_callback_call_counter++; // The first argument was curried to be the callback id. RPCObjectID callback_id = server->m_callback_ids[function]; JSObjectRef arguments_array = jsc::Object::create_array(ctx, uint32_t(argc), arguments); @@ -334,25 +346,52 @@ void RPCServer::run_callback(JSContextRef ctx, JSObjectRef function, JSObjectRef {"callback", callback_id}, {"this", this_json}, {"arguments", arguments_json}, + {"callback_call_counter", counter} }; }); - // Wait for the next callback result to come off the result stack. - while (server->m_callback_results.empty()) { - // This may recursively bring us into another callback, hence the callback results being a stack. - server->m_worker.try_run_task(); +// // Wait for the next callback result to come off the result stack. +// while (server->m_callback_results.empty()) { +// // This may recursively bring us into another callback, hence the callback results being a stack. +// server->m_worker.try_run_task(); +// } + + + json callbackResult = nullptr; + while (callbackResult == nullptr) { + callbackResult = server->m_callback_results.pop_if([&](json result) { + auto resultCallbackId = result["callback"].get(); + auto resultCallbackCounter = result["callback_call_counter"].get(); + if (resultCallbackId == callback_id && resultCallbackCounter == counter) { + return true; + } + else { + return false; + } + }); + + if (callbackResult == nullptr) { + server->m_worker.try_run_task(); + } } - json results = server->m_callback_results.pop_back(); + //json results = server->m_callback_results.pop_back(); + + json results = callbackResult; json error = results["error"]; + auto resultCallbackId = results["callback"]; + if (resultCallbackId.is_null()) { + + } // The callback id should be identical! - assert(callback_id == results["callback"].get()); + assert(callback_id == resultCallbackId.get()); if (!error.is_null()) { throw jsc::Exception(ctx, error.get()); } + return_value.set(server->deserialize_json_value(results["result"])); } @@ -370,6 +409,15 @@ json RPCServer::perform_request(std::string name, const json &args) { json results(args); m_callback_results.push_back(std::move(results)); } + else if (name == "/callback_poll_result") { + json results(args); + m_callback_results.push_back(std::move(results)); + return json::object(); + } + else if (name == "/callbacks_poll") { + auto result = m_worker.try_pop_task_result(); + return result; + } else { RPCRequest action = m_requests[name]; assert(action); @@ -397,7 +445,8 @@ json RPCServer::perform_request(std::string name, const json &args) { try { // This will either be the return value (or exception) of the action perform, OR an instruction to run a callback. - return m_worker.pop_task_result(); + auto result = m_worker.pop_task_result(); + return result; } catch (std::exception &exception) { return {{"error", exception.what()}}; } diff --git a/src/rpc.hpp b/src/rpc.hpp index 38c2a0d3..0f1c85be 100644 --- a/src/rpc.hpp +++ b/src/rpc.hpp @@ -48,11 +48,12 @@ class RPCWorker { json pop_task_result(); bool try_run_task(); void stop(); + json try_pop_task_result(); private: bool m_stop = false; //ALooper* m_looper; - std::thread m_thread; + //std::thread m_thread; ConcurrentDeque> m_tasks; ConcurrentDeque> m_futures; }; @@ -64,6 +65,7 @@ class RPCServer { json perform_request(std::string name, const json &args); bool try_run_task(); + private: JSGlobalContextRef m_context; std::mutex m_request_mutex; @@ -77,6 +79,7 @@ class RPCServer { ConcurrentDeque m_callback_results; RPCObjectID m_session_id; RPCWorker m_worker; + u_int64_t m_callback_call_counter; static void run_callback(JSContextRef, JSObjectRef, JSObjectRef, size_t, const JSValueRef[], jsc::ReturnValue &); From 5365e1bb139ba8879227157fdea68b9f49fc6033 Mon Sep 17 00:00:00 2001 From: blagoev Date: Mon, 6 Nov 2017 13:04:56 +0200 Subject: [PATCH 04/13] initial CFRunLoop impl --- react-native/ios/RealmReact/RealmReact.mm | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/react-native/ios/RealmReact/RealmReact.mm b/react-native/ios/RealmReact/RealmReact.mm index b39ebdd3..ee6382f9 100644 --- a/react-native/ios/RealmReact/RealmReact.mm +++ b/react-native/ios/RealmReact/RealmReact.mm @@ -88,6 +88,7 @@ extern "C" JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executo #if DEBUG GCDWebServer *_webServer; std::unique_ptr _rpcServer; + std::thread _workerThread; #endif } @@ -210,6 +211,15 @@ RCT_REMAP_METHOD(emit, emitEvent:(NSString *)eventName withObject:(id)object) { return [ipAddresses copy]; } +static void +TimerCallback(CFRunLoopTimerRef timer, void *info) +{ + RPCServer* rpcServer = (RPCServer*)info; + rpcServer->try_run_task(); +done: + return; +} + - (void)startRPC { [GCDWebServer setLogLevel:3]; _webServer = [[GCDWebServer alloc] init]; @@ -250,6 +260,27 @@ RCT_REMAP_METHOD(emit, emitEvent:(NSString *)eventName withObject:(id)object) { [response setValue:@"http://localhost:8081" forAdditionalHeader:@"Access-Control-Allow-Origin"]; return response; }]; + + _workerThread = std::thread([&]() { + + auto timerInterval = 0.5; + CFStringRef timerMode = CFSTR("TimerMode"); + CFRunLoopTimerContext timerContext = { 0, NULL, NULL, NULL, NULL }; + timerContext.info = &_rpcServer; + CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, + CFAbsoluteTimeGetCurrent() + timerInterval, + timerInterval, + 0, + 0, + TimerCallback, + &timerContext); + + CFRunLoopAddTimer(CFRunLoopGetCurrent(), timerRef, timerMode); + CFRunLoopRunInMode(timerMode, 4, false); + + + CFRunLoopRun(); + }); [_webServer startWithPort:WEB_SERVER_PORT bonjourName:nil]; return; From e2c6a5f9061e48d5c9731aa40c9298a9982210c8 Mon Sep 17 00:00:00 2001 From: blagoev Date: Mon, 13 Nov 2017 09:24:52 +0200 Subject: [PATCH 05/13] remove dead code --- react-native/ios/RealmReact/RealmReact.mm | 31 ----------------------- 1 file changed, 31 deletions(-) diff --git a/react-native/ios/RealmReact/RealmReact.mm b/react-native/ios/RealmReact/RealmReact.mm index ee6382f9..b39ebdd3 100644 --- a/react-native/ios/RealmReact/RealmReact.mm +++ b/react-native/ios/RealmReact/RealmReact.mm @@ -88,7 +88,6 @@ extern "C" JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executo #if DEBUG GCDWebServer *_webServer; std::unique_ptr _rpcServer; - std::thread _workerThread; #endif } @@ -211,15 +210,6 @@ RCT_REMAP_METHOD(emit, emitEvent:(NSString *)eventName withObject:(id)object) { return [ipAddresses copy]; } -static void -TimerCallback(CFRunLoopTimerRef timer, void *info) -{ - RPCServer* rpcServer = (RPCServer*)info; - rpcServer->try_run_task(); -done: - return; -} - - (void)startRPC { [GCDWebServer setLogLevel:3]; _webServer = [[GCDWebServer alloc] init]; @@ -260,27 +250,6 @@ done: [response setValue:@"http://localhost:8081" forAdditionalHeader:@"Access-Control-Allow-Origin"]; return response; }]; - - _workerThread = std::thread([&]() { - - auto timerInterval = 0.5; - CFStringRef timerMode = CFSTR("TimerMode"); - CFRunLoopTimerContext timerContext = { 0, NULL, NULL, NULL, NULL }; - timerContext.info = &_rpcServer; - CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, - CFAbsoluteTimeGetCurrent() + timerInterval, - timerInterval, - 0, - 0, - TimerCallback, - &timerContext); - - CFRunLoopAddTimer(CFRunLoopGetCurrent(), timerRef, timerMode); - CFRunLoopRunInMode(timerMode, 4, false); - - - CFRunLoopRun(); - }); [_webServer startWithPort:WEB_SERVER_PORT bonjourName:nil]; return; From 521fd129373bb9f0585f38246df267b5296c8c0c Mon Sep 17 00:00:00 2001 From: blagoev Date: Mon, 13 Nov 2017 09:26:13 +0200 Subject: [PATCH 06/13] fix iOS debugger --- src/rpc.cpp | 42 +++++++++++++++++++++++++++++++----------- src/rpc.hpp | 8 +++++--- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/rpc.cpp b/src/rpc.cpp index 33f2105d..b3b2ef42 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -28,7 +28,6 @@ #include "object_accessor.hpp" #include "shared_realm.hpp" #include "results.hpp" -#include using namespace realm; using namespace realm::rpc; @@ -83,15 +82,30 @@ RPCServer*& get_rpc_server(JSGlobalContextRef ctx) { } } -RPCWorker::RPCWorker() { - //m_thread = std::thread([this]() { - //m_looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); +#ifdef REALM_PLATFORM_APPLE +void runLoopFunc(CFRunLoopRef loop, RPCWorker* rpcWorker) { + auto m_stop = false; + CFRunLoopPerformBlock(loop, kCFRunLoopDefaultMode, + ^{ + rpcWorker->try_run_task(); + if (rpcWorker->should_stop()) { + CFRunLoopStop(CFRunLoopGetCurrent()); + } else { + runLoopFunc(loop, rpcWorker); + } + }); + CFRunLoopWakeUp(loop); +} +#endif - // TODO: Create ALooper/CFRunLoop to support async calls. - //while (!m_stop) { - //try_run_task(); - //} - //}); +RPCWorker::RPCWorker() { + #ifdef REALM_PLATFORM_APPLE + m_thread = std::thread([this]() { + m_loop = CFRunLoopGetCurrent(); + runLoopFunc(m_loop, this); + CFRunLoopRun(); + }); + #endif } RPCWorker::~RPCWorker() { @@ -139,11 +153,17 @@ bool RPCWorker::try_run_task() { return m_stop; } +bool RPCWorker::should_stop() { + return m_stop; +} + void RPCWorker::stop() { if (!m_stop) { m_stop = true; - //m_thread.join(); - //m_looper = nullptr; +#if REALM_PLATFORM_APPLE + m_thread.join(); + m_loop = nullptr; +#endif } } diff --git a/src/rpc.hpp b/src/rpc.hpp index 0f1c85be..4fd434de 100644 --- a/src/rpc.hpp +++ b/src/rpc.hpp @@ -26,7 +26,6 @@ #include "json.hpp" #include "jsc_types.hpp" #include "jsc_protected.hpp" -#include namespace realm { @@ -49,11 +48,14 @@ class RPCWorker { bool try_run_task(); void stop(); json try_pop_task_result(); + bool should_stop(); private: bool m_stop = false; - //ALooper* m_looper; - //std::thread m_thread; +#if REALM_PLATFORM_APPLE + std::thread m_thread; + CFRunLoopRef m_loop; +#endif ConcurrentDeque> m_tasks; ConcurrentDeque> m_futures; }; From c3ac7ac81d3323c3e96ae76fe269fb0a13cb7c77 Mon Sep 17 00:00:00 2001 From: blagoev Date: Mon, 13 Nov 2017 22:52:50 +0200 Subject: [PATCH 07/13] fix iOS code --- lib/browser/rpc.js | 2 +- src/js_realm.hpp | 2 +- src/object-store | 2 +- src/rpc.cpp | 6 +++--- src/rpc.hpp | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/browser/rpc.js b/lib/browser/rpc.js index 95d75c90..2d1320ca 100644 --- a/lib/browser/rpc.js +++ b/lib/browser/rpc.js @@ -229,7 +229,7 @@ function deserialize_json_value(value) { return result; } - +let commandCounter = 0; function sendRequest(command, data, host = sessionHost) { clearTimeout(pollTimeoutId); try { diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 26a28f93..aa00e8bc 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -756,7 +756,7 @@ void RealmClass::wait_for_download_completion(ContextType ctx, ObjectType thi auto syncSession = create_object>(ctx, new WeakSession(session)); ValueType callback_arguments[1]; callback_arguments[0] = syncSession; - Function::callback(protected_ctx, session_callback_func, protected_this, 1, callback_arguments); + Function::callback(protected_ctx, session_callback_func, typename T::Object(), 1, callback_arguments); } session->wait_for_download_completion(std::move(wait_handler)); diff --git a/src/object-store b/src/object-store index 7c12b340..3e333d8f 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit 7c12b340e078aa4ce371347e235eba51f37f84c4 +Subproject commit 3e333d8fa081aa88fc2c435a1e328a106d4a1b7e diff --git a/src/rpc.cpp b/src/rpc.cpp index b3b2ef42..f611de76 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -82,7 +82,7 @@ RPCServer*& get_rpc_server(JSGlobalContextRef ctx) { } } -#ifdef REALM_PLATFORM_APPLE +#ifdef __APPLE__ void runLoopFunc(CFRunLoopRef loop, RPCWorker* rpcWorker) { auto m_stop = false; CFRunLoopPerformBlock(loop, kCFRunLoopDefaultMode, @@ -99,7 +99,7 @@ void runLoopFunc(CFRunLoopRef loop, RPCWorker* rpcWorker) { #endif RPCWorker::RPCWorker() { - #ifdef REALM_PLATFORM_APPLE + #ifdef __APPLE__ m_thread = std::thread([this]() { m_loop = CFRunLoopGetCurrent(); runLoopFunc(m_loop, this); @@ -160,7 +160,7 @@ bool RPCWorker::should_stop() { void RPCWorker::stop() { if (!m_stop) { m_stop = true; -#if REALM_PLATFORM_APPLE +#if __APPLE__ m_thread.join(); m_loop = nullptr; #endif diff --git a/src/rpc.hpp b/src/rpc.hpp index 4fd434de..f753c4eb 100644 --- a/src/rpc.hpp +++ b/src/rpc.hpp @@ -52,7 +52,7 @@ class RPCWorker { private: bool m_stop = false; -#if REALM_PLATFORM_APPLE +#if __APPLE__ std::thread m_thread; CFRunLoopRef m_loop; #endif From 3e65994474ff7ec49d1b06b59125c8054fc93b1e Mon Sep 17 00:00:00 2001 From: blagoev Date: Mon, 13 Nov 2017 22:53:27 +0200 Subject: [PATCH 08/13] remove debug code --- lib/browser/rpc.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/browser/rpc.js b/lib/browser/rpc.js index 2d1320ca..7398d3ab 100644 --- a/lib/browser/rpc.js +++ b/lib/browser/rpc.js @@ -229,7 +229,7 @@ function deserialize_json_value(value) { return result; } -let commandCounter = 0; + function sendRequest(command, data, host = sessionHost) { clearTimeout(pollTimeoutId); try { @@ -241,9 +241,7 @@ function sendRequest(command, data, host = sessionHost) { commandCounter++; let url = 'http://' + host + '/' + command; - console.log(`\n command ${commandCounter} ${command} data ${JSON.stringify(data)}`); let response = makeRequest(url, data); - console.log(`\n command ${commandCounter} response ${JSON.stringify(response)}`); if (!response || response.error) { let error = response && response.error; From d22f1cd1ed52438e2474c3f763c5f6b861c9ab34 Mon Sep 17 00:00:00 2001 From: blagoev Date: Mon, 13 Nov 2017 22:57:04 +0200 Subject: [PATCH 09/13] remove commented code --- src/rpc.cpp | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/rpc.cpp b/src/rpc.cpp index f611de76..1868ce71 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -329,10 +329,6 @@ RPCServer::RPCServer() { return json::object(); }; - -// m_requests["/callbacks_poll"] = [this](const json dict) { -// return json::object(); -// }; } RPCServer::~RPCServer() { @@ -370,13 +366,7 @@ void RPCServer::run_callback(JSContextRef ctx, JSObjectRef function, JSObjectRef }; }); -// // Wait for the next callback result to come off the result stack. -// while (server->m_callback_results.empty()) { -// // This may recursively bring us into another callback, hence the callback results being a stack. -// server->m_worker.try_run_task(); -// } - - + // Wait for this callback call result to come off the result stack. json callbackResult = nullptr; while (callbackResult == nullptr) { callbackResult = server->m_callback_results.pop_if([&](json result) { @@ -395,8 +385,6 @@ void RPCServer::run_callback(JSContextRef ctx, JSObjectRef function, JSObjectRef } } - //json results = server->m_callback_results.pop_back(); - json results = callbackResult; json error = results["error"]; From ba2309a54059c2158978abdf75a4c39b1091efb6 Mon Sep 17 00:00:00 2001 From: blagoev Date: Mon, 13 Nov 2017 23:31:33 +0200 Subject: [PATCH 10/13] revert the promise resolve --- lib/extensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/extensions.js b/lib/extensions.js index 9b1757da..467e3347 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -76,7 +76,7 @@ module.exports = function(realmConstructor) { else { try { let syncedRealm = new realmConstructor(config); - resolve(syncedRealm); + setTimeout(() => { resolve(syncedRealm); }, 1); } catch (e) { reject(e); } From 9bcfa35bba27aa0afaac75a429e3339b57191969 Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 14 Nov 2017 08:58:21 +0200 Subject: [PATCH 11/13] fix js code --- lib/browser/rpc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/browser/rpc.js b/lib/browser/rpc.js index 7398d3ab..ed7e94f2 100644 --- a/lib/browser/rpc.js +++ b/lib/browser/rpc.js @@ -239,7 +239,6 @@ function sendRequest(command, data, host = sessionHost) { data = Object.assign({}, data, sessionId ? {sessionId} : null); - commandCounter++; let url = 'http://' + host + '/' + command; let response = makeRequest(url, data); From c68c3983cfb176073b22494c01ddb230b7c5848b Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 14 Nov 2017 11:08:40 +0200 Subject: [PATCH 12/13] format the code --- lib/browser/rpc.js | 88 +++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/lib/browser/rpc.js b/lib/browser/rpc.js index ed7e94f2..e4e00485 100644 --- a/lib/browser/rpc.js +++ b/lib/browser/rpc.js @@ -21,7 +21,7 @@ import * as base64 from './base64'; import { keys, objectTypes } from './constants'; -const {id: idKey, realm: _realmKey} = keys; +const { id: idKey, realm: _realmKey } = keys; let registeredCallbacks = []; const typeConverters = {}; @@ -41,8 +41,8 @@ if (XMLHttpRequest.__proto__ != global.XMLHttpRequestEventTarget) { global.XMLHttpRequest = fakeXMLHttpRequest; } -registerTypeConverter(objectTypes.DATA, (_, {value}) => base64.decode(value)); -registerTypeConverter(objectTypes.DATE, (_, {value}) => new Date(value)); +registerTypeConverter(objectTypes.DATA, (_, { value }) => base64.decode(value)); +registerTypeConverter(objectTypes.DATE, (_, { value }) => new Date(value)); registerTypeConverter(objectTypes.DICT, deserializeDict); registerTypeConverter(objectTypes.FUNCTION, deserializeFunction); @@ -62,18 +62,18 @@ export function createRealm(args) { args = args.map((arg) => serialize(null, arg)); } - return sendRequest('create_realm', {arguments: args}); + return sendRequest('create_realm', { arguments: args }); } export function createUser(args) { args = args.map((arg) => serialize(null, arg)); - const result = sendRequest('create_user', {arguments: args}); + const result = sendRequest('create_user', { arguments: args }); return deserialize(undefined, result); } export function _adminUser(args) { args = args.map((arg) => serialize(null, arg)); - const result = sendRequest('_adminUser', {arguments: args}); + const result = sendRequest('_adminUser', { arguments: args }); return deserialize(undefined, result); } @@ -82,18 +82,18 @@ export function callMethod(realmId, id, name, args) { args = args.map((arg) => serialize(realmId, arg)); } - let result = sendRequest('call_method', {realmId, id, name, arguments: args}); + let result = sendRequest('call_method', { realmId, id, name, arguments: args }); return deserialize(realmId, result); } export function getProperty(realmId, id, name) { - let result = sendRequest('get_property', {realmId, id, name}); + let result = sendRequest('get_property', { realmId, id, name }); return deserialize(realmId, result); } export function setProperty(realmId, id, name, value) { value = serialize(realmId, value); - sendRequest('set_property', {realmId, id, name, value}); + sendRequest('set_property', { realmId, id, name, value }); } export function getAllUsers() { @@ -115,36 +115,36 @@ function registerCallback(callback) { function serialize(realmId, value) { if (typeof value == 'undefined') { - return {type: objectTypes.UNDEFINED}; + return { type: objectTypes.UNDEFINED }; } if (typeof value == 'function') { - return {type: objectTypes.FUNCTION, value: registerCallback(value)}; + return { type: objectTypes.FUNCTION, value: registerCallback(value) }; } if (!value || typeof value != 'object') { - return {value: value}; + return { value: value }; } let id = value[idKey]; if (id) { - return {id}; + return { id }; } if (value instanceof Date) { - return {type: objectTypes.DATE, value: value.getTime()}; + return { type: objectTypes.DATE, value: value.getTime() }; } if (Array.isArray(value)) { let array = value.map((item) => serialize(realmId, item)); - return {value: array}; + return { value: array }; } if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) { - return {type: objectTypes.DATA, value: base64.encode(value)}; + return { type: objectTypes.DATA, value: base64.encode(value) }; } let keys = Object.keys(value); let values = keys.map((key) => serialize(realmId, value[key])); - return {type: objectTypes.DICT, keys, values}; + return { type: objectTypes.DICT, keys, values }; } export function deserialize(realmId, info) { @@ -163,7 +163,7 @@ export function deserialize(realmId, info) { } function deserializeDict(realmId, info) { - let {keys, values} = info; + let { keys, values } = info; let object = {}; for (let i = 0, len = keys.length; i < len; i++) { @@ -185,10 +185,10 @@ function makeRequest(url, data) { if (global.__debug__) { let request = global.__debug__.require('sync-request'); let response = request('POST', url, { - body: JSON.stringify(data), - headers: { - "Content-Type": "text/plain;charset=UTF-8" - } + body: JSON.stringify(data), + headers: { + "Content-Type": "text/plain;charset=UTF-8" + } }); statusCode = response.statusCode; @@ -226,7 +226,7 @@ function deserialize_json_value(value) { result[propName] = propValue.value; } } - + return result; } @@ -237,33 +237,33 @@ function sendRequest(command, data, host = sessionHost) { throw new Error('Must first create RPC session with a valid host'); } - data = Object.assign({}, data, sessionId ? {sessionId} : null); - + data = Object.assign({}, data, sessionId ? { sessionId } : null); + let url = 'http://' + host + '/' + command; let response = makeRequest(url, data); if (!response || response.error) { let error = response && response.error; - // Remove the type prefix from the error message (e.g. "Error: "). - if (error && error.replace) { - error = error.replace(/^[a-z]+: /i, ''); - } - else if (error.type && error.type === 'dict') { - const responseError = deserialize_json_value(error); - let responeMessage; - if (response.message && response.message !== '') { - // Remove the type prefix from the error message (e.g. "Error: "). - responeMessage = response.message.replace(/^[a-z]+: /i, ''); + // Remove the type prefix from the error message (e.g. "Error: "). + if (error && error.replace) { + error = error.replace(/^[a-z]+: /i, ''); + } + else if (error.type && error.type === 'dict') { + const responseError = deserialize_json_value(error); + let responeMessage; + if (response.message && response.message !== '') { + // Remove the type prefix from the error message (e.g. "Error: "). + responeMessage = response.message.replace(/^[a-z]+: /i, ''); + } + + const exceptionToReport = new Error(responeMessage); + Object.assign(exceptionToReport, responseError); + throw exceptionToReport; } - const exceptionToReport = new Error(responeMessage); - Object.assign(exceptionToReport, responseError); - throw exceptionToReport; + throw new Error(error || `Invalid response for "${command}"`); } - - throw new Error(error || `Invalid response for "${command}"`); - } let callback = response.callback; if (callback != null) { let result; @@ -277,13 +277,13 @@ function sendRequest(command, data, host = sessionHost) { } catch (e) { error = e.message || ('' + e); } - + let callbackCommand = "callback_result"; if (command == 'callbacks_poll') { - callbackCommand = "callback_poll_result"; + callbackCommand = "callback_poll_result"; } - return sendRequest(callbackCommand, {callback, result, error, "callback_call_counter" : response.callback_call_counter}); + return sendRequest(callbackCommand, { callback, result, error, "callback_call_counter": response.callback_call_counter }); } return response.result; From 255873ab9de8d0ae4e08e0ba77b9f0245440b5ee Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 14 Nov 2017 11:11:28 +0200 Subject: [PATCH 13/13] add changelog entry --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08ed80c5..e494c76b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +X.Y.Z Release notes +============================================================= +### Breaking changes +* None. + +### Enchancements +* None + +### Bug fixes +* Fixes Realm.open hangs in React Native debugger for iOS and Android + +### Internal +* None. + + 2.0.6 Release notes (2017-11-10) ============================================================= ### Breaking changes