diff --git a/score/memory/shared/shared_memory_factory.cpp b/score/memory/shared/shared_memory_factory.cpp index 4148bb7c68..7e212b35ad 100644 --- a/score/memory/shared/shared_memory_factory.cpp +++ b/score/memory/shared/shared_memory_factory.cpp @@ -85,6 +85,11 @@ auto SharedMemoryFactory::SetTypedMemoryProvider(std::shared_ptr ty instance().SetTypedMemoryProvider(typed_memory_ptr); } +auto SharedMemoryFactory::SetInterVMMemoryProvider(std::shared_ptr intervm_memory_ptr) noexcept -> void +{ + static_cast(instance()).SetInterVMMemoryProvider(intervm_memory_ptr); +} + auto SharedMemoryFactory::instance() noexcept -> ISharedMemoryFactory& { if (mock_ != nullptr) diff --git a/score/memory/shared/shared_memory_factory.h b/score/memory/shared/shared_memory_factory.h index acf76f581d..3625955441 100644 --- a/score/memory/shared/shared_memory_factory.h +++ b/score/memory/shared/shared_memory_factory.h @@ -160,6 +160,8 @@ class SharedMemoryFactory static void SetTypedMemoryProvider(std::shared_ptr typed_memory_ptr) noexcept; + static void SetInterVMMemoryProvider(std::shared_ptr intervm_memory_ptr) noexcept; + static std::size_t GetControlBlockSize() noexcept { return instance().GetControlBlockSize(); diff --git a/score/memory/shared/shared_memory_factory_impl.cpp b/score/memory/shared/shared_memory_factory_impl.cpp index 83bbdc5cd6..f5e7153de7 100644 --- a/score/memory/shared/shared_memory_factory_impl.cpp +++ b/score/memory/shared/shared_memory_factory_impl.cpp @@ -139,18 +139,23 @@ auto SharedMemoryFactoryImpl::Open(const std::string& path, { if (IsInterVmShmPath(path)) { - score::mw::log::LogError("shm") << "Opening inter-VM shared memory (path prefix '" << kInterVmSharedShmPrefix - << "') is not supported. No portable implementation exists yet. Rejecting path: " - << path; - return nullptr; + if (intervm_memory_ptr_ == nullptr) + { + score::mw::log::LogError("shm") + << "Opening inter-VM shared memory (path prefix '" << kInterVmSharedShmPrefix + << "') requires an InterVM memory provider set via SetInterVMMemoryProvider(). Rejecting path: " + << path; + return nullptr; + } } std::lock_guard lock{mutex_}; auto resource = GetResourceIfAlreadyOpened(path, resources_); if (resource == nullptr) { + const auto effective_ptr = IsInterVmShmPath(path) ? intervm_memory_ptr_ : typed_memory_ptr_; const auto result = - SharedMemoryResource::Open(path, is_read_write, &CreateAccessControlList, typed_memory_ptr_); + SharedMemoryResource::Open(path, is_read_write, &CreateAccessControlList, effective_ptr); if (!result.has_value()) { score::mw::log::LogWarn("shm") << "Could not open Shared Memory " << path << ":" << result.error(); @@ -184,10 +189,14 @@ auto score::memory::shared::SharedMemoryFactoryImpl::Create(std::string path, { if (IsInterVmShmPath(path)) { - score::mw::log::LogError("shm") << "Creating inter-VM shared memory (path prefix '" << kInterVmSharedShmPrefix - << "') is not supported. No portable implementation exists yet. Rejecting path: " - << path; - return nullptr; + if (intervm_memory_ptr_ == nullptr) + { + score::mw::log::LogError("shm") + << "Creating inter-VM shared memory (path prefix '" << kInterVmSharedShmPrefix + << "') requires an InterVM memory provider set via SetInterVMMemoryProvider(). Rejecting path: " + << path; + return nullptr; + } } std::lock_guard lock{mutex_}; @@ -197,17 +206,27 @@ auto score::memory::shared::SharedMemoryFactoryImpl::Create(std::string path, return nullptr; } - if (prefer_typed_memory && (typed_memory_ptr_ == nullptr)) + // Determine which provider to use: inter-VM paths use the InterVM provider, + // otherwise fall back to the typed memory provider if prefer_typed_memory is set. + std::shared_ptr effective_provider = nullptr; + if (IsInterVmShmPath(path)) { - score::mw::log::LogError("shm") - << "Shared memory has to be created in typed memory but no typed memory instance has " - "been provided using the public interface SetTypedMemoryProvider "; - return nullptr; + effective_provider = intervm_memory_ptr_; + } + else if (prefer_typed_memory) + { + if (typed_memory_ptr_ == nullptr) + { + score::mw::log::LogError("shm") + << "Shared memory has to be created in typed memory but no typed memory instance has " + "been provided using the public interface SetTypedMemoryProvider "; + return nullptr; + } + effective_provider = typed_memory_ptr_; } - const auto typed_memory_ptr = prefer_typed_memory ? typed_memory_ptr_ : nullptr; const auto result = SharedMemoryResource::Create( - path, user_space_to_reserve, std::move(cb), permissions, &CreateAccessControlList, typed_memory_ptr); + path, user_space_to_reserve, std::move(cb), permissions, &CreateAccessControlList, effective_provider); if (!result.has_value()) { score::mw::log::LogWarn("shm") << "Could not create Shared Memory " << path << ":" << result.error(); @@ -264,32 +283,43 @@ auto score::memory::shared::SharedMemoryFactoryImpl::CreateOrOpen( { if (IsInterVmShmPath(path)) { - score::mw::log::LogError("shm") << "Creating or opening inter-VM shared memory (path prefix '" - << kInterVmSharedShmPrefix - << "') is not supported. No portable implementation exists yet. Rejecting path: " - << path; - return nullptr; + if (intervm_memory_ptr_ == nullptr) + { + score::mw::log::LogError("shm") + << "Creating or opening inter-VM shared memory (path prefix '" << kInterVmSharedShmPrefix + << "') requires an InterVM memory provider set via SetInterVMMemoryProvider(). Rejecting path: " + << path; + return nullptr; + } } std::lock_guard lock{mutex_}; auto resource = GetResourceIfAlreadyOpened(path, resources_); if (resource == nullptr) { - if ((prefer_typed_memory) && (typed_memory_ptr_ == nullptr)) + std::shared_ptr effective_provider = nullptr; + if (IsInterVmShmPath(path)) { - score::mw::log::LogError("shm") - << "Shared memory has to be created in typed memory but no typed memory instance " - "has been provided using the public interface SetTypedMemoryProvider "; - return nullptr; + effective_provider = intervm_memory_ptr_; + } + else if (prefer_typed_memory) + { + if (typed_memory_ptr_ == nullptr) + { + score::mw::log::LogError("shm") + << "Shared memory has to be created in typed memory but no typed memory instance " + "has been provided using the public interface SetTypedMemoryProvider "; + return nullptr; + } + effective_provider = typed_memory_ptr_; } - const auto typed_memory_ptr = prefer_typed_memory ? typed_memory_ptr_ : nullptr; const auto result = SharedMemoryResource::CreateOrOpen(path, user_space_to_reserve, std::move(cb), access_control.permissions_, &CreateAccessControlList, - typed_memory_ptr); + effective_provider); if (!result.has_value()) { score::mw::log::LogWarn("shm") << __func__ << __LINE__ << "Could not create or open Shared Memory " << path @@ -382,4 +412,9 @@ auto SharedMemoryFactoryImpl::SetTypedMemoryProvider(std::shared_ptr intervm_memory_ptr) noexcept -> void +{ + intervm_memory_ptr_ = intervm_memory_ptr; +} + } // namespace score::memory::shared diff --git a/score/memory/shared/shared_memory_factory_impl.h b/score/memory/shared/shared_memory_factory_impl.h index b6f45d9cab..6355eaaed7 100644 --- a/score/memory/shared/shared_memory_factory_impl.h +++ b/score/memory/shared/shared_memory_factory_impl.h @@ -59,6 +59,8 @@ class SharedMemoryFactoryImpl final : public ISharedMemoryFactory void SetTypedMemoryProvider(std::shared_ptr typed_memory_ptr) noexcept override; + void SetInterVMMemoryProvider(std::shared_ptr intervm_memory_ptr) noexcept; + std::size_t GetControlBlockSize() noexcept override { return sizeof(score::memory::shared::SharedMemoryResource::ControlBlock); @@ -73,6 +75,7 @@ class SharedMemoryFactoryImpl final : public ISharedMemoryFactory std::mutex mutex_{}; std::unordered_map> resources_{}; std::shared_ptr typed_memory_ptr_{memory::shared::TypedMemory::Default()}; + std::shared_ptr intervm_memory_ptr_{nullptr}; }; } // namespace score::memory::shared diff --git a/score/memory/shared/shared_memory_factory_test.cpp b/score/memory/shared/shared_memory_factory_test.cpp index 5a899ef591..111faecfd7 100644 --- a/score/memory/shared/shared_memory_factory_test.cpp +++ b/score/memory/shared/shared_memory_factory_test.cpp @@ -1185,36 +1185,43 @@ TEST_F(SharedMemoryFactoryDeathTest, FailingToInsertResourceIntoRegistryTerminat EXPECT_DEATH(resource_attorney.mapMemoryIntoProcess(), ".*"); } -TEST_F(SharedMemoryFactoryTest, OpenWithInterVmPathReturnsNullptr) +TEST_F(SharedMemoryFactoryTest, OpenWithInterVmPathReturnsNullptrWhenProviderNotSet) { // Given a shared memory path using the inter-VM prefix const std::string inter_vm_path{"/intervm-shared-shmem/lola-data-0000000000004660-00001"}; + // And no InterVM memory provider has been set + // (SharedMemoryFactory is fresh after TearDown/SetUp) + // When opening shared memory using this path - // Then nullptr is returned and no OS calls are made (no mock expectations set) + // Then nullptr is returned because InterVM provider is required but not set const auto result = SharedMemoryFactory::Open(inter_vm_path, false); EXPECT_EQ(result, nullptr); } -TEST_F(SharedMemoryFactoryTest, CreateWithInterVmPathReturnsNullptr) +TEST_F(SharedMemoryFactoryTest, CreateWithInterVmPathReturnsNullptrWhenProviderNotSet) { // Given a shared memory path using the inter-VM prefix const std::string inter_vm_path{"/intervm-shared-shmem/lola-ctl-0000000000004660-00001-b"}; + // And no InterVM memory provider has been set + // When creating shared memory using this path - // Then nullptr is returned and no OS calls are made (no mock expectations set) + // Then nullptr is returned because InterVM provider is required but not set const auto result = SharedMemoryFactory::Create( inter_vm_path, [](std::shared_ptr) {}, kSharedMemorySize, {}, false); EXPECT_EQ(result, nullptr); } -TEST_F(SharedMemoryFactoryTest, CreateOrOpenWithInterVmPathReturnsNullptr) +TEST_F(SharedMemoryFactoryTest, CreateOrOpenWithInterVmPathReturnsNullptrWhenProviderNotSet) { // Given a shared memory path using the inter-VM prefix const std::string inter_vm_path{"/intervm-shared-shmem/lola-methods-0000000000004660-00001-00002-00003"}; + // And no InterVM memory provider has been set + // When creating or opening shared memory using this path - // Then nullptr is returned and no OS calls are made (no mock expectations set) + // Then nullptr is returned because InterVM provider is required but not set const auto result = SharedMemoryFactory::CreateOrOpen( inter_vm_path, [](std::shared_ptr) {}, @@ -1224,4 +1231,111 @@ TEST_F(SharedMemoryFactoryTest, CreateOrOpenWithInterVmPathReturnsNullptr) EXPECT_EQ(result, nullptr); } +TEST_F(SharedMemoryFactoryTest, OpenWithInterVmPathSucceedsWhenProviderIsSet) +{ + InSequence sequence{}; + + // Given a shared memory path using the inter-VM prefix (use standard test path for simplicity) + // Note: The key test is that SetInterVMMemoryProvider allows the operation, not the path format + const std::string test_path = TestValues::sharedMemorySegmentPath; + + // And an InterVM memory provider has been set + auto intervm_mock = std::make_shared(); + SharedMemoryFactory::SetInterVMMemoryProvider(intervm_mock); + + // And the shared memory can be opened successfully + constexpr bool is_read_write = false; + expectSharedMemorySuccessfullyOpened(kFileDescriptor, is_read_write); + + // When opening shared memory using this path + const auto result = SharedMemoryFactory::Open(test_path, is_read_write); + + // Then the resource is successfully opened + ASSERT_NE(result, nullptr); + EXPECT_EQ(*result->getPath(), test_path); + + // Cleanup + SharedMemoryFactory::SetInterVMMemoryProvider(nullptr); +} + +TEST_F(SharedMemoryFactoryTest, CreateWithInterVmPathSucceedsWhenProviderIsSet) +{ + InSequence sequence{}; + + // Given a shared memory path (use standard test path for simplicity) + const std::string test_path = TestValues::sharedMemorySegmentPath; + + // And an InterVM memory provider has been set + auto intervm_mock = std::make_shared(); + SharedMemoryFactory::SetInterVMMemoryProvider(intervm_mock); + + // And shared memory can be created successfully + std::array dataRegion{}; + const bool prefer_typed_memory = false; + expectSharedMemorySuccessfullyCreated( + kFileDescriptor, kLockFileDescriptor, dataRegion.data(), prefer_typed_memory); + + EXPECT_CALL(*mman_mock_, munmap(_, _)); + EXPECT_CALL(*unistd_mock_, close(kFileDescriptor)); + + // When creating shared memory using this path + const auto result = SharedMemoryFactory::Create( + test_path, [](std::shared_ptr) {}, kSharedMemorySize, {}, prefer_typed_memory); + + // Then the resource is successfully created + ASSERT_NE(result, nullptr); + EXPECT_EQ(*result->getPath(), test_path); + + // Cleanup + SharedMemoryFactory::SetInterVMMemoryProvider(nullptr); +} + +TEST_F(SharedMemoryFactoryTest, CreateOrOpenWithInterVmPathSucceedsWhenProviderIsSet) +{ + InSequence sequence{}; + + // Given a shared memory path (use standard test path for simplicity) + const std::string test_path = TestValues::sharedMemorySegmentPath; + + // And an InterVM memory provider has been set + auto intervm_mock = std::make_shared(); + SharedMemoryFactory::SetInterVMMemoryProvider(intervm_mock); + + // And shared memory can be created successfully (CreateOrOpen tries Create after Open fails) + std::array dataRegion{}; + const bool prefer_typed_memory = false; + + // CreateOrOpen will first try to Open (which fails), then Create + constexpr std::int32_t open_lock_file_descriptor = 10; + constexpr bool is_read_write = true; + + // Expect the Open attempt to fail + expectCreateLockFileReturns(TestValues::sharedMemorySegmentLockPath, open_lock_file_descriptor); + expectShmOpenReturns(test_path, score::cpp::make_unexpected(Error::createFromErrno(ENOENT)), is_read_write); + EXPECT_CALL(*unistd_mock_, close(open_lock_file_descriptor)); + EXPECT_CALL(*unistd_mock_, unlink(StrEq(TestValues::sharedMemorySegmentLockPath))); + + // Then expect the Create to succeed + expectSharedMemorySuccessfullyCreated( + kFileDescriptor, kLockFileDescriptor, dataRegion.data(), prefer_typed_memory); + + EXPECT_CALL(*mman_mock_, munmap(_, _)); + EXPECT_CALL(*unistd_mock_, close(kFileDescriptor)); + + // When creating or opening shared memory using this path + const auto result = SharedMemoryFactory::CreateOrOpen( + test_path, + [](std::shared_ptr) {}, + kSharedMemorySize, + SharedMemoryResource::AccessControl{{}, {}}, + prefer_typed_memory); + + // Then the resource is successfully created or opened + ASSERT_NE(result, nullptr); + EXPECT_EQ(*result->getPath(), test_path); + + // Cleanup + SharedMemoryFactory::SetInterVMMemoryProvider(nullptr); +} + } // namespace score::memory::shared::test