Merge pull request #407 from realm/sk-chrome-migration

All tests now pass in Chrome debug mode (including migration)
This commit is contained in:
Scott Kyle 2016-05-02 16:19:09 -07:00
commit 741db2037f
14 changed files with 355 additions and 172 deletions

View File

@ -28,11 +28,33 @@ import * as rpc from './rpc';
import * as util from './util';
const {debugHosts, debugPort} = NativeModules.Realm;
const listenersKey = Symbol();
rpc.registerTypeConverter(objectTypes.LIST, createList);
rpc.registerTypeConverter(objectTypes.RESULTS, createResults);
rpc.registerTypeConverter(objectTypes.OBJECT, createObject);
rpc.registerTypeConverter(objectTypes.REALM, createRealm);
function createRealm(_, info) {
let realm = Object.create(Realm.prototype);
setupRealm(realm, info.id);
return realm;
}
function setupRealm(realm, realmId) {
realm[keys.id] = realmId;
realm[keys.realm] = realmId;
realm[keys.type] = objectTypes.REALM;
[
'path',
'readOnly',
'schema',
'schemaVersion',
].forEach((name) => {
Object.defineProperty(realm, name, {get: util.getterForProperty(name)});
});
}
export default class Realm {
constructor(config) {
@ -63,20 +85,7 @@ export default class Realm {
let realmId = rpc.createRealm(Array.from(arguments));
registerConstructors(realmId, constructors);
this[keys.id] = realmId;
this[keys.realm] = realmId;
this[keys.type] = objectTypes.REALM;
this[listenersKey] = new Set();
[
'path',
'readOnly',
'schema',
'schemaVersion',
].forEach((name) => {
Object.defineProperty(this, name, {get: util.getterForProperty(name)});
});
setupRealm(this, realmId);
}
create(type, ...args) {
@ -96,62 +105,13 @@ export default class Realm {
let method = util.createMethod(objectTypes.REALM, 'objects');
return method.apply(this, [type, ...args]);
}
addListener(name, callback) {
if (typeof callback != 'function') {
throw new Error('Realm.addListener must be passed a function!');
}
if (name != 'change') {
throw new Error("Only 'change' notification is supported.");
}
this[listenersKey].add(callback);
}
removeListener(name, callback) {
if (typeof callback != 'function') {
throw new Error('Realm.removeListener must be passed a function!');
}
if (name != 'change') {
throw new Error("Only 'change' notification is supported.");
}
this[listenersKey].delete(callback);
}
removeAllListeners(name) {
if (name != undefined && name != 'change') {
throw new Error("Only 'change' notification is supported.");
}
this[listenersKey].clear();
}
write(callback) {
let realmId = this[keys.realm];
if (!realmId) {
throw new TypeError('write method was not called on a Realm object!');
}
if (typeof callback != 'function') {
throw new TypeError('Realm.write() must be passed a function!');
}
rpc.beginTransaction(realmId);
try {
callback();
} catch (e) {
rpc.cancelTransaction(realmId);
collections.fireMutationListeners(realmId);
throw e;
}
rpc.commitTransaction(realmId);
this[listenersKey].forEach((cb) => cb(this, 'change'));
}
}
// Non-mutating methods:
util.createMethods(Realm.prototype, objectTypes.REALM, [
'addListener',
'removeListener',
'removeAllListeners',
'close',
]);
@ -159,6 +119,7 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [
util.createMethods(Realm.prototype, objectTypes.REALM, [
'delete',
'deleteAll',
'write',
], true);
Object.defineProperties(Realm, {

View File

@ -22,6 +22,7 @@ import * as base64 from './base64';
import { keys, objectTypes } from './constants';
const {id: idKey, realm: realmKey} = keys;
const registeredCallbacks = [];
const typeConverters = {};
let XMLHttpRequest = global.originalXMLHttpRequest || global.XMLHttpRequest;
@ -78,20 +79,16 @@ export function setProperty(realmId, id, name, value) {
sendRequest('set_property', {realmId, id, name, value});
}
export function beginTransaction(realmId) {
sendRequest('begin_transaction', {realmId});
}
export function cancelTransaction(realmId) {
sendRequest('cancel_transaction', {realmId});
}
export function commitTransaction(realmId) {
sendRequest('commit_transaction', {realmId});
}
export function clearTestState() {
sendRequest('clear_test_state');
// Clear all registered callbacks.
registeredCallbacks.length = 0;
}
function registerCallback(callback) {
let key = registeredCallbacks.indexOf(callback);
return key >= 0 ? key : (registeredCallbacks.push(callback) - 1);
}
function serialize(realmId, value) {
@ -99,7 +96,7 @@ function serialize(realmId, value) {
return {type: objectTypes.UNDEFINED};
}
if (typeof value == 'function') {
return {type: objectTypes.FUNCTION};
return {type: objectTypes.FUNCTION, value: registerCallback(value)};
}
if (!value || typeof value != 'object') {
return {value: value};
@ -189,5 +186,20 @@ 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 args = deserialize(realmId, response.arguments);
result = registeredCallbacks[callback].apply(null, args);
result = serialize(realmId, result);
} catch (e) {
error = e.message || ('' + e);
}
return sendRequest('callback_result', {callback, result, error});
}
return response.result;
}

View File

@ -46,13 +46,13 @@ export function createMethod(type, name, mutates) {
throw new TypeError(name + ' method was called on an object of the wrong type!');
}
let result = rpc.callMethod(realmId, id, name, Array.from(arguments));
if (mutates) {
fireMutationListeners(realmId);
try {
return rpc.callMethod(realmId, id, name, Array.from(arguments));
} finally {
if (mutates) {
fireMutationListeners(realmId);
}
}
return result;
};
}

View File

@ -1,7 +1,5 @@
package io.realm.react;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.facebook.react.bridge.ReactApplicationContext;
@ -19,7 +17,6 @@ import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import fi.iki.elonen.NanoHTTPD;
@ -28,7 +25,6 @@ public class RealmReactModule extends ReactContextBaseJavaModule {
private static boolean sentAnalytics = false;
private AndroidWebServer webServer;
private Handler handler = new Handler(Looper.getMainLooper());
static {
SoLoader.loadLibrary("realmreact");
@ -156,25 +152,11 @@ public class RealmReactModule extends ReactContextBaseJavaModule {
e.printStackTrace();
}
final String json = map.get("postData");
final String[] jsonResponse = new String[1];
final CountDownLatch latch = new CountDownLatch(1);
// Process the command on the UI thread
handler.post(new Runnable() {
@Override
public void run() {
jsonResponse[0] = processChromeDebugCommand(cmdUri, json);
latch.countDown();
}
});
try {
latch.await();
Response response = newFixedLengthResponse(jsonResponse[0]);
response.addHeader("Access-Control-Allow-Origin", "http://localhost:8081");
return response;
} catch (InterruptedException e) {
e.printStackTrace();
return null;
}
final String jsonResponse = processChromeDebugCommand(cmdUri, json);
Response response = newFixedLengthResponse(jsonResponse);
response.addHeader("Access-Control-Allow-Origin", "http://localhost:8081");
return response;
}
}

View File

@ -213,23 +213,24 @@ RCT_REMAP_METHOD(emit, emitEvent:(NSString *)eventName withObject:(id)object) {
[_webServer addDefaultHandlerForMethod:@"POST"
requestClass:[GCDWebServerDataRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
__typeof__(self) self = weakSelf;
RPCServer *rpcServer = self ? self->_rpcServer.get() : nullptr;
GCDWebServerResponse *response;
try {
// perform all realm ops on the main thread
__block NSData *responseData;
dispatch_sync(dispatch_get_main_queue(), ^{
RealmReact *self = weakSelf;
if (self) {
if (_rpcServer) {
json args = json::parse([[(GCDWebServerDataRequest *)request text] UTF8String]);
std::string responseText = _rpcServer->perform_request(request.path.UTF8String, args).dump();
responseData = [NSData dataWithBytes:responseText.c_str() length:responseText.length()];
return;
}
}
NSData *responseData;
if (rpcServer) {
json args = json::parse([[(GCDWebServerDataRequest *)request text] UTF8String]);
std::string responseText = rpcServer->perform_request(request.path.UTF8String, args).dump();
responseData = [NSData dataWithBytes:responseText.c_str() length:responseText.length()];
}
else {
// we have been deallocated
responseData = [NSData data];
});
}
response = [[GCDWebServerDataResponse alloc] initWithData:responseData contentType:@"application/json"];
}
catch(std::exception &ex) {

90
src/concurrent_deque.hpp Normal file
View File

@ -0,0 +1,90 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#pragma once
#include <condition_variable>
#include <deque>
#include <exception>
#include <mutex>
namespace realm {
class ConcurrentDequeTimeout : public std::exception {
public:
ConcurrentDequeTimeout() : std::exception() {}
};
template <typename T>
class ConcurrentDeque {
public:
T pop_front(size_t timeout = 0) {
std::unique_lock<std::mutex> lock(m_mutex);
while (m_deque.empty()) {
wait(lock, timeout);
}
T item = std::move(m_deque.front());
m_deque.pop_front();
return item;
}
T pop_back(size_t timeout = 0) {
std::unique_lock<std::mutex> lock(m_mutex);
while (m_deque.empty()) {
wait(lock, timeout);
}
T item = std::move(m_deque.back());
m_deque.pop_back();
return item;
}
void push_front(T&& item) {
std::unique_lock<std::mutex> lock(m_mutex);
m_deque.push_front(std::move(item));
lock.unlock();
m_condition.notify_one();
}
void push_back(T&& item) {
std::unique_lock<std::mutex> lock(m_mutex);
m_deque.push_back(std::move(item));
lock.unlock();
m_condition.notify_one();
}
bool empty() {
std::lock_guard<std::mutex> lock(m_mutex);
return m_deque.empty();
}
void wait(std::unique_lock<std::mutex> &lock, size_t timeout = 0) {
if (!timeout) {
m_condition.wait(lock);
}
else if (m_condition.wait_for(lock, std::chrono::milliseconds(timeout)) == std::cv_status::timeout) {
throw ConcurrentDequeTimeout();
}
}
private:
std::condition_variable m_condition;
std::mutex m_mutex;
std::deque<T> m_deque;
};
} // realm

View File

@ -157,6 +157,7 @@
F60103141CC4CC8C00EC01BA /* jsc_return_value.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = jsc_return_value.hpp; sourceTree = "<group>"; };
F60103151CC4CCFD00EC01BA /* node_return_value.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = node_return_value.hpp; sourceTree = "<group>"; };
F60103161CC4CD2F00EC01BA /* node_string.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = node_string.hpp; sourceTree = "<group>"; };
F6079B181CD3EB9000BD2401 /* concurrent_deque.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = concurrent_deque.hpp; sourceTree = "<group>"; };
F61378781C18EAAC008BFC51 /* js */ = {isa = PBXFileReference; lastKnownFileType = folder; path = js; sourceTree = "<group>"; };
F620F0521CAF0B600082977B /* js_class.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = js_class.hpp; sourceTree = "<group>"; };
F620F0531CAF2EF70082977B /* jsc_class.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = jsc_class.hpp; sourceTree = "<group>"; };
@ -259,6 +260,7 @@
029048351C042A3C00ABDED4 /* platform.hpp */,
0290480F1C0428DF00ABDED4 /* rpc.cpp */,
029048101C0428DF00ABDED4 /* rpc.hpp */,
F6079B181CD3EB9000BD2401 /* concurrent_deque.hpp */,
);
name = RealmJS;
path = ..;

View File

@ -253,7 +253,7 @@ struct Exception : public std::runtime_error {
const Protected<ValueType> m_value;
Exception(ContextType ctx, const std::string &message)
: std::runtime_error(message), m_value(value(ctx, message)) {}
: std::runtime_error(message), m_value(ctx, value(ctx, message)) {}
Exception(ContextType ctx, const ValueType &val)
: std::runtime_error(std::string(Value<T>::to_string(ctx, val))), m_value(ctx, val) {}

View File

@ -28,6 +28,7 @@ class Protected<JSGlobalContextRef> {
JSGlobalContextRef m_context;
public:
Protected() : m_context(nullptr) {}
Protected(const Protected<JSGlobalContextRef> &other) : Protected(other.m_context) {}
Protected(Protected<JSGlobalContextRef> &&other) : m_context(other.m_context) {
other.m_context = nullptr;
@ -43,6 +44,9 @@ class Protected<JSGlobalContextRef> {
operator JSGlobalContextRef() const {
return m_context;
}
operator bool() const {
return m_context != nullptr;
}
};
template<>
@ -51,6 +55,7 @@ class Protected<JSValueRef> {
JSValueRef m_value;
public:
Protected() {}
Protected(const Protected<JSValueRef> &other) : Protected(other.m_context, other.m_value) {}
Protected(Protected<JSValueRef> &&other) : m_context(other.m_context), m_value(other.m_value) {
other.m_context = nullptr;
@ -67,11 +72,15 @@ class Protected<JSValueRef> {
operator JSValueRef() const {
return m_value;
}
operator bool() const {
return m_value != nullptr;
}
};
template<>
class Protected<JSObjectRef> : public Protected<JSValueRef> {
public:
Protected() : Protected<JSValueRef>() {}
Protected(const Protected<JSObjectRef> &other) : Protected<JSValueRef>(other) {}
Protected(Protected<JSObjectRef> &&other) : Protected<JSValueRef>(std::move(other)) {}
Protected(JSContextRef ctx, JSObjectRef value) : Protected<JSValueRef>(ctx, value) {}

View File

@ -29,11 +29,15 @@ class Protected {
Nan::Persistent<MemberType, v8::CopyablePersistentTraits<MemberType>> m_value;
public:
Protected() {}
Protected(v8::Local<MemberType> value) : m_value(value) {}
operator v8::Local<MemberType>() const {
return Nan::New(m_value);
}
operator bool() const {
return m_value.isEmpty();
}
bool operator==(const v8::Local<MemberType> &other) const {
return m_value == other;
}
@ -55,6 +59,7 @@ namespace js {
template<>
class Protected<node::Types::GlobalContext> : public node::Protected<v8::Context> {
public:
Protected() : node::Protected<v8::Context>() {}
Protected(v8::Local<v8::Context> ctx) : node::Protected<v8::Context>(ctx) {}
operator v8::Isolate*() const {
@ -65,18 +70,21 @@ class Protected<node::Types::GlobalContext> : public node::Protected<v8::Context
template<>
class Protected<node::Types::Value> : public node::Protected<v8::Value> {
public:
Protected() : node::Protected<v8::Value>() {}
Protected(v8::Isolate* isolate, v8::Local<v8::Value> value) : node::Protected<v8::Value>(value) {}
};
template<>
class Protected<node::Types::Object> : public node::Protected<v8::Object> {
public:
Protected() : node::Protected<v8::Object>() {}
Protected(v8::Isolate* isolate, v8::Local<v8::Object> object) : node::Protected<v8::Object>(object) {}
};
template<>
class Protected<node::Types::Function> : public node::Protected<v8::Function> {
public:
Protected() : node::Protected<v8::Function>() {}
Protected(v8::Isolate* isolate, v8::Local<v8::Function> object) : node::Protected<v8::Function>(object) {}
};

View File

@ -22,9 +22,7 @@
#include <string>
#include "rpc.hpp"
#include "jsc_init.hpp"
#include "jsc_types.hpp"
#include "base64.hpp"
#include "object_accessor.hpp"
@ -43,10 +41,57 @@ static const char * const RealmObjectTypesFunction = "function";
static const char * const RealmObjectTypesList = "list";
static const char * const RealmObjectTypesObject = "object";
static const char * const RealmObjectTypesResults = "results";
static const char * const RealmObjectTypesRealm = "realm";
static const char * const RealmObjectTypesUndefined = "undefined";
static RPCServer*& get_rpc_server(JSGlobalContextRef ctx) {
static std::map<JSGlobalContextRef, RPCServer*> s_map;
return s_map[ctx];
}
RPCWorker::RPCWorker() {
m_thread = std::thread([this]() {
// TODO: Create ALooper/CFRunLoop to support async calls.
while (!m_stop) {
try_run_task();
}
});
}
RPCWorker::~RPCWorker() {
m_stop = true;
m_thread.join();
}
void RPCWorker::add_task(std::function<json()> task) {
m_tasks.push_back(std::packaged_task<json()>(task));
}
json RPCWorker::pop_task_result() {
// This might block until a future has been added.
auto future = m_futures.pop_back();
// This will block until a return value (or exception) is available.
return future.get();
}
void RPCWorker::try_run_task() {
try {
// Use a 10 millisecond timeout to keep this thread unblocked.
auto task = m_tasks.pop_back(10);
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());
}
catch (ConcurrentDequeTimeout &) {
// We tried.
}
}
RPCServer::RPCServer() {
m_context = JSGlobalContextCreate(NULL);
get_rpc_server(m_context) = this;
// JavaScriptCore crashes when trying to walk up the native stack to print the stacktrace.
// FIXME: Avoid having to do this!
@ -65,7 +110,7 @@ RPCServer::RPCServer() {
return (json){{"result", m_session_id}};
};
m_requests["/create_realm"] = [this](const json dict) {
JSObjectRef realm_constructor = m_session_id ? m_objects[m_session_id] : NULL;
JSObjectRef realm_constructor = m_session_id ? JSObjectRef(m_objects[m_session_id]) : NULL;
if (!realm_constructor) {
throw std::runtime_error("Realm constructor not found!");
}
@ -82,27 +127,6 @@ RPCServer::RPCServer() {
RPCObjectID realm_id = store_object(realm_object);
return (json){{"result", realm_id}};
};
m_requests["/begin_transaction"] = [this](const json dict) {
RPCObjectID realm_id = dict["realmId"].get<RPCObjectID>();
SharedRealm realm = *jsc::Object::get_internal<js::RealmClass<jsc::Types>>(m_objects[realm_id]);
realm->begin_transaction();
return json::object();
};
m_requests["/cancel_transaction"] = [this](const json dict) {
RPCObjectID realm_id = dict["realmId"].get<RPCObjectID>();
SharedRealm realm = *jsc::Object::get_internal<js::RealmClass<jsc::Types>>(m_objects[realm_id]);
realm->cancel_transaction();
return json::object();
};
m_requests["/commit_transaction"] = [this](const json dict) {
RPCObjectID realm_id = dict["realmId"].get<RPCObjectID>();
SharedRealm realm = *jsc::Object::get_internal<js::RealmClass<jsc::Types>>(m_objects[realm_id]);
realm->commit_transaction();
return json::object();
};
m_requests["/call_method"] = [this](const json dict) {
JSObjectRef object = m_objects[dict["id"].get<RPCObjectID>()];
std::string method_string = dict["name"].get<std::string>();
@ -148,20 +172,17 @@ RPCServer::RPCServer() {
};
m_requests["/dispose_object"] = [this](const json dict) {
RPCObjectID oid = dict["id"].get<RPCObjectID>();
JSValueUnprotect(m_context, m_objects[oid]);
m_objects.erase(oid);
return json::object();
};
m_requests["/clear_test_state"] = [this](const json dict) {
for (auto object : m_objects) {
// The session ID points to the Realm constructor object, which should remain.
if (object.first == m_session_id) {
continue;
if (object.first != m_session_id) {
m_objects.erase(object.first);
}
JSValueUnprotect(m_context, object.second);
m_objects.erase(object.first);
}
m_callbacks.clear();
JSGarbageCollect(m_context);
js::delete_all_realms();
return json::object();
@ -169,24 +190,74 @@ RPCServer::RPCServer() {
}
RPCServer::~RPCServer() {
for (auto item : m_objects) {
JSValueUnprotect(m_context, item.second);
}
get_rpc_server(m_context) = nullptr;
JSGlobalContextRelease(m_context);
}
json RPCServer::perform_request(std::string name, json &args) {
try {
void RPCServer::run_callback(JSContextRef ctx, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], jsc::ReturnValue &return_value) {
RPCServer* server = get_rpc_server(JSContextGetGlobalContext(ctx));
if (!server) {
return;
}
// The first argument was curried to be the callback id.
RPCObjectID callback_id = jsc::Value::to_number(ctx, arguments[0]);
JSObjectRef arguments_array = jsc::Object::create_array(ctx, uint32_t(argc - 1), argc == 1 ? nullptr : arguments + 1);
json arguments_json = server->serialize_json_value(arguments_array);
// The next task on the stack will instruct the JS to run this callback.
// This captures references since it will be executed before exiting this function.
server->m_worker.add_task([&]() -> json {
return {
{"callback", callback_id},
{"arguments", arguments_json},
};
});
// 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 results = server->m_callback_results.pop_back();
json error = results["error"];
// The callback id should be identical!
assert(callback_id == results["callback"].get<RPCObjectID>());
if (!error.is_null()) {
throw jsc::Exception(ctx, error.get<std::string>());
}
return_value.set(server->deserialize_json_value(results["result"]));
}
json RPCServer::perform_request(std::string name, const json &args) {
std::lock_guard<std::mutex> lock(m_request_mutex);
// Only create_session is allowed without the correct session id (since it creates the session id).
if (name != "/create_session" && m_session_id != args["sessionId"].get<RPCObjectID>()) {
return {{"error", "Invalid session ID"}};
}
// The callback_result message contains the return value (or exception) of a callback ran by run_callback().
if (name == "/callback_result") {
json results(args);
m_callback_results.push_back(std::move(results));
}
else {
RPCRequest action = m_requests[name];
assert(action);
if (name == "/create_session" || m_session_id == args["sessionId"].get<RPCObjectID>()) {
m_worker.add_task([=] {
return action(args);
}
else {
return {{"error", "Invalid session ID"}};
}
});
}
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();
} catch (std::exception &exception) {
return {{"error", exception.what()}};
}
@ -194,9 +265,9 @@ json RPCServer::perform_request(std::string name, json &args) {
RPCObjectID RPCServer::store_object(JSObjectRef object) {
static RPCObjectID s_next_id = 1;
RPCObjectID next_id = s_next_id++;
JSValueProtect(m_context, object);
m_objects[next_id] = object;
m_objects.emplace(next_id, js::Protected<JSObjectRef>(m_context, object));
return next_id;
}
@ -244,6 +315,12 @@ json RPCServer::serialize_json_value(JSValueRef js_value) {
{"schema", serialize_object_schema(results->get_object_schema())}
};
}
else if (jsc::Object::is_instance<js::RealmClass<jsc::Types>>(m_context, js_object)) {
return {
{"type", RealmObjectTypesRealm},
{"id", store_object(js_object)},
};
}
else if (jsc::Value::is_array(m_context, js_object)) {
uint32_t length = jsc::Object::validated_get_length(m_context, js_object);
std::vector<json> array;
@ -313,8 +390,20 @@ JSValueRef RPCServer::deserialize_json_value(const json dict) {
std::string type_string = type.get<std::string>();
if (type_string == RealmObjectTypesFunction) {
// FIXME: Make this actually call the function by its id once we need it to.
return JSObjectMakeFunction(m_context, NULL, 0, NULL, jsc::String(""), NULL, 1, NULL);
RPCObjectID callback_id = value.get<RPCObjectID>();
if (!m_callbacks.count(callback_id)) {
JSObjectRef callback = JSObjectMakeFunctionWithCallback(m_context, nullptr, js::wrap<run_callback>);
// Curry the first argument to be the callback id.
JSValueRef bind_args[2] = {jsc::Value::from_null(m_context), jsc::Value::from_number(m_context, callback_id)};
JSValueRef bound_callback = jsc::Object::call_method(m_context, callback, "bind", 2, bind_args);
callback = jsc::Value::to_function(m_context, bound_callback);
m_callbacks.emplace(callback_id, js::Protected<JSObjectRef>(m_context, callback));
}
return m_callbacks.at(callback_id);
}
else if (type_string == RealmObjectTypesDictionary) {
JSObjectRef js_object = jsc::Object::create_empty(m_context);

View File

@ -18,8 +18,14 @@
#pragma once
#include <functional>
#include <future>
#include <thread>
#include "concurrent_deque.hpp"
#include "json.hpp"
#include "jsc_types.hpp"
#include "jsc_protected.hpp"
namespace realm {
@ -28,20 +34,43 @@ class ObjectSchema;
namespace rpc {
using json = nlohmann::json;
using RPCObjectID = u_int64_t;
using RPCRequest = std::function<json(const json)>;
class RPCWorker {
public:
RPCWorker();
~RPCWorker();
void add_task(std::function<json()>);
json pop_task_result();
void try_run_task();
private:
bool m_stop = false;
std::thread m_thread;
ConcurrentDeque<std::packaged_task<json()>> m_tasks;
ConcurrentDeque<std::future<json>> m_futures;
};
class RPCServer {
public:
RPCServer();
~RPCServer();
json perform_request(std::string name, json &args);
json perform_request(std::string name, const json &args);
private:
JSGlobalContextRef m_context;
std::mutex m_request_mutex;
std::map<std::string, RPCRequest> m_requests;
std::map<RPCObjectID, JSObjectRef> m_objects;
std::map<RPCObjectID, js::Protected<JSObjectRef>> m_objects;
std::map<RPCObjectID, js::Protected<JSObjectRef>> m_callbacks;
ConcurrentDeque<json> m_callback_results;
RPCObjectID m_session_id;
RPCWorker m_worker;
static void run_callback(JSContextRef, JSObjectRef, size_t, const JSValueRef[], jsc::ReturnValue &);
RPCObjectID store_object(JSObjectRef object);

View File

@ -94,7 +94,7 @@ module.exports = {
}
catch (e) {
caught = true;
if (e != expectedException) {
if (e.message != expectedException.message) {
throw new TestFailureError('Expected exception "' + expectedException + '" not thrown - instead caught: "' + e + '"');
}
}

View File

@ -48,7 +48,7 @@ module.exports = BaseTest.extend({
});
// migration function exceptions should propogate
var exception = 'expected exception';
var exception = new Error('expected exception');
realm = undefined;
TestCase.assertThrowsException(function() {
realm = new Realm({schema: [], schemaVersion: 3, migration: function() {