Skip to content

Commit

Permalink
WIP on an NapiScheduler
Browse files Browse the repository at this point in the history
  • Loading branch information
kraenhansen committed Jul 19, 2024
1 parent f2da5b6 commit 2914c69
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 6 deletions.
1 change: 1 addition & 0 deletions packages/realm/bindgen/js_opt_in_spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ records:
- schema_version
- schema_mode
- disable_format_upgrade
- scheduler
- sync_config
- force_sync_history
- migration_function
Expand Down
9 changes: 7 additions & 2 deletions packages/realm/bindgen/src/templates/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import { strict as assert } from "assert";

import { TemplateContext } from "@realm/bindgen/context";
import { CppVar, CppFunc, CppFuncProps, CppCtor, CppMethod, CppClass, CppDecls } from "@realm/bindgen/cpp";
import { CppVar, CppFunc, CppFuncProps, CppCtor, CppMethod, CppClass, CppDecls, CppMemInit } from "@realm/bindgen/cpp";
import {
BoundSpec,
Class,
Expand Down Expand Up @@ -83,6 +83,7 @@ class NodeAddon extends CppClass {
this.withCrtpBase("Napi::Addon");

this.members.push(new CppVar("std::deque<std::string>", "m_string_bufs"));
this.members.push(new CppVar("std::shared_ptr<NapiScheduler>", "m_scheduler"));
this.addMethod(
new CppMethod("wrapString", "const std::string&", [new CppVar("std::string", "str")], {
attributes: "inline",
Expand Down Expand Up @@ -124,6 +125,7 @@ class NodeAddon extends CppClass {

this.addMethod(
new CppCtor(this.name, [new CppVar("Napi::Env", env), new CppVar("Napi::Object", "exports")], {
mem_inits: [new CppMemInit("m_scheduler", `std::make_shared<NapiScheduler>(${env})`)],
body: `
DefineAddon(exports, {
${Object.entries(this.exports)
Expand Down Expand Up @@ -585,7 +587,9 @@ function convertFromNode(addon: NodeAddon, type: Type, expr: string): string {
// For now assuming that all void-returning functions are "notifications" and don't need to block until done.
// Non-void returning functions *must* block so they have something to return.
const shouldBlock = !type.ret.isVoid();
return shouldBlock ? `schedulerWrapBlockingFunction(${lambda})` : `util::EventLoopDispatcher(${lambda})`;
return shouldBlock
? `schedulerWrapBlockingFunction(${lambda}, ${env}.GetInstanceData<RealmAddon>()->m_scheduler)`
: `util::EventLoopDispatcher(${lambda}, ${env}.GetInstanceData<RealmAddon>()->m_scheduler)`;

case "Enum":
return `${type.cppName}((${expr}).As<Napi::Number>().DoubleValue())`;
Expand Down Expand Up @@ -936,6 +940,7 @@ export function generate({ rawSpec, spec, file: makeFile }: TemplateContext): vo
#include <napi.h>
#include <realm_helpers.h>
#include <realm_js_node_helpers.h>
#include <napi_scheduler.h>
namespace realm::js::node {
namespace {
Expand Down
2 changes: 1 addition & 1 deletion packages/realm/bindgen/vendor/realm-core
Submodule realm-core updated 120 files
5 changes: 2 additions & 3 deletions packages/realm/binding/node/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ else()
target_compile_options(realm-js PRIVATE -Wall -Wextra)
endif()

target_include_directories(realm-js PRIVATE "${BINDGEN_DIR}/src")
target_include_directories(realm-js PRIVATE "${BINDING_DIR}")
target_include_directories(realm-js PRIVATE "${BINDGEN_DIR}/src" "${BINDING_DIR}" "${BINDING_DIR}/node")


file(GLOB_RECURSE SDK_TS_FILES
Expand Down Expand Up @@ -137,4 +136,4 @@ bindgen(
SOURCES ${SDK_TS_FILES}
)

target_sources(realm-js PRIVATE node_init.cpp ${CMAKE_JS_SRC} ${BINDING_DIR}/node/platform.cpp)
target_sources(realm-js PRIVATE node_init.cpp ${CMAKE_JS_SRC} ${BINDING_DIR}/node/platform.cpp ${BINDING_DIR}/node/napi_scheduler.cpp)
79 changes: 79 additions & 0 deletions packages/realm/binding/node/napi_scheduler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024 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.
//
////////////////////////////////////////////////////////////////////////////

#include "napi_scheduler.h"

#include <realm/util/functional.hpp>
#include <realm/object-store/util/scheduler.hpp>

#include <napi.h>

#include <functional>
#include <memory>
#include <type_traits>

namespace realm::js::node {

namespace {
/**
* Assumes called exactly once per data value:
* An absent call results in a leak and multiple calls result in use-after-free.
*/
void call_func_from_data(Napi::Env, Napi::Function, std::nullptr_t*, VoidUniqueFunctionImpl* data)
{
(realm::util::UniqueFunction<void()>(data))();
}

/**
* A NAPI thread-safe function which use the data to construct and call a `UniqueFunction`:
* Simpler and faster than passing and calling a `Napi::Function` to `NonBlockingCall`.
*/
using SchedulerThreadSafeFunction =
Napi::TypedThreadSafeFunction<std::nullptr_t, VoidUniqueFunctionImpl, &call_func_from_data>;

} // namespace

NapiScheduler::NapiScheduler(Napi::Env& env)
: m_env(env)
// TODO: Consider including an id from the env in the resource name
, m_tsf(SchedulerThreadSafeFunction::New(env, "realm::NapiScheduler", 0, 1))
{
}

bool NapiScheduler::is_on_thread() const noexcept
{
return false;
}

bool NapiScheduler::is_same_as(const Scheduler* other) const noexcept
{
auto o = dynamic_cast<const NapiScheduler*>(other);
return (o && (o->m_env == m_env));
}

bool NapiScheduler::can_invoke() const noexcept
{
return true;
}

void NapiScheduler::invoke(realm::util::UniqueFunction<void()>&& func)
{
m_tsf.NonBlockingCall(func.release());
}

} // namespace realm::js::node
43 changes: 43 additions & 0 deletions packages/realm/binding/node/napi_scheduler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024 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 <realm/util/functional.hpp>
#include <realm/object-store/util/scheduler.hpp>

#include <napi.h>

namespace realm::js::node {

using VoidUniqueFunctionImpl = std::remove_pointer_t<decltype(realm::util::UniqueFunction<void()>().release())>;

class NapiScheduler : public realm::util::Scheduler {
public:
NapiScheduler(Napi::Env& env);
bool is_on_thread() const noexcept override;
bool is_same_as(const Scheduler* other) const noexcept override;
bool can_invoke() const noexcept override;
void invoke(realm::util::UniqueFunction<void()>&& func) override;

private:
Napi::Env m_env;
Napi::TypedThreadSafeFunction<std::nullptr_t, VoidUniqueFunctionImpl> m_tsf;
};

} // namespace realm::js::node

0 comments on commit 2914c69

Please sign in to comment.