diff --git a/.github/workflows/vcpkg.yml b/.github/workflows/vcpkg.yml index 2caa6909b..c3b8394ba 100644 --- a/.github/workflows/vcpkg.yml +++ b/.github/workflows/vcpkg.yml @@ -21,10 +21,8 @@ jobs: with: submodules: 'recursive' - - name: "Set environmental variables" - shell: bash - run: | - echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" >> $GITHUB_ENV + - uses: friendlyanon/setup-vcpkg@v1 + with: { committish: 6e31ee33cc9fc93599c4ceb38e229098cf339bb7 } - name: Install dependencies if: matrix.os == 'ubuntu-latest' @@ -41,7 +39,7 @@ jobs: working-directory: ./tests if: matrix.os == 'macos-latest' run: | - cmake -GNinja -B build -S . -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_OVERLAY_PORTS="../ports" -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DVCPKG_TARGET_TRIPLET=x64-osx + cmake -GNinja -B build -S . -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_OVERLAY_PORTS="../ports" -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DVCPKG_TARGET_TRIPLET=arm64-osx cd build cmake --build . --config ${{matrix.build_type}} @@ -88,10 +86,8 @@ jobs: with: submodules: 'recursive' - - name: "Set environmental variables" - shell: bash - run: | - echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" >> $GITHUB_ENV + - uses: friendlyanon/setup-vcpkg@v1 + with: { committish: 6e31ee33cc9fc93599c4ceb38e229098cf339bb7 } - name: Install dependencies if: matrix.os == 'ubuntu-latest' @@ -108,7 +104,7 @@ jobs: working-directory: ./tests if: matrix.os == 'macos-latest' run: | - cmake -GNinja -B build -S . -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_OVERLAY_PORTS="../ports" -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DVCPKG_TARGET_TRIPLET=x64-osx-dynamic + cmake -GNinja -B build -S . -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_OVERLAY_PORTS="../ports" -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DVCPKG_TARGET_TRIPLET=arm64-osx-dynamic cd build cmake --build . --config ${{matrix.build_type}} diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cd80caf6..e437afc15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,12 @@ X.Y.Z Release notes (YYYY-MM-DD) ============================================================= ### Fixed -* Fixed a compilation issue seen in MSBuild 17.10.4 due to usage of `std::apply`. +* Fixed a compilation issue seen in MSVC 19.40.33811 due to usage of `std::apply`. ### Enhancements -* None +* Add `realm::default_scheduler::set_default_factory(std::function()>&& factory_fn)` for generating a default scheduler. + Set your scheduler factory before instantiating a `realm::db_config`. +* Add `realm::default_scheduler::make_default()` which generates a platform default scheduler if `realm::default_scheduler::set_default_factory` is not set. ### Compatibility * Fileformat: Generates files with format v24. Reads and automatically upgrade from fileformat v10. diff --git a/CMakeLists.txt b/CMakeLists.txt index c73b8920a..d6abfe709 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,12 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Linux") set(REALM_USE_SYSTEM_OPENSSL ON) endif() -add_subdirectory(realm-core) + +if (VCPKG_TOOLCHAIN) + find_package(Realm REQUIRED) +else() + add_subdirectory(realm-core) +endif() set(REALM_NO_CONFIG) set(REALM_ENABLE_SYNC) @@ -49,6 +54,13 @@ add_compile_definitions(REALM_ENABLE_ENCRYPTION) add_compile_definitions(REALM_INSTALL_LIBEXECDIR) add_compile_definitions(REALM_BUILD_LIB_ONLY) +# on Apple platforms we use the built-in CFRunLoop +# everywhere else it's libuv, except UWP where it doesn't build +if(NOT APPLE AND NOT WINDOWS_STORE AND NOT ANDROID) + set(REALM_HAVE_UV 1) + add_compile_definitions(REALM_HAVE_UV=1) +endif() + configure_file(src/cpprealm/util/config.h.in include/cpprealm/util/config.h) add_subdirectory(src) @@ -65,9 +77,7 @@ target_include_directories(${PROJECT_NAME} $ ) -# on Apple platforms we use the built-in CFRunLoop -# everywhere else it's libuv, except UWP where it doesn't build -if(NOT APPLE AND NOT WINDOWS_STORE AND NOT ANDROID) +if(REALM_HAVE_UV) if(VCPKG_TOOLCHAIN) find_package(libuv CONFIG REQUIRED) if(BUILD_SHARED_LIBS) @@ -102,9 +112,7 @@ if(NOT APPLE AND NOT WINDOWS_STORE AND NOT ANDROID) endif() endif() - target_compile_definitions(ObjectStore PUBLIC REALM_HAVE_UV=1) target_link_libraries(cpprealm PUBLIC ${libuv_target}) - target_link_libraries(ObjectStore PUBLIC ${libuv_target}) endif() target_link_libraries(cpprealm PUBLIC Realm::ObjectStore) diff --git a/conanfile.py b/conanfile.py index cb6d40291..039099196 100644 --- a/conanfile.py +++ b/conanfile.py @@ -36,7 +36,7 @@ def source(self): git = Git(self) git.clone(url="https://github.com/realm/realm-cpp", target=".") git.folder = "." - git.checkout(commit="70ccfd7254b8b76fe63119b9a12528cf7e84ca69") + git.checkout(commit="fd20cf3d9f226dac8a21c89300728ac9be8bd2e2") git.run("submodule update --init --recursive") def layout(self): diff --git a/include/cpprealm/internal/bridge/realm.hpp b/include/cpprealm/internal/bridge/realm.hpp index 927221e9e..38703d1e7 100644 --- a/include/cpprealm/internal/bridge/realm.hpp +++ b/include/cpprealm/internal/bridge/realm.hpp @@ -19,13 +19,14 @@ #ifndef CPPREALM_BRIDGE_REALM_HPP #define CPPREALM_BRIDGE_REALM_HPP +#include + #include #include #include #include #include #include -#include namespace realm { class Realm; @@ -176,8 +177,8 @@ namespace realm::internal::bridge { manual }; config(); - config(const config& other) ; - config& operator=(const config& other) ; + config(const config& other); + config& operator=(const config& other); config(config&& other); config& operator=(config&& other); ~config(); @@ -186,7 +187,7 @@ namespace realm::internal::bridge { const std::shared_ptr& scheduler); [[nodiscard]] std::string path() const; [[nodiscard]] struct sync_config sync_config() const; - [[nodiscard]] std::shared_ptr scheduler(); + [[nodiscard]] std::shared_ptr scheduler() const; operator RealmConfig() const; //NOLINT(google-explicit-constructor) void set_path(const std::string&); void set_schema(const std::vector&); diff --git a/include/cpprealm/internal/scheduler/realm_core_scheduler.hpp b/include/cpprealm/internal/scheduler/realm_core_scheduler.hpp new file mode 100644 index 000000000..a3c54157f --- /dev/null +++ b/include/cpprealm/internal/scheduler/realm_core_scheduler.hpp @@ -0,0 +1,74 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef CPPREALM_REALM_CORE_SCHEDULER_HPP +#define CPPREALM_REALM_CORE_SCHEDULER_HPP + +#include + +namespace realm { + namespace util { + class Scheduler; + } +} +namespace realm::internal { + + /** + * A type erased scheduler used for wrapping default scheduler implementations from RealmCore. + */ + struct realm_core_scheduler final : public scheduler { + /** + * Invoke the given function on the scheduler's thread. + * This function can be called from any thread. + */ + void invoke(std::function &&fn) final; + + /** + * Check if the caller is currently running on the scheduler's thread. + * This function can be called from any thread. + */ + [[nodiscard]] bool is_on_thread() const noexcept final; + + /** + * Checks if this scheduler instance wraps the same underlying instance. + * This is up to the platforms to define, but if this method returns true, + * caching may occur. + */ + bool is_same_as(const scheduler *other) const noexcept final; + + /** + * Check if this scheduler actually can support invoke(). Invoking may be + * either not implemented, not applicable to a scheduler type, or simply not + * be possible currently (e.g. if the associated event loop is not actually + * running). + * + * This function is not thread-safe. + */ + [[nodiscard]] bool can_invoke() const noexcept final; + ~realm_core_scheduler() final = default; + realm_core_scheduler() = delete; + explicit realm_core_scheduler(std::shared_ptr s) : s(std::move(s)) {} + operator std::shared_ptr(); + private: + std::shared_ptr s; + }; + + std::shared_ptr create_scheduler_shim(const std::shared_ptr& s); +} // namespace realm::internal + +#endif//CPPREALM_REALM_CORE_SCHEDULER_HPP diff --git a/include/cpprealm/schedulers/default_scheduler.hpp b/include/cpprealm/schedulers/default_scheduler.hpp new file mode 100644 index 000000000..2b7c0b3b9 --- /dev/null +++ b/include/cpprealm/schedulers/default_scheduler.hpp @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef CPPREALM_DEFAULT_SCHEDULERS_HPP +#define CPPREALM_DEFAULT_SCHEDULERS_HPP + +#include + +#if __has_include() +#include +#endif + +#if defined(REALM_HAVE_UV) && REALM_HAVE_UV +typedef struct uv_loop_s uv_loop_t; +#endif + +namespace realm::default_scheduler { + /** + * Tries to choose a built in scheduler as default for the platform + * Current options are: + * - CFRunLoop for Apple platforms + * - UV for Linux and Windows + * - ALooper for Android + * If no suitable scheduler is available a generic scheduler will be provided. + */ + std::shared_ptr make_platform_default(); + + /** + * Register a factory function which can produce custom schedulers when + * `scheduler::make_default()` is called. This function is not thread-safe + * and must be called before any schedulers are created. + */ + void set_default_factory(std::function()>&& factory_fn); + + /** + * Create a new instance of the scheduler type returned by the default + * scheduler factory. By default, the factory function is + * `scheduler::make_platform_default()`. + */ + std::shared_ptr make_default(); +} // namespace realm + +#endif//CPPREALM_DEFAULT_SCHEDULERS_HPP diff --git a/include/cpprealm/sdk.hpp b/include/cpprealm/sdk.hpp index cc2b38c0d..f374094b6 100644 --- a/include/cpprealm/sdk.hpp +++ b/include/cpprealm/sdk.hpp @@ -19,8 +19,8 @@ #ifndef CPPREALM_SDK_HPP #define CPPREALM_SDK_HPP -#if __has_include() -#include +#if __has_include() +#include #endif #include diff --git a/ports/portfile.cmake b/ports/portfile.cmake index 8f854ad66..e5ac0c94a 100644 --- a/ports/portfile.cmake +++ b/ports/portfile.cmake @@ -8,7 +8,7 @@ vcpkg_execute_required_process( LOGNAME submodules ) -set(CPPREALM_CMAKE_OPTIONS -DREALM_CPP_NO_TESTS=ON -DREALM_CORE_SUBMODULE_BUILD=OFF) +set(CPPREALM_CMAKE_OPTIONS -DREALM_CPP_NO_TESTS=ON -DREALM_ENABLE_EXPERIMENTAL=ON) if (ANDROID OR WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux") list(APPEND CPPREALM_CMAKE_OPTIONS -DREALM_USE_SYSTEM_OPENSSL=ON) @@ -24,7 +24,6 @@ vcpkg_cmake_configure( vcpkg_cmake_install() vcpkg_cmake_config_fixup(PACKAGE_NAME "cpprealm" CONFIG_PATH "cmake") -vcpkg_cmake_config_fixup(PACKAGE_NAME "Realm" CONFIG_PATH "share/cmake/Realm") file(READ ${CURRENT_PACKAGES_DIR}/debug/include/cpprealm/internal/bridge/bridge_types.hpp DEBUG_TYPE_HEADER_CONTENTS) set(REGEX_PATTERN "\\{([^()]*)\\}") @@ -47,11 +46,7 @@ string(REGEX REPLACE "\\{([^()]*)\\}" " " MODIFIED_HEADER "${DEBUG_TYPE_HEADER_CONTENTS}") file(WRITE ${CURRENT_PACKAGES_DIR}/include/cpprealm/internal/bridge/bridge_types.hpp "${MODIFIED_HEADER}") - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" -) +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE") diff --git a/ports/vcpkg.json b/ports/vcpkg.json index 7ddf31cf3..3985507bf 100644 --- a/ports/vcpkg.json +++ b/ports/vcpkg.json @@ -4,6 +4,7 @@ "description": "Realm is a mobile database that runs directly inside phones, tablets or wearables.", "homepage": "https://github.com/realm/realm-cpp", "license": "Apache-2.0", + "supports": "!emscripten", "dependencies": [ { "name": "vcpkg-cmake", @@ -24,14 +25,8 @@ "platform": "linux" }, { - "name": "openssl", - "version>=": "3.2.0", - "platform": "!osx" - }, - { - "name": "zlib", - "version>=": "1.3", - "platform": "!osx, !emscripten" + "name": "realm-core", + "version>=": "14.8.0" } ] } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 75dc40b8f..246f47bf3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -66,6 +66,8 @@ set(SOURCES cpprealm/internal/bridge/thread_safe_reference.cpp cpprealm/internal/bridge/timestamp.cpp cpprealm/internal/bridge/uuid.cpp + cpprealm/internal/scheduler/realm_core_scheduler.cpp + cpprealm/schedulers/default_scheduler.cpp cpprealm/logger.cpp cpprealm/sdk.cpp) # REALM_SOURCES @@ -124,6 +126,8 @@ set(HEADERS ../include/cpprealm/internal/bridge/uuid.hpp ../include/cpprealm/internal/generic_network_transport.hpp ../include/cpprealm/internal/type_info.hpp + ../include/cpprealm/internal/scheduler/realm_core_scheduler.hpp + ../include/cpprealm/schedulers/default_scheduler.hpp ../include/cpprealm/logger.hpp ../include/cpprealm/notifications.hpp ../include/cpprealm/rbool.hpp @@ -136,7 +140,11 @@ include(GNUInstallDirs) add_library(cpprealm ${SOURCES} ${HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/cpprealm/internal/bridge/bridge_types.hpp) -install(TARGETS ${PROJECT_NAME} ObjectStore Storage QueryParser Sync +if (NOT VCPKG_TOOLCHAIN) + set(INSTALL_TARGETS ObjectStore Storage QueryParser Sync) +endif() + +install(TARGETS ${PROJECT_NAME} ${INSTALL_TARGETS} EXPORT "${PROJECT_NAME}Targets" INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} # include ) diff --git a/src/cpprealm/internal/bridge/realm.cpp b/src/cpprealm/internal/bridge/realm.cpp index fd26002ac..845b4985a 100644 --- a/src/cpprealm/internal/bridge/realm.cpp +++ b/src/cpprealm/internal/bridge/realm.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -12,8 +13,8 @@ #include #include #include -#include -#include +#include +#include #include #include @@ -32,63 +33,6 @@ #include #endif -#ifdef QT_CORE_LIB -#include -#include -#include -#include -#include -#endif - -namespace realm { -#if QT_CORE_LIB -struct QtMainLoopScheduler : public QObject, public util::Scheduler { - - bool is_on_thread() const noexcept override - { - return m_id == std::this_thread::get_id(); - } - bool is_same_as(const Scheduler* other) const noexcept override - { - auto o = dynamic_cast(other); - return (o && (o->m_id == m_id)); - } - bool can_deliver_notifications() const noexcept override - { - return QThread::currentThread()->eventDispatcher(); - } - - void set_notify_callback(Function fn) override - { - m_callback = std::move(fn); - } - - void notify() override - { - schedule(m_callback); - } - - void schedule(Function fn) { - QMetaObject::invokeMethod(this, fn); - } -private: - Function m_callback; - std::thread::id m_id = std::this_thread::get_id(); -}; -static std::shared_ptr make_qt() -{ - return std::make_shared(); -} -#endif - - std::shared_ptr make_default_scheduler() { -#if QT_CORE_LIB - util::Scheduler::set_default_factory(make_qt); -#endif - return util::Scheduler::make_default(); - } -} - namespace realm::internal::bridge { static_assert((uint8_t)realm::config::schema_mode::automatic == (uint8_t)::realm::SchemaMode::Automatic); static_assert((uint8_t)realm::config::schema_mode::immutable == (uint8_t)::realm::SchemaMode::Immutable); @@ -126,42 +70,11 @@ namespace realm::internal::bridge { m_realm->commit_transaction(); } - struct internal_scheduler : util::Scheduler { - internal_scheduler(const std::shared_ptr& s) - : m_scheduler(s) - { - } - - ~internal_scheduler() override = default; - void invoke(util::UniqueFunction &&fn) override { - m_scheduler->invoke([fn = std::move(fn.release())]() { - auto f = util::UniqueFunction(std::move(fn)); - f(); - }); - } - - bool is_on_thread() const noexcept override { - return m_scheduler->is_on_thread(); - } - bool is_same_as(const util::Scheduler *other) const noexcept override { - if (auto o = dynamic_cast(other)) { - return m_scheduler->is_same_as(o->m_scheduler.get()); - } - return false; - } - - bool can_invoke() const noexcept override { - return m_scheduler->can_invoke(); - } - private: - std::shared_ptr m_scheduler; - }; - realm::realm(thread_safe_reference&& tsr, const std::optional>& s) { if (s) { - m_realm = Realm::get_shared_realm(std::move(tsr), std::make_shared(*s)); + m_realm = Realm::get_shared_realm(std::move(tsr), create_scheduler_shim(*s)); } else { - m_realm = Realm::get_shared_realm(std::move(tsr)); + m_realm = Realm::get_shared_realm(std::move(tsr), create_scheduler_shim(default_scheduler::make_default())); } } @@ -174,13 +87,10 @@ namespace realm::internal::bridge { std::string path = cwd; path.append("/default.realm"); config.path = path; - - config.scheduler = ::realm::util::Scheduler::make_generic(); #else config.path = std::filesystem::current_path().append("default.realm").generic_string(); - config.scheduler = ::realm::make_default_scheduler(); #endif - + config.scheduler = create_scheduler_shim(default_scheduler::make_default()); config.schema_version = 0; #ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES new (&m_config) RealmConfig(config); @@ -245,7 +155,7 @@ namespace realm::internal::bridge { RealmConfig config; config.cache = true; config.path = path; - config.scheduler = std::make_shared(scheduler); + config.scheduler = create_scheduler_shim(scheduler); config.schema_version = 0; #ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES new (&m_config) RealmConfig(config); @@ -351,7 +261,11 @@ namespace realm::internal::bridge { #endif } void realm::config::set_scheduler(const std::shared_ptr &s) { - get_config()->scheduler = std::make_shared(s); + if (auto core_scheduler = dynamic_cast(s.get())) { + get_config()->scheduler = core_scheduler->operator std::shared_ptr(); + return; + } + get_config()->scheduler = create_scheduler_shim(s); } void realm::config::set_sync_config(const std::optional &s) { if (s) @@ -411,7 +325,7 @@ namespace realm::internal::bridge { void realm::config::after_client_reset(std::function callback) { get_config()->sync_config->notify_after_client_reset = [cb = std::move(callback)](::realm::SharedRealm local, ::realm::ThreadSafeReference remote, bool) { - cb(realm(local), realm(::realm::Realm::get_shared_realm(std::move(remote)))); + cb(realm(local), realm(::realm::Realm::get_shared_realm(std::move(remote), create_scheduler_shim(default_scheduler::make_default())))); }; } @@ -419,44 +333,12 @@ namespace realm::internal::bridge { return get_config()->sync_config; } - struct external_scheduler final : public scheduler { - // Invoke the given function on the scheduler's thread. - // - // This function can be called from any thread. - void invoke(std::function &&fn) final { - s->invoke(std::move(fn)); - } - - // Check if the caller is currently running on the scheduler's thread. - // - // This function can be called from any thread. - [[nodiscard]] bool is_on_thread() const noexcept final { - return s->is_on_thread(); - } - - // Checks if this scheduler instance wraps the same underlying instance. - // This is up to the platforms to define, but if this method returns true, - // caching may occur. - bool is_same_as(const scheduler *other) const noexcept final { - return this == other; - } - - // Check if this scheduler actually can support invoke(). Invoking may be - // either not implemented, not applicable to a scheduler type, or simply not - // be possible currently (e.g. if the associated event loop is not actually - // running). - // - // This function is not thread-safe. - [[nodiscard]] bool can_invoke() const noexcept final { - return s->can_invoke(); - } - std::shared_ptr s; - external_scheduler(std::shared_ptr s) : s(std::move(s)) {} - ~external_scheduler() final = default; - }; + struct std::shared_ptr realm::config::scheduler() const { + return std::make_shared(realm_core_scheduler(get_config()->scheduler)); + } struct std::shared_ptr realm::scheduler() const { - return std::make_shared(external_scheduler(m_realm->scheduler())); + return std::make_shared(realm_core_scheduler(m_realm->scheduler())); } async_open_task realm::get_synchronized_realm(const config &c) { @@ -506,7 +388,7 @@ namespace realm::internal::bridge { return *this; auto config = m_realm->config(); config.cache = true; - config.scheduler = ::realm::make_default_scheduler(); + config.scheduler = create_scheduler_shim(default_scheduler::make_default()); return realm(std::move(config)); } diff --git a/src/cpprealm/internal/scheduler/realm_core_scheduler.cpp b/src/cpprealm/internal/scheduler/realm_core_scheduler.cpp new file mode 100644 index 000000000..00d1804c1 --- /dev/null +++ b/src/cpprealm/internal/scheduler/realm_core_scheduler.cpp @@ -0,0 +1,63 @@ +#include + +#include + +namespace realm::internal { + void realm_core_scheduler::invoke(std::function &&fn) { + s->invoke(std::move(fn)); + } + + [[nodiscard]] bool realm_core_scheduler::is_on_thread() const noexcept { + return s->is_on_thread(); + } + + bool realm_core_scheduler::is_same_as(const scheduler *other) const noexcept { + if (auto o = dynamic_cast(other)) { + return o->s->is_same_as(this->s.get()); + } + return false; + } + + [[nodiscard]] bool realm_core_scheduler::can_invoke() const noexcept { + return s->can_invoke(); + } + + realm_core_scheduler::operator std::shared_ptr() { + return s; + } + + std::shared_ptr create_scheduler_shim(const std::shared_ptr& s) { + struct internal_scheduler : util::Scheduler { + internal_scheduler(const std::shared_ptr& s) + : m_scheduler(s) + { + } + + ~internal_scheduler() override = default; + void invoke(util::UniqueFunction &&fn) override { + m_scheduler->invoke([fn = std::move(fn.release())]() { + auto f = util::UniqueFunction(std::move(fn)); + f(); + }); + } + + bool is_on_thread() const noexcept override { + return m_scheduler->is_on_thread(); + } + bool is_same_as(const util::Scheduler *other) const noexcept override { + if (auto o = dynamic_cast(other)) { + return m_scheduler->is_same_as(o->m_scheduler.get()); + } + return false; + } + + bool can_invoke() const noexcept override { + return m_scheduler->can_invoke(); + } + private: + std::shared_ptr m_scheduler; + }; + + return std::make_shared(s); + } +} \ No newline at end of file diff --git a/src/cpprealm/schedulers/default_scheduler.cpp b/src/cpprealm/schedulers/default_scheduler.cpp new file mode 100644 index 000000000..bff1c035a --- /dev/null +++ b/src/cpprealm/schedulers/default_scheduler.cpp @@ -0,0 +1,129 @@ +#include +#include + +#include + +#if defined(REALM_HAVE_UV) && REALM_HAVE_UV +#include +#endif + +namespace realm::default_scheduler { +#if defined(REALM_HAVE_UV) && REALM_HAVE_UV + std::shared_ptr make_uv(); +#endif + + std::function()> s_factory = make_platform_default; + + std::shared_ptr make_platform_default() { +#if REALM_PLATFORM_APPLE || REALM_ANDROID && !defined(REALM_AOSP_VENDOR) + return std::make_shared(util::Scheduler::make_platform_default()); +#elif defined(REALM_HAVE_UV) && REALM_HAVE_UV + return make_uv(); +#else + return std::make_shared(util::Scheduler::make_generic()); +#endif + } + + void set_default_factory(std::function()>&& factory_fn) { + s_factory = std::move(factory_fn); + } + + std::shared_ptr make_default() + { + return s_factory(); + } + +#if defined(REALM_HAVE_UV) && REALM_HAVE_UV + class invocation_queue { + public: + void push(std::function&&); + void invoke_all(); + + private: + std::mutex m_mutex; + std::vector> m_functions; + }; + + void invocation_queue::push(std::function&& fn) + { + std::lock_guard lock(m_mutex); + m_functions.push_back(std::move(fn)); + } + + void invocation_queue::invoke_all() + { + std::vector> functions; + { + std::lock_guard lock(m_mutex); + functions.swap(m_functions); + } + for (auto&& fn : functions) { + fn(); + } + } + + class uv_scheduler final : public realm::scheduler { + public: + uv_scheduler() + : m_handle(std::make_unique()) { + int err = uv_async_init(uv_default_loop(), m_handle.get(), [](uv_async_t *handle) { + if (!handle->data) { + return; + } + auto &data = *static_cast(handle->data); + if (data.close_requested) { + uv_close(reinterpret_cast(handle), [](uv_handle_t *handle) { + delete reinterpret_cast(handle->data); + delete reinterpret_cast(handle); + }); + } else { + data.queue.invoke_all(); + } + }); + if (err < 0) { + throw std::runtime_error("uv_async_init failed"); + } + m_handle->data = new data; + } + + ~uv_scheduler() { + if (m_handle && m_handle->data) { + static_cast(m_handle->data)->close_requested = true; + uv_async_send(m_handle.get()); + // Don't delete anything here as we need to delete it from within the event loop instead + m_handle.release(); + } + } + + bool is_on_thread() const noexcept override { + return m_id == std::this_thread::get_id(); + } + bool is_same_as(const scheduler *other) const noexcept override { + auto o = dynamic_cast(other); + return (o && (o->m_id == m_id)); + } + bool can_invoke() const noexcept override { + return true; + } + + void invoke(std::function &&fn) override { + auto &data = *static_cast(m_handle->data); + data.queue.push(std::move(fn)); + uv_async_send(m_handle.get()); + } + + private: + struct data { + invocation_queue queue; + std::atomic close_requested = {false}; + }; + std::unique_ptr m_handle; + std::thread::id m_id = std::this_thread::get_id(); + }; + + std::shared_ptr make_uv() { + return std::make_shared(); + } +#endif + +} \ No newline at end of file diff --git a/src/cpprealm/util/config.h.in b/src/cpprealm/util/config.h.in index e900e82a9..5ed92b2e7 100644 --- a/src/cpprealm/util/config.h.in +++ b/src/cpprealm/util/config.h.in @@ -1 +1,2 @@ -#cmakedefine01 REALM_ENABLE_EXPERIMENTAL \ No newline at end of file +#cmakedefine01 REALM_ENABLE_EXPERIMENTAL +#cmakedefine01 REALM_HAVE_UV \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7d8fe84c0..f6c6f2e13 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -47,7 +47,7 @@ add_executable(cpprealm_db_tests db/query_tests.cpp db/realm_tests.cpp db/results_tests.cpp - db/run_loop_tests.cpp + db/scheduler_tests.cpp db/string_tests.cpp db/performance_tests.cpp db/numeric_tests.cpp diff --git a/tests/db/run_loop_tests.cpp b/tests/db/run_loop_tests.cpp deleted file mode 100644 index 465adad8e..000000000 --- a/tests/db/run_loop_tests.cpp +++ /dev/null @@ -1,498 +0,0 @@ -#include "../main.hpp" -#include "test_objects.hpp" -#include -#include - -#include - -#if REALM_HAVE_UV -#include -#elif REALM_PLATFORM_APPLE -#include "realm/util/cf_ptr.hpp" -#include -#else -#error "No EventLoop implementation selected, tests will fail" -#endif - -class InvocationQueue { -public: - void push(std::function&&); - void invoke_all(); - -private: - std::mutex m_mutex; - std::vector> m_functions; -}; - -void InvocationQueue::push(std::function&& fn) -{ - std::lock_guard lock(m_mutex); - m_functions.push_back(std::move(fn)); -} - -void InvocationQueue::invoke_all() -{ - std::vector> functions; - { - std::lock_guard lock(m_mutex); - functions.swap(m_functions); - } - for (auto&& fn : functions) { - fn(); - } -} - -#if REALM_PLATFORM_APPLE - -struct RefCountedInvocationQueue { - InvocationQueue queue; - std::atomic ref_count = {0}; -}; - -class RunLoopScheduler : public realm::scheduler { -public: - RunLoopScheduler(CFRunLoopRef run_loop = nullptr); - ~RunLoopScheduler(); - - void invoke(std::function&&) override; - - bool is_on_thread() const noexcept override; - bool is_same_as(const scheduler* other) const noexcept override; - bool can_invoke() const noexcept override; - -private: - CFRunLoopRef m_runloop; - CFRunLoopSourceRef m_notify_signal = nullptr; - RefCountedInvocationQueue& m_queue; - - void release(CFRunLoopSourceRef&); - void set_callback(CFRunLoopSourceRef&, std::function); -}; - -RunLoopScheduler::RunLoopScheduler(CFRunLoopRef run_loop) - : m_runloop(run_loop ?: CFRunLoopGetCurrent()) - , m_queue(*new RefCountedInvocationQueue) -{ - CFRetain(m_runloop); - - CFRunLoopSourceContext ctx{}; - ctx.info = &m_queue; - ctx.perform = [](void* info) { - static_cast(info)->queue.invoke_all(); - }; - ctx.retain = [](const void* info) { - static_cast(const_cast(info)) - ->ref_count.fetch_add(1, std::memory_order_relaxed); - return info; - }; - ctx.release = [](const void* info) { - auto ptr = static_cast(const_cast(info)); - if (ptr->ref_count.fetch_add(-1, std::memory_order_acq_rel) == 1) { - delete ptr; - } - }; - - m_notify_signal = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &ctx); - CFRunLoopAddSource(m_runloop, m_notify_signal, kCFRunLoopDefaultMode); -} - -RunLoopScheduler::~RunLoopScheduler() -{ - CFRunLoopSourceInvalidate(m_notify_signal); - CFRelease(m_notify_signal); - CFRelease(m_runloop); -} - -void RunLoopScheduler::invoke(std::function&& fn) -{ - m_queue.queue.push(std::move(fn)); - - CFRunLoopSourceSignal(m_notify_signal); - // Signalling the source makes it run the next time the runloop gets - // to it, but doesn't make the runloop start if it's currently idle - // waiting for events - CFRunLoopWakeUp(m_runloop); -} - -bool RunLoopScheduler::is_on_thread() const noexcept -{ - return CFRunLoopGetCurrent() == m_runloop; -} - -bool RunLoopScheduler::is_same_as(const scheduler* other) const noexcept -{ - auto o = dynamic_cast(other); - return (o && (o->m_runloop == m_runloop)); -} - -bool RunLoopScheduler::can_invoke() const noexcept -{ - if (pthread_main_np()) - return true; - - if (auto mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent())) { - CFRelease(mode); - return true; - } - return false; -} - -#else -class UvScheduler final : public realm::scheduler { -public: - UvScheduler(uv_loop_t* loop) - : m_handle(std::make_unique()) { - int err = uv_async_init(loop, m_handle.get(), [](uv_async_t *handle) { - if (!handle->data) { - return; - } - auto &data = *static_cast(handle->data); - if (data.close_requested) { - uv_close(reinterpret_cast(handle), [](uv_handle_t *handle) { - delete reinterpret_cast(handle->data); - delete reinterpret_cast(handle); - }); - } else { - data.queue.invoke_all(); - } - }); - if (err < 0) { - throw std::runtime_error("uv_async_init failed"); - } - m_handle->data = new Data; - } - - ~UvScheduler() { - if (m_handle && m_handle->data) { - static_cast(m_handle->data)->close_requested = true; - uv_async_send(m_handle.get()); - // Don't delete anything here as we need to delete it from within the event loop instead - m_handle.release(); - } - } - - bool is_on_thread() const noexcept override { - return m_id == std::this_thread::get_id(); - } - bool is_same_as(const scheduler *other) const noexcept override { - auto o = dynamic_cast(other); - return (o && (o->m_id == m_id)); - } - bool can_invoke() const noexcept override { - return true; - } - - void invoke(std::function &&fn) override { - auto &data = *static_cast(m_handle->data); - data.queue.push(std::move(fn)); - uv_async_send(m_handle.get()); - } - -private: - struct Data { - InvocationQueue queue; - std::atomic close_requested = {false}; - }; - std::unique_ptr m_handle; - std::thread::id m_id = std::this_thread::get_id(); -}; -#endif - -#if REALM_PLATFORM_APPLE - -void run_until(CFRunLoopRef loop, std::function predicate) -{ - REALM_ASSERT(loop == CFRunLoopGetCurrent()); - - auto callback = [](CFRunLoopObserverRef, CFRunLoopActivity, void* info) { - if ((*static_cast*>(info))()) { - CFRunLoopStop(CFRunLoopGetCurrent()); - } - }; - CFRunLoopObserverContext ctx{}; - ctx.info = &predicate; - CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, true, 0, callback, &ctx); - CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler( - kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0.0005, 0, 0, - ^(CFRunLoopTimerRef){ - // Do nothing. The timer firing is sufficient to cause our runloop observer to run. - }); - CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes); - CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes); - CFRunLoopRun(); - CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes); - CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes); - CFRelease(observer); - CFRelease(timer); -} - -TEST_CASE("run loops", "[run loops]") { - realm_path path; - - SECTION("CFRunLoop threads", "[run loops]") { - realm::notification_token t1; - realm::notification_token t2; - - bool signal1; - std::mutex m1; - std::condition_variable v1; - - bool signal2; - std::mutex m2; - std::condition_variable v2; - - { - std::thread([&](){ - // Each thread needs a run loop - auto loop = CFRunLoopGetCurrent(); - - auto obj = realm::AllTypesObject(); - auto config = realm::db_config(path, std::make_shared(loop)); - - auto realm = realm::db(std::move(config)); - auto managed_obj = realm.write([&realm, &obj] { - return realm.add(std::move(obj)); - }); - auto token = managed_obj.observe([&](auto change) { - std::unique_lock lock(m1); - signal1 = true; - v1.notify_one(); - }); - - t1 = std::move(token); - - realm.write([&realm, &managed_obj] { - managed_obj.str_col = "456"; - }); - - CFRunLoopRun(); - }).detach(); - } - - { - std::thread([&](){ - // Each thread needs a run loop - auto loop = CFRunLoopGetCurrent(); - - auto obj = realm::AllTypesObject(); - - auto config = realm::db_config(path, std::make_shared(loop)); - auto realm = realm::db(std::move(config)); - { - obj._id = 123; - auto managed_obj = realm.write([&realm, &obj] { - return realm.add(std::move(obj)); - }); - - auto token = managed_obj.observe([&](auto change) { - std::unique_lock lock(m2); - signal2 = true; - v2.notify_one(); - }); - - t2 = std::move(token); - } - - { - auto c = realm::db_config(path, std::make_shared(loop)); - - auto r = realm::db(std::move(c)); - auto o = r.objects().where([](auto& obj) { return obj._id == 123; })[0]; - - r.write([&r, &o] { - o.str_col = "123"; - }); - } - - CFRunLoopRun(); - }).detach(); - } - - std::unique_lock lock(m1); - v1.wait(lock, [&] { return signal1; }); - std::unique_lock lock2(m2); - v2.wait(lock2, [&] { return signal2; }); - } - - SECTION("CFRunLoop main thread", "[run loops]") { - realm::notification_token token; - - bool signal = false; - - auto loop = CFRunLoopGetCurrent(); - auto obj = realm::AllTypesObject(); - auto config = realm::db_config(path, std::make_shared(loop)); - - auto realm = realm::db(std::move(config)); - auto managed_obj = realm.write([&realm, &obj] { - return realm.add(std::move(obj)); - }); - token = managed_obj.observe([&](auto change) { - signal = true; - }); - - realm.write([&realm, &managed_obj] { - managed_obj.str_col = "456"; - }); - - run_until(loop, [&]() { - return signal; - }); - } -} - -#else - -struct IdleHandler { - uv_idle_t* idle = new uv_idle_t; - - IdleHandler(uv_loop_t* loop) - { - uv_idle_init(loop, idle); - } - ~IdleHandler() - { - uv_close(reinterpret_cast(idle), [](uv_handle_t* handle) { - delete reinterpret_cast(handle); - }); - } -}; - -inline void run_until(uv_loop_t *loop, std::function predicate) { - if (predicate()) - return; - - IdleHandler observer(loop); - observer.idle->data = &predicate; - - uv_idle_start(observer.idle, [](uv_idle_t* handle) { - auto& predicate = *static_cast*>(handle->data); - if (predicate()) { - uv_stop(handle->loop); - } - }); - - uv_run(loop, UV_RUN_DEFAULT); - uv_idle_stop(observer.idle); -} - -TEST_CASE("run loops", "[run loops]") { - realm_path path; - -// #ifndef _WIN32 -// SECTION("uv threads detached linux", "[run loops]") { -// realm::notification_token t1; -// realm::notification_token t2; -// -// bool signal1; -// std::mutex m1; -// std::condition_variable v1; -// -// bool signal2; -// std::mutex m2; -// std::condition_variable v2; -// -// { -// std::thread([&](){ -// // Each thread needs a run loop -// auto loop = uv_loop_new(); -// -// auto obj = realm::AllTypesObject(); -// auto config = realm::db_config(path, std::make_shared(loop)); -// -// auto realm = realm::db(std::move(config)); -// auto managed_obj = realm.write([&realm, &obj] { -// return realm.add(std::move(obj)); -// }); -// auto token = managed_obj.observe([&](auto change) { -// std::unique_lock lock(m1); -// signal1 = true; -// v1.notify_one(); -// }); -// -// t1 = std::move(token); -// -// realm.write([&realm, &managed_obj] { -// managed_obj.str_col = "456"; -// }); -// -// uv_run(loop, UV_RUN_DEFAULT); -// }).detach(); -// } -// -// { -// std::thread([&](){ -// // Each thread needs a run loop -// auto loop = uv_loop_new(); -// -// auto obj = realm::AllTypesObject(); -// -// auto config = realm::db_config(path, std::make_shared(loop)); -// auto realm = realm::db(std::move(config)); -// -// { -// obj._id = 123; -// auto managed_obj = realm.write([&realm, &obj] { -// return realm.add(std::move(obj)); -// }); -// -// auto token = managed_obj.observe([&](auto change) { -// std::unique_lock lock(m2); -// signal2 = true; -// v2.notify_one(); -// }); -// -// t2 = std::move(token); -// } -// -// { -// auto c = realm::db_config(path, std::make_shared(loop)); -// -// auto r = realm::db(std::move(c)); -// auto o = r.objects().where([](auto& obj) { return obj._id == 123; })[0]; -// -// r.write([&r, &o] { -// o.str_col = "123"; -// }); -// } -// -// uv_run(loop, UV_RUN_DEFAULT); -// }).detach(); -// } -// -// std::unique_lock lock(m1); -// v1.wait(lock, [&] { return signal1; }); -// std::unique_lock lock2(m2); -// v2.wait(lock2, [&] { return signal2; }); -// } -// #endif - SECTION("uv main thread", "[run loops]") { - realm::notification_token t1; - realm::notification_token t2; - - bool signal = false; - - auto loop = uv_loop_new(); - auto obj = realm::AllTypesObject(); - auto config = realm::db_config(path, std::make_shared(loop)); - - auto realm = realm::db(std::move(config)); - auto managed_obj = realm.write([&realm, &obj] { - return realm.add(std::move(obj)); - }); - auto token = managed_obj.observe([&](auto change) { - signal = true; - }); - - t1 = std::move(token); - - realm.write([&realm, &managed_obj] { - managed_obj.str_col = "456"; - }); - - run_until(loop, [&]() { - return signal; - }); - } -} -#endif diff --git a/tests/db/scheduler_tests.cpp b/tests/db/scheduler_tests.cpp new file mode 100644 index 000000000..656be1765 --- /dev/null +++ b/tests/db/scheduler_tests.cpp @@ -0,0 +1,43 @@ +#include "../main.hpp" +#include "test_objects.hpp" + +#include + +class test_scheduler final : public realm::scheduler { +public: + + ~test_scheduler() = default; + + bool is_on_thread() const noexcept override { + return m_id == std::this_thread::get_id(); + } + bool is_same_as(const scheduler *other) const noexcept override { + auto o = dynamic_cast(other); + return (o && (o->m_id == m_id)); + } + bool can_invoke() const noexcept override { + return true; + } + + void invoke(std::function &&fn) override { + fn(); + } + +private: + std::thread::id m_id = std::this_thread::get_id(); +}; + +TEST_CASE("user defined scheduler", "[scheduler]") { + SECTION("set default factory", "[scheduler]") { + auto scheduler = test_scheduler(); + realm::default_scheduler::set_default_factory([]() { + return std::make_shared(); + }); + auto default_scheduler = realm::default_scheduler::make_default(); + CHECK(scheduler.is_same_as(default_scheduler.get())); + // Set back to platform default so other tests are no affected. + realm::default_scheduler::set_default_factory([]() { + return realm::default_scheduler::make_platform_default(); + }); + } +} \ No newline at end of file