diff --git a/build/Mlos.NetCore.Grpc.props b/build/Mlos.NetCore.Grpc.props index 5b56d1a12f..8168211ac7 100644 --- a/build/Mlos.NetCore.Grpc.props +++ b/build/Mlos.NetCore.Grpc.props @@ -4,34 +4,41 @@ $(BaseDir)\out\Grpc.out\$(GrpcServices)\$(BuildType) 2.29.0 + 3.12.0 - - + - - - - + - - - + - - + + + + + $([System.IO.Path]::GetFullPath('$(BaseDir)\source\Mlos.Grpc.Binplace\Mlos.Grpc.Binplace.csproj')) + $([System.IO.Path]::GetFullPath('$(MSBuildProjectFullPath)')) + + + + diff --git a/source/Examples/SmartCache/Main.cpp b/source/Examples/SmartCache/Main.cpp index c6b82aefa3..fe3b596c75 100644 --- a/source/Examples/SmartCache/Main.cpp +++ b/source/Examples/SmartCache/Main.cpp @@ -43,7 +43,7 @@ using namespace SmartCache; // HRESULTs are an error code encoding mechanism typically used in Windows environments. // See Also: https://en.wikipedia.org/wiki/HRESULT // -void CheckHR(HRESULT hr) +void ThrowIfFail(HRESULT hr) { if (FAILED(hr)) { @@ -74,7 +74,7 @@ main( // Mlos::Core::InterProcessMlosContextInitializer mlosContextInitializer; HRESULT hr = mlosContextInitializer.Initialize(); - CheckHR(hr); + ThrowIfFail(hr); Mlos::Core::InterProcessMlosContext mlosContext(std::move(mlosContextInitializer)); @@ -124,7 +124,7 @@ main( hr = mlosContext.RegisterSettingsAssembly( "SmartCache.SettingsRegistry.dll", SmartCache::ObjectDeserializationHandler::DispatchTableBaseIndex()); - CheckHR(hr); + ThrowIfFail(hr); // Create a component configuration object. // This will be stored in a shared memory region below for use by both the @@ -145,7 +145,7 @@ main( // config for this component and if not creates it. // hr = mlosContext.RegisterComponentConfig(config); - CheckHR(hr); + ThrowIfFail(hr); // Create an instance of our SmartCache component to tune. // diff --git a/source/Examples/SmartCache/SmartCache.sln b/source/Examples/SmartCache/SmartCache.sln index 2742b4cfee..e1de60c248 100644 --- a/source/Examples/SmartCache/SmartCache.sln +++ b/source/Examples/SmartCache/SmartCache.sln @@ -6,6 +6,7 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SmartCache", "SmartCache.vcxproj", "{0CE12F0A-E7B5-4E52-B6DF-E9BA1F525030}" ProjectSection(ProjectDependencies) = postProject {E4407270-6E64-4E87-A4B7-3B932121BC81} = {E4407270-6E64-4E87-A4B7-3B932121BC81} + {5DB4C179-4A97-40F9-8155-8C44B2BE416D} = {5DB4C179-4A97-40F9-8155-8C44B2BE416D} EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartCache.SettingsRegistry", "SmartCache.SettingsRegistry\SmartCache.SettingsRegistry.csproj", "{E4407270-6E64-4E87-A4B7-3B932121BC81}" diff --git a/source/Mlos.Agent.Server/MlosAgentServer.cs b/source/Mlos.Agent.Server/MlosAgentServer.cs index 5fbce6837a..d3aef5bb59 100644 --- a/source/Mlos.Agent.Server/MlosAgentServer.cs +++ b/source/Mlos.Agent.Server/MlosAgentServer.cs @@ -104,10 +104,15 @@ public static void Main(string[] args) MlosContext.OptimizerFactory = new MlosOptimizer.BayesianOptimizerFactory(optimizerAddressUri); } - // Create (or open) the circular buffer shared memory before running the target process. + // In the active learning mode, create a new shared memory map before running the target process. + // On Linux, we unlink existing shared memory map, if they exist. + // If the agent is not in the active learning mode, create new or open existing to communicate with the target process. // + using MlosContext mlosContext = (executableFilePath != null) + ? InterProcessMlosContext.Create() + : InterProcessMlosContext.CreateOrOpen(); using var mainAgent = new MainAgent(); - mainAgent.InitializeSharedChannel(); + mainAgent.InitializeSharedChannel(mlosContext); // Active learning mode. // @@ -153,7 +158,9 @@ public static void Main(string[] args) Console.WriteLine("Starting Mlos.Agent"); Task mlosAgentTask = Task.Factory.StartNew( () => mainAgent.RunAgent(), - TaskCreationOptions.LongRunning); + CancellationToken.None, + TaskCreationOptions.LongRunning, + TaskScheduler.Current); Task waitForTargetProcessTask = Task.Factory.StartNew( () => @@ -165,13 +172,15 @@ public static void Main(string[] args) mainAgent.UninitializeSharedChannel(); } }, - TaskCreationOptions.LongRunning); + CancellationToken.None, + TaskCreationOptions.LongRunning, + TaskScheduler.Current); Console.WriteLine("Waiting for Mlos.Agent to exit"); while (true) { - Task.WaitAny(new[] { mlosAgentTask, waitForTargetProcessTask }); + Task.WaitAny(mlosAgentTask, waitForTargetProcessTask); if (mlosAgentTask.IsFaulted && targetProcessManager != null && !waitForTargetProcessTask.IsCompleted) { diff --git a/source/Mlos.Agent.Server/TargetProcessManager.cs b/source/Mlos.Agent.Server/TargetProcessManager.cs index 9e4e736f21..9090977ae5 100644 --- a/source/Mlos.Agent.Server/TargetProcessManager.cs +++ b/source/Mlos.Agent.Server/TargetProcessManager.cs @@ -7,9 +7,7 @@ // ----------------------------------------------------------------------- using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Text; namespace Mlos.Agent.Server { @@ -22,9 +20,10 @@ namespace Mlos.Agent.Server internal class TargetProcessManager : IDisposable { private readonly string executableFilePath; + private Process targetProcess; - private bool disposed = false; + private bool isDisposed; public TargetProcessManager(string executableFilePath) { @@ -40,18 +39,15 @@ public void Dispose() private void Dispose(bool disposing) { - if (disposed) + if (isDisposed || !disposing) { return; } - if (disposing) - { - targetProcess?.Dispose(); - targetProcess = null; - } + targetProcess?.Dispose(); + targetProcess = null; - disposed = true; + isDisposed = true; } public void StartTargetProcess() diff --git a/source/Mlos.Agent/MainAgent.cs b/source/Mlos.Agent/MainAgent.cs index f4ca52d861..b350f2b020 100644 --- a/source/Mlos.Agent/MainAgent.cs +++ b/source/Mlos.Agent/MainAgent.cs @@ -13,7 +13,6 @@ using System.Runtime.InteropServices; using Mlos.Core; -using Proxy.Mlos.Core; using MlosProxy = Proxy.Mlos.Core; using MlosProxyInternal = Proxy.Mlos.Core.Internal; @@ -28,18 +27,6 @@ namespace Mlos.Agent /// public class MainAgent : IDisposable { - /// - /// Shared memory mapping name must start with "Host_" prefix, to be accessible from certain applications. - /// TODO: Make these config regions configurable to support multiple processes. - /// - private const string GlobalMemoryMapName = "Host_Mlos.GlobalMemory"; - private const string ControlChannelMemoryMapName = "Host_Mlos.ControlChannel"; - private const string FeedbackChannelMemoryMapName = "Host_Mlos.FeedbackChannel"; - private const string ControlChannelSemaphoreName = @"Global\ControlChannel_Event"; //// FIXME: Use non-backslashes for Linux environments. - private const string FeedbackChannelSemaphoreName = @"Global\FeedbackChannel_Event"; - private const string SharedConfigMemoryMapName = "Host_Mlos.Config.SharedMemory"; - private const int SharedMemorySize = 65536; - private readonly SettingsAssemblyManager settingsAssemblyManager = new SettingsAssemblyManager(); private readonly Dictionary memoryRegions = new Dictionary(); @@ -53,15 +40,9 @@ public class MainAgent : IDisposable private bool isDisposed; #region Shared objects - private SharedMemoryRegionView globalMemoryRegionView; - - private SharedMemoryMapView controlChannelMemoryMapView; - private SharedMemoryMapView feedbackChannelMemoryMapView; - private NamedEvent controlChannelNamedEvent; - private NamedEvent feedbackChannelNamedEvent; + private MlosContext mlosContext; - private SharedMemoryRegionView sharedConfigMemoryMapView; #endregion #region Mlos.Agent setup @@ -69,48 +50,17 @@ public class MainAgent : IDisposable /// /// Initialize shared channel. /// - public void InitializeSharedChannel() + /// Mlos context instance. + public void InitializeSharedChannel(MlosContext mlosContext) { - // Create or open the memory mapped files. - // - Console.WriteLine("Create or open memory mapped files"); - globalMemoryRegionView = SharedMemoryRegionView.CreateOrOpen(GlobalMemoryMapName, SharedMemorySize); - controlChannelMemoryMapView = SharedMemoryMapView.CreateOrOpen(ControlChannelMemoryMapName, SharedMemorySize); - feedbackChannelMemoryMapView = SharedMemoryMapView.CreateOrOpen(FeedbackChannelMemoryMapName, SharedMemorySize); - sharedConfigMemoryMapView = SharedMemoryRegionView.CreateOrOpen(SharedConfigMemoryMapName, SharedMemorySize); - - // Create channel synchronization primitives. - // - Console.WriteLine("Create channel synchronization primitives"); - controlChannelNamedEvent = NamedEvent.CreateOrOpen(ControlChannelSemaphoreName); - feedbackChannelNamedEvent = NamedEvent.CreateOrOpen(FeedbackChannelSemaphoreName); - - // Setup feedback channel. - // - Console.WriteLine("Setup feedback channel"); - - MlosProxyInternal.GlobalMemoryRegion globalMemoryRegion = globalMemoryRegionView.MemoryRegion(); - - Console.WriteLine("Setup feedback channel"); - - - var feedbackChannel = new SharedChannel( - buffer: feedbackChannelMemoryMapView.Buffer, - size: (uint)feedbackChannelMemoryMapView.MemSize, - sync: globalMemoryRegion.FeedbackChannelSynchronization) - { - ChannelPolicy = { NotificationEvent = feedbackChannelNamedEvent }, - }; + this.mlosContext = mlosContext; // Set SharedConfig memory region. // - Console.WriteLine("Set SharedConfig memory region."); - - sharedConfigManager.SetMemoryRegion(new MlosProxyInternal.SharedConfigMemoryRegion { Buffer = sharedConfigMemoryMapView.MemoryRegion().Buffer }); + sharedConfigManager.SetMemoryRegion(new MlosProxyInternal.SharedConfigMemoryRegion { Buffer = mlosContext.SharedConfigMemoryRegion.Buffer }); // Setup MlosContext. // - MlosContext.FeedbackChannel = feedbackChannel; MlosContext.SharedConfigManager = sharedConfigManager; // Initialize callbacks. @@ -129,7 +79,7 @@ public void InitializeSharedChannel() // Register assemblies from the shared config. // Assembly Mlos.NetCore does not have a config, as it is always registered first. // - for (uint index = 1; index < globalMemoryRegion.RegisteredSettingsAssemblyCount.Load(); index++) + for (uint index = 1; index < mlosContext.GlobalMemoryRegion.RegisteredSettingsAssemblyCount.Load(); index++) { RegisterSettingsAssembly(assemblyIndex: index); } @@ -144,8 +94,8 @@ public void UninitializeSharedChannel() // Signal named event to close any waiter threads. // - controlChannelNamedEvent.Signal(); - feedbackChannelNamedEvent.Signal(); + mlosContext.TerminateControlChannel(); + mlosContext.TerminateFeedbackChannel(); } /// @@ -301,15 +251,13 @@ private void RegisterSharedConfigMemoryRegionRequestMessageCallback(MlosProxyInt /// /// #TODO remove, this is not required. - /// set the terminate channel in sync object and signal. /// /// - private void TerminateReaderThreadRequestMessageCallback(TerminateReaderThreadRequestMessage msg) + private void TerminateReaderThreadRequestMessageCallback(MlosProxy.TerminateReaderThreadRequestMessage msg) { // Terminate the channel. // - MlosProxyInternal.GlobalMemoryRegion globalMemoryRegion = globalMemoryRegionView.MemoryRegion(); - ChannelSynchronization controlChannelSync = globalMemoryRegion.ControlChannelSynchronization; + MlosProxy.ChannelSynchronization controlChannelSync = mlosContext.GlobalMemoryRegion.ControlChannelSynchronization; controlChannelSync.TerminateChannel.Store(true); } @@ -320,20 +268,9 @@ private void TerminateReaderThreadRequestMessageCallback(TerminateReaderThreadRe /// public void RunAgent() { - // Create the shared memory control channel. - // - var globalMemoryRegion = globalMemoryRegionView.MemoryRegion(); - var controlChannel = new SharedChannel( - buffer: controlChannelMemoryMapView.Buffer, - size: (uint)controlChannelMemoryMapView.MemSize, - sync: globalMemoryRegion.ControlChannelSynchronization) - { - ChannelPolicy = { NotificationEvent = controlChannelNamedEvent }, - }; - // Process the messages from the control channel. // - controlChannel.ProcessMessages(dispatchTable: ref globalDispatchTable); + MlosContext.ControlChannel.ProcessMessages(dispatchTable: ref globalDispatchTable); } protected virtual void Dispose(bool disposing) @@ -343,32 +280,16 @@ protected virtual void Dispose(bool disposing) return; } - // Close shared memory. + // Dispose MlosContext. // - globalMemoryRegionView?.Dispose(); - globalMemoryRegionView = null; - - controlChannelMemoryMapView?.Dispose(); - controlChannelMemoryMapView = null; - - feedbackChannelMemoryMapView?.Dispose(); - feedbackChannelMemoryMapView = null; - - sharedConfigMemoryMapView?.Dispose(); - sharedConfigMemoryMapView = null; - - controlChannelNamedEvent?.Dispose(); - controlChannelNamedEvent = null; - - feedbackChannelNamedEvent?.Dispose(); - feedbackChannelNamedEvent = null; + mlosContext?.Dispose(); + mlosContext = null; isDisposed = true; } public void Dispose() { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } diff --git a/source/Mlos.Core/CMakeLists.txt b/source/Mlos.Core/CMakeLists.txt index 231c0e4c51..6bcf6dd5e2 100644 --- a/source/Mlos.Core/CMakeLists.txt +++ b/source/Mlos.Core/CMakeLists.txt @@ -6,6 +6,8 @@ include("${MLOS_ROOT}/build/Mlos.Cpp.cmake") add_library(${PROJECT_NAME} STATIC GlobalMemoryRegion.cpp + InternalMlosContext.cpp + InterProcessMlosContext.cpp Mlos.Core.cpp MlosContext.cpp NamedEvent.Linux.cpp diff --git a/source/Mlos.Core/InterProcessMlosContext.cpp b/source/Mlos.Core/InterProcessMlosContext.cpp new file mode 100644 index 0000000000..3db14d2509 --- /dev/null +++ b/source/Mlos.Core/InterProcessMlosContext.cpp @@ -0,0 +1,152 @@ +//********************************************************************* +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root +// for license information. +// +// @File: InterProcessMlosContext.cpp +// +// Purpose: +// +// +// Notes: +// +// +//********************************************************************* + +#include "Mlos.Core.h" +#include "Mlos.Core.inl" + +namespace Mlos +{ +namespace Core +{ +//---------------------------------------------------------------------------- +// NAME: InterProcessMlosContextInitializer::Constructor. +// +// PURPOSE: +// Move constructor. +// +// NOTES: +// +InterProcessMlosContextInitializer::InterProcessMlosContextInitializer(InterProcessMlosContextInitializer&& initializer) noexcept + : m_globalMemoryRegionView(std::move(initializer.m_globalMemoryRegionView)), + m_controlChannelMemoryMapView(std::move(initializer.m_controlChannelMemoryMapView)), + m_feedbackChannelMemoryMapView(std::move(initializer.m_feedbackChannelMemoryMapView)), + m_controlChannelPolicy(std::move(initializer.m_controlChannelPolicy)), + m_feedbackChannelPolicy(std::move(initializer.m_feedbackChannelPolicy)) +{ +} + +//---------------------------------------------------------------------------- +// NAME: InterProcessMlosContextInitializer::Initialize +// +// PURPOSE: +// Opens the shared memory and synchronization primitives used for the communication channel. +// +// NOTES: +// +_Check_return_ +HRESULT InterProcessMlosContextInitializer::Initialize() +{ + // #TODO const as codegen, pass a config struct ? + // + const size_t SharedMemorySize = 65536; + + // TODO: Make these config regions configurable to support multiple processes. + // Note: Shared memory mapping name must start with "Host_" prefix, to be accessible from certain applications. + // + HRESULT hr = m_globalMemoryRegionView.CreateOrOpen("Host_Mlos.GlobalMemory", SharedMemorySize); + if (SUCCEEDED(hr)) + { + // Increase the usage counter. When closing global shared memory, we will decrease the counter. + // If there is no process using the shared memory, we will clean the OS resources. On Windows OS, + // this is no-op; on Linux, we unlink created files. + // + Internal::GlobalMemoryRegion& globalMemoryRegion = m_globalMemoryRegionView.MemoryRegion(); + globalMemoryRegion.AttachedProcessesCount.fetch_add(1); + } + + if (SUCCEEDED(hr)) + { + hr = m_controlChannelMemoryMapView.CreateOrOpen("Host_Mlos.ControlChannel", SharedMemorySize); + } + + if (SUCCEEDED(hr)) + { + hr = m_feedbackChannelMemoryMapView.CreateOrOpen("Host_Mlos.FeedbackChannel", SharedMemorySize); + } + + // FIXME: Use non-backslashes for Linux environments. + // + if (SUCCEEDED(hr)) + { + hr = m_controlChannelPolicy.m_notificationEvent.CreateOrOpen("Global\\ControlChannel_Event"); + } + + if (SUCCEEDED(hr)) + { + hr = m_feedbackChannelPolicy.m_notificationEvent.CreateOrOpen("Global\\FeedbackChannel_Event"); + } + + if (FAILED(hr)) + { + // Close all the shared maps if we fail to create one. + // + m_globalMemoryRegionView.Close(); + m_controlChannelMemoryMapView.Close(); + m_feedbackChannelMemoryMapView.Close(); + m_controlChannelPolicy.m_notificationEvent.Close(); + m_feedbackChannelPolicy.m_notificationEvent.Close(); + } + + return hr; +} + +//---------------------------------------------------------------------------- +// NAME: InterProcessMlosContext::Constructor +// +// PURPOSE: +// Creates InterProcessMlosContext. +// +// NOTES: +// +InterProcessMlosContext::InterProcessMlosContext(InterProcessMlosContextInitializer&& initializer) noexcept + : MlosContext(initializer.m_globalMemoryRegionView.MemoryRegion(), m_controlChannel, m_controlChannel, m_feedbackChannel), + m_contextInitializer(std::move(initializer)), + m_controlChannel( + m_contextInitializer.m_globalMemoryRegionView.MemoryRegion().ControlChannelSynchronization, + m_contextInitializer.m_controlChannelMemoryMapView, + std::move(m_contextInitializer.m_controlChannelPolicy)), + m_feedbackChannel( + m_contextInitializer.m_globalMemoryRegionView.MemoryRegion().FeedbackChannelSynchronization, + m_contextInitializer.m_feedbackChannelMemoryMapView, + std::move(m_contextInitializer.m_feedbackChannelPolicy)) +{ +} + +//---------------------------------------------------------------------------- +// NAME: InterProcessMlosContext::Destructor +// +// PURPOSE: +// Destroys InterProcessMlosContext object. +// +// NOTES: +// +InterProcessMlosContext::~InterProcessMlosContext() +{ + // Decrease the usage counter. + // + uint32_t usageCount = m_globalMemoryRegion.AttachedProcessesCount.fetch_sub(1); + if (usageCount == 1) + { + // This is the last process using shared memory map. + // + m_contextInitializer.m_globalMemoryRegionView.CleanupOnClose = true; + m_contextInitializer.m_controlChannelMemoryMapView.CleanupOnClose = true; + m_contextInitializer.m_feedbackChannelMemoryMapView.CleanupOnClose = true; + m_controlChannel.ChannelPolicy.m_notificationEvent.CleanupOnClose = true; + m_feedbackChannel.ChannelPolicy.m_notificationEvent.CleanupOnClose = true; + } +} +} +} diff --git a/source/Mlos.Core/InterProcessMlosContext.h b/source/Mlos.Core/InterProcessMlosContext.h new file mode 100644 index 0000000000..b692b06387 --- /dev/null +++ b/source/Mlos.Core/InterProcessMlosContext.h @@ -0,0 +1,93 @@ +//********************************************************************* +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root +// for license information. +// +// @File: InterProcessMlosContext.h +// +// Purpose: +// +// +// Notes: +// +// +//********************************************************************* + +#pragma once + +namespace Mlos +{ +namespace Core +{ +//---------------------------------------------------------------------------- +// NAME: InterProcessMlosContextInitializer +// +// PURPOSE: +// Helper class used to initialize shared memory for inter-process MlosContexts. +// +// NOTES: +// +class InterProcessMlosContextInitializer +{ +public: + InterProcessMlosContextInitializer() {} + + _Check_return_ + HRESULT Initialize(); + + InterProcessMlosContextInitializer(InterProcessMlosContextInitializer&& initializer) noexcept; + + InterProcessMlosContextInitializer(const InterProcessMlosContextInitializer&) = delete; + + InterProcessMlosContextInitializer& operator=(const InterProcessMlosContextInitializer&) = delete; + +private: + // Global shared memory region. + // + SharedMemoryRegionView m_globalMemoryRegionView; + + // Named shared memory for Telemetry and Control Channel. + // + SharedMemoryMapView m_controlChannelMemoryMapView; + + // Named shared memory for Feedback Channel. + // + SharedMemoryMapView m_feedbackChannelMemoryMapView; + + // Channel policy for control channel. + // + InterProcessSharedChannelPolicy m_controlChannelPolicy; + + // Channel policy for feedback channel. + // + InterProcessSharedChannelPolicy m_feedbackChannelPolicy; + + friend class InterProcessMlosContext; +}; + +//---------------------------------------------------------------------------- +// NAME: InterProcessMlosContext +// +// PURPOSE: +// Implementation of an inter-process MlosContext. +// +class InterProcessMlosContext : public MlosContext +{ +public: + InterProcessMlosContext(InterProcessMlosContextInitializer&&) noexcept; + + ~InterProcessMlosContext(); + +private: + InterProcessMlosContextInitializer m_contextInitializer; + + InterProcessSharedChannel m_controlChannel; + + InterProcessSharedChannel m_feedbackChannel; + + NamedEvent m_controlChannelNamedEvent; + + NamedEvent m_feedbackChannelNamedEvent; +}; +} +} diff --git a/source/Mlos.Core/InternalMlosContext.cpp b/source/Mlos.Core/InternalMlosContext.cpp new file mode 100644 index 0000000000..3d23f5ae00 --- /dev/null +++ b/source/Mlos.Core/InternalMlosContext.cpp @@ -0,0 +1,100 @@ +//********************************************************************* +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root +// for license information. +// +// @File: InternalMlosContext.cpp +// +// Purpose: +// +// +// Notes: +// +// +//********************************************************************* + +#include "Mlos.Core.h" +#include "Mlos.Core.inl" + +namespace Mlos +{ +namespace Core +{ +//---------------------------------------------------------------------------- +// NAME: InternalMlosContextInitializer::Constructor. +// +// PURPOSE: +// Move constructor. +// +// NOTES: +// +InternalMlosContextInitializer::InternalMlosContextInitializer(InternalMlosContextInitializer&& initializer) noexcept + : m_globalMemoryRegionView(std::move(initializer.m_globalMemoryRegionView)), + m_controlChannelMemoryMapView(std::move(initializer.m_controlChannelMemoryMapView)), + m_feedbackChannelMemoryMapView(std::move(initializer.m_feedbackChannelMemoryMapView)) +{ +} + +//---------------------------------------------------------------------------- +// NAME: InternalMlosContextInitializer::Initialize +// +// PURPOSE: +// Opens the shared memory used for the communication channel. +// +// NOTES: +// +_Check_return_ +HRESULT InternalMlosContextInitializer::Initialize() +{ + const size_t SharedMemorySize = 65536; + + HRESULT hr = S_OK; + + if (SUCCEEDED(hr)) + { + hr = m_globalMemoryRegionView.Create("Test_Mlos.GlobalMemory", SharedMemorySize); + } + + if (SUCCEEDED(hr)) + { + hr = m_controlChannelMemoryMapView.Create("Test_SharedChannelMemory", SharedMemorySize); + } + + if (SUCCEEDED(hr)) + { + hr = m_feedbackChannelMemoryMapView.Create("Test_FeedbackChannelMemory", SharedMemorySize); + } + + if (FAILED(hr)) + { + // Close all the shared maps if we fail to create one. + // + m_globalMemoryRegionView.Close(); + m_controlChannelMemoryMapView.Close(); + m_feedbackChannelMemoryMapView.Close(); + } + + return hr; +} + +//---------------------------------------------------------------------------- +// NAME: InternalMlosContext::Constructor +// +// PURPOSE: +// Creates InternalMlosContext. +// +// NOTES: +// +InternalMlosContext::InternalMlosContext(InternalMlosContextInitializer&& initializer) noexcept + : MlosContext(initializer.m_globalMemoryRegionView.MemoryRegion(), m_controlChannel, m_controlChannel, m_feedbackChannel), + m_contextInitializer(std::move(initializer)), + m_controlChannel( + m_contextInitializer.m_globalMemoryRegionView.MemoryRegion().ControlChannelSynchronization, + m_contextInitializer.m_controlChannelMemoryMapView), + m_feedbackChannel( + m_contextInitializer.m_globalMemoryRegionView.MemoryRegion().FeedbackChannelSynchronization, + m_contextInitializer.m_feedbackChannelMemoryMapView) +{ +} +} +} diff --git a/source/Mlos.Core/InternalMlosContext.h b/source/Mlos.Core/InternalMlosContext.h new file mode 100644 index 0000000000..9d0a8addca --- /dev/null +++ b/source/Mlos.Core/InternalMlosContext.h @@ -0,0 +1,84 @@ +//********************************************************************* +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root +// for license information. +// +// @File: InternalMlosContext.h +// +// Purpose: +// +// +// Notes: +// +// +//********************************************************************* + +#pragma once + +namespace Mlos +{ +namespace Core +{ +//---------------------------------------------------------------------------- +// NAME: InternalMlosContextInitializer +// +// PURPOSE: +// Helper class used to initialize shared memory for TestMlosContext. +// +// NOTES: +// +class InternalMlosContextInitializer +{ +public: + InternalMlosContextInitializer() {} + + _Check_return_ + HRESULT Initialize(); + + InternalMlosContextInitializer(InternalMlosContextInitializer&& initializer) noexcept; + + InternalMlosContextInitializer(const InternalMlosContextInitializer&) = delete; + + InternalMlosContextInitializer& operator=(const InternalMlosContextInitializer&) = delete; + +private: + // Global shared memory region. + // + SharedMemoryRegionView m_globalMemoryRegionView; + + // Named shared memory for Telemetry and Control Channel. + // + SharedMemoryMapView m_controlChannelMemoryMapView; + + // Named shared memory for Feedback Channel. + // + SharedMemoryMapView m_feedbackChannelMemoryMapView; + + friend class InternalMlosContext; +}; + +//---------------------------------------------------------------------------- +// NAME: InternalMlosContext +// +// PURPOSE: +// Simple implementation of MlosContext. +// Single channel used to send control and telemetry messages. +// Channel does not use OS synchronization primitive, sender and receiver thread should be running inside the same process. +// +// NOTES: +// Intended to use only in the test. +// +class InternalMlosContext : public MlosContext +{ +public: + InternalMlosContext(InternalMlosContextInitializer&&) noexcept; + +private: + InternalMlosContextInitializer m_contextInitializer; + + TestSharedChannel m_controlChannel; + + TestSharedChannel m_feedbackChannel; +}; +} +} diff --git a/source/Mlos.Core/Mlos.Core.h b/source/Mlos.Core/Mlos.Core.h index 794b0e0ddf..f15d50b93c 100644 --- a/source/Mlos.Core/Mlos.Core.h +++ b/source/Mlos.Core/Mlos.Core.h @@ -70,6 +70,7 @@ constexpr int32_t INVALID_FD_VALUE = -1; #endif #define UNUSED(x) (void)x +#define ReturnIfFail(hr) if (FAILED(hr)) return hr; #include "MlosPlatform.h" @@ -125,6 +126,8 @@ constexpr int32_t INVALID_FD_VALUE = -1; // Include Mlos Client API. // #include "MlosContext.h" +#include "InternalMlosContext.h" +#include "InterProcessMlosContext.h" #include "StaticSingleton.h" #include "StaticVector.h" diff --git a/source/Mlos.Core/Mlos.Core.vcxproj b/source/Mlos.Core/Mlos.Core.vcxproj index 201d017dcc..df4e141b37 100644 --- a/source/Mlos.Core/Mlos.Core.vcxproj +++ b/source/Mlos.Core/Mlos.Core.vcxproj @@ -26,6 +26,8 @@ + + Create @@ -43,6 +45,8 @@ + + diff --git a/source/Mlos.Core/Mlos.Core.vcxproj.filters b/source/Mlos.Core/Mlos.Core.vcxproj.filters index b1e5df186a..0098815f25 100644 --- a/source/Mlos.Core/Mlos.Core.vcxproj.filters +++ b/source/Mlos.Core/Mlos.Core.vcxproj.filters @@ -14,7 +14,8 @@ MemoryRegions - + + @@ -59,6 +60,9 @@ + + + diff --git a/source/Mlos.Core/MlosContext.cpp b/source/Mlos.Core/MlosContext.cpp index 88ea0eefb7..a1f1b106a0 100644 --- a/source/Mlos.Core/MlosContext.cpp +++ b/source/Mlos.Core/MlosContext.cpp @@ -226,165 +226,5 @@ bool MlosContext::IsFeedbackChannelActive() { return !(m_feedbackChannel.Sync.TerminateChannel); } - -//---------------------------------------------------------------------------- -// NAME: InternalMlosContextInitializer::Constructor. -// -// PURPOSE: -// Move constructor. -// -// NOTES: -// -InternalMlosContextInitializer::InternalMlosContextInitializer(InternalMlosContextInitializer&& initializer) noexcept - : m_globalMemoryRegionView(std::move(initializer.m_globalMemoryRegionView)), - m_controlChannelMemoryMapView(std::move(initializer.m_controlChannelMemoryMapView)), - m_feedbackChannelMemoryMapView(std::move(initializer.m_feedbackChannelMemoryMapView)) -{ -} - -//---------------------------------------------------------------------------- -// NAME: InternalMlosContextInitializer::Initialize -// -// PURPOSE: -// Opens the shared memory used for the communication channel. -// -// NOTES: -// -HRESULT InternalMlosContextInitializer::Initialize() -{ - const size_t SharedMemorySize = 65536; - - HRESULT hr = m_globalMemoryRegionView.Create("Test_Mlos.GlobalMemory", SharedMemorySize); - if (FAILED(hr)) - { - return hr; - } - - hr = m_controlChannelMemoryMapView.Create("Test_SharedChannelMemory", SharedMemorySize); - if (FAILED(hr)) - { - return hr; - } - - hr = m_feedbackChannelMemoryMapView.Create("Test_FeedbackChannelMemory", SharedMemorySize); - if (FAILED(hr)) - { - return hr; - } - - return hr; -} - -//---------------------------------------------------------------------------- -// NAME: InternalMlosContext::Constructor -// -// PURPOSE: -// Creates InternalMlosContext. -// -// NOTES: -// -InternalMlosContext::InternalMlosContext(InternalMlosContextInitializer&& initializer) noexcept - : MlosContext(initializer.m_globalMemoryRegionView.MemoryRegion(), m_controlChannel, m_controlChannel, m_feedbackChannel), - m_contextInitializer(std::move(initializer)), - m_controlChannel( - m_contextInitializer.m_globalMemoryRegionView.MemoryRegion().ControlChannelSynchronization, - m_contextInitializer.m_controlChannelMemoryMapView), - m_feedbackChannel( - m_contextInitializer.m_globalMemoryRegionView.MemoryRegion().FeedbackChannelSynchronization, - m_contextInitializer.m_feedbackChannelMemoryMapView) -{ -} - -//---------------------------------------------------------------------------- -// NAME: InterProcessMlosContextInitializer::Constructor. -// -// PURPOSE: -// Move constructor. -// -// NOTES: -// -InterProcessMlosContextInitializer::InterProcessMlosContextInitializer(InterProcessMlosContextInitializer&& initializer) noexcept - : m_globalMemoryRegionView(std::move(initializer.m_globalMemoryRegionView)), - m_controlChannelMemoryMapView(std::move(initializer.m_controlChannelMemoryMapView)), - m_feedbackChannelMemoryMapView(std::move(initializer.m_feedbackChannelMemoryMapView)), - m_controlChannelPolicy(std::move(initializer.m_controlChannelPolicy)), - m_feedbackChannelPolicy(std::move(initializer.m_feedbackChannelPolicy)) -{ -} - -//---------------------------------------------------------------------------- -// NAME: InterProcessMlosContextInitializer::Initialize -// -// PURPOSE: -// Opens the shared memory and synchronization primitives used for the communication channel. -// -// NOTES: -// -HRESULT InterProcessMlosContextInitializer::Initialize() -{ - // #TODO const as codegen, pass a config struct ? - // - const size_t SharedMemorySize = 65536; - - // TODO: Make these config regions configurable to support multiple processes. - - // Note: Shared memory mapping name must start with "Host_" prefix, to be accessible from certain applications. - - HRESULT hr = m_globalMemoryRegionView.CreateOrOpen("Host_Mlos.GlobalMemory", SharedMemorySize); - if (FAILED(hr)) - { - return hr; - } - - hr = m_controlChannelMemoryMapView.CreateOrOpen("Host_Mlos.ControlChannel", SharedMemorySize); - if (FAILED(hr)) - { - return hr; - } - - hr = m_feedbackChannelMemoryMapView.CreateOrOpen("Host_Mlos.FeedbackChannel", SharedMemorySize); - if (FAILED(hr)) - { - return hr; - } - - // FIXME: Use non-backslashes for Linux environments. - - hr = m_controlChannelPolicy.m_notificationEvent.CreateOrOpen("Global\\ControlChannel_Event"); - if (FAILED(hr)) - { - return hr; - } - - hr = m_feedbackChannelPolicy.m_notificationEvent.CreateOrOpen("Global\\FeedbackChannel_Event"); - if (FAILED(hr)) - { - return hr; - } - - return hr; -} - -//---------------------------------------------------------------------------- -// NAME: InterProcessMlosContext::Constructor -// -// PURPOSE: -// Creates InterProcessMlosContext. -// -// NOTES: -// -InterProcessMlosContext::InterProcessMlosContext(InterProcessMlosContextInitializer&& initializer) noexcept - : MlosContext(initializer.m_globalMemoryRegionView.MemoryRegion(), m_controlChannel, m_controlChannel, m_feedbackChannel), - m_contextInitializer(std::move(initializer)), - m_controlChannel( - m_contextInitializer.m_globalMemoryRegionView.MemoryRegion().ControlChannelSynchronization, - m_contextInitializer.m_controlChannelMemoryMapView, - std::move(m_contextInitializer.m_controlChannelPolicy)), - m_feedbackChannel( - m_contextInitializer.m_globalMemoryRegionView.MemoryRegion().FeedbackChannelSynchronization, - m_contextInitializer.m_feedbackChannelMemoryMapView, - std::move(m_contextInitializer.m_feedbackChannelPolicy)) -{ -} } } diff --git a/source/Mlos.Core/MlosContext.h b/source/Mlos.Core/MlosContext.h index 5751d4dccd..f6c887f282 100644 --- a/source/Mlos.Core/MlosContext.h +++ b/source/Mlos.Core/MlosContext.h @@ -75,7 +75,7 @@ class MlosContext bool IsFeedbackChannelActive(); -private: +protected: // Creates a shared memory view and registers it with Mlos Agent. // template @@ -111,130 +111,5 @@ class MlosContext template friend class ComponentConfig; }; - -//---------------------------------------------------------------------------- -// NAME: InternalMlosContextInitializer -// -// PURPOSE: -// Helper class used to initialize shared memory for TestMlosContext. -// -// NOTES: -// -class InternalMlosContextInitializer -{ -public: - InternalMlosContextInitializer() {} - - HRESULT Initialize(); - - InternalMlosContextInitializer(InternalMlosContextInitializer&& initializer) noexcept; - - InternalMlosContextInitializer(const InternalMlosContextInitializer&) = delete; - - InternalMlosContextInitializer& operator=(const InternalMlosContextInitializer&) = delete; - -public: - // Global shared memory region. - // - SharedMemoryRegionView m_globalMemoryRegionView; - - // Named shared memory for Telemetry and Control Channel. - // - SharedMemoryMapView m_controlChannelMemoryMapView; - - // Named shared memory for Feedback Channel. - // - SharedMemoryMapView m_feedbackChannelMemoryMapView; -}; - -//---------------------------------------------------------------------------- -// NAME: InternalMlosContext -// -// PURPOSE: -// Simple implementation of MlosContext. -// Single channel used to send control and telemetry messages. -// Channel does not use OS synchronization primitive, sender and receiver thread should be running inside the same process. -// -// NOTES: -// Intended to use only in the test. -// -class InternalMlosContext : public MlosContext -{ -public: - InternalMlosContext(InternalMlosContextInitializer&&) noexcept; - -private: - InternalMlosContextInitializer m_contextInitializer; - - TestSharedChannel m_controlChannel; - - TestSharedChannel m_feedbackChannel; -}; - -//---------------------------------------------------------------------------- -// NAME: InterProcessMlosContextInitializer -// -// PURPOSE: -// Helper class used to initialize shared memory for inter-process MlosContexts. -// -// NOTES: -// -class InterProcessMlosContextInitializer -{ -public: - InterProcessMlosContextInitializer() {} - - HRESULT Initialize(); - - InterProcessMlosContextInitializer(InterProcessMlosContextInitializer&& initializer) noexcept; - - InterProcessMlosContextInitializer(const InterProcessMlosContextInitializer&) = delete; - - InterProcessMlosContextInitializer& operator=(const InterProcessMlosContextInitializer&) = delete; - -public: - // Global shared memory region. - // - SharedMemoryRegionView m_globalMemoryRegionView; - - // Named shared memory for Telemetry and Control Channel. - // - SharedMemoryMapView m_controlChannelMemoryMapView; - - // Named shared memory for Feedback Channel. - // - SharedMemoryMapView m_feedbackChannelMemoryMapView; - - // Channel policy for control channel. - // - InterProcessSharedChannelPolicy m_controlChannelPolicy; - - // Channel policy for feedback channel. - // - InterProcessSharedChannelPolicy m_feedbackChannelPolicy; -}; - -//---------------------------------------------------------------------------- -// NAME: InterProcessMlosContext -// -// PURPOSE: -// Implementation of an inter-process MlosContext. -// -class InterProcessMlosContext : public MlosContext -{ -public: - InterProcessMlosContext(InterProcessMlosContextInitializer&&) noexcept; - -private: - InterProcessMlosContextInitializer m_contextInitializer; - - InterProcessSharedChannel m_controlChannel; - - InterProcessSharedChannel m_feedbackChannel; - - NamedEvent m_controlChannelNamedEvent; - - NamedEvent m_feedbackChannelNamedEvent; -}; } } diff --git a/source/Mlos.Core/NamedEvent.Linux.cpp b/source/Mlos.Core/NamedEvent.Linux.cpp index 828785c3c6..942c940d3b 100644 --- a/source/Mlos.Core/NamedEvent.Linux.cpp +++ b/source/Mlos.Core/NamedEvent.Linux.cpp @@ -15,8 +15,9 @@ #include "Mlos.Core.h" -#include #include +#include +#include using namespace Mlos::Core; @@ -28,7 +29,9 @@ namespace Core // NAME: NamedEvent::Constructor. // NamedEvent::NamedEvent() noexcept - : m_semaphore(SEM_FAILED) + : m_semaphore(SEM_FAILED), + m_namedEventName(nullptr), + CleanupOnClose(false) { } @@ -39,7 +42,9 @@ NamedEvent::NamedEvent() noexcept // Move constructor. // NamedEvent::NamedEvent(NamedEvent&& namedEvent) noexcept - : m_semaphore(std::exchange(namedEvent.m_semaphore, SEM_FAILED)) + : m_semaphore(std::exchange(namedEvent.m_semaphore, SEM_FAILED)), + m_namedEventName(std::exchange(namedEvent.m_namedEventName, nullptr)), + CleanupOnClose(std::exchange(namedEvent.CleanupOnClose, false)) { } @@ -57,6 +62,12 @@ HRESULT NamedEvent::CreateOrOpen(const char* const namedEventName) noexcept { HRESULT hr = S_OK; + m_namedEventName = strdup(namedEventName); + if (m_namedEventName == nullptr) + { + return E_OUTOFMEMORY; + } + m_semaphore = sem_open(namedEventName, O_CREAT, S_IRUSR | S_IWUSR, 0); if (m_semaphore == SEM_FAILED) { @@ -85,10 +96,38 @@ HRESULT NamedEvent::Open(const char* const namedEventName) noexcept // NAME: NamedEvent::Destructor. // NamedEvent::~NamedEvent() +{ + Close(); +} + +//---------------------------------------------------------------------------- +// NAME: NamedEvent::Close +// +// PURPOSE: +// Closes a named event object. +// +void NamedEvent::Close() { if (m_semaphore != SEM_FAILED) { sem_close(m_semaphore); + m_semaphore = SEM_FAILED; + + if (CleanupOnClose) + { + if (m_namedEventName != nullptr) + { + sem_unlink(m_namedEventName); + } + + CleanupOnClose = false; + } + } + + if (m_namedEventName != nullptr) + { + free(m_namedEventName); + m_namedEventName = nullptr; } } diff --git a/source/Mlos.Core/NamedEvent.Linux.h b/source/Mlos.Core/NamedEvent.Linux.h index 35c0746613..c4b39c83e2 100644 --- a/source/Mlos.Core/NamedEvent.Linux.h +++ b/source/Mlos.Core/NamedEvent.Linux.h @@ -41,6 +41,10 @@ class NamedEvent _Check_return_ HRESULT Open(const char* const namedEventName) noexcept; + // Closes a named event object. + // + void Close(); + // Sets the named event object to the signaled state. // _Check_return_ @@ -51,8 +55,14 @@ class NamedEvent _Check_return_ HRESULT Wait(); +public: + // Indicates if we should cleanup OS resources when closing the shared memory map view. + // + bool CleanupOnClose; + private: sem_t* m_semaphore; + char* m_namedEventName; }; } diff --git a/source/Mlos.Core/NamedEvent.Window.cpp b/source/Mlos.Core/NamedEvent.Window.cpp index 32f31216b7..8a45f3a200 100644 --- a/source/Mlos.Core/NamedEvent.Window.cpp +++ b/source/Mlos.Core/NamedEvent.Window.cpp @@ -25,7 +25,8 @@ namespace Core // NAME: NamedEvent::Constructor. // NamedEvent::NamedEvent() noexcept - : m_hEvent(nullptr) + : m_hEvent(nullptr), + CleanupOnClose(false) { } @@ -36,7 +37,8 @@ NamedEvent::NamedEvent() noexcept // Move constructor. // NamedEvent::NamedEvent(NamedEvent&& namedEvent) noexcept - : m_hEvent(std::exchange(namedEvent.m_hEvent, nullptr)) + : m_hEvent(std::exchange(namedEvent.m_hEvent, nullptr)), + CleanupOnClose(std::exchange(namedEvent.CleanupOnClose, false)) { } @@ -101,7 +103,7 @@ HRESULT NamedEvent::CreateOrOpen(const char* const namedEventName) noexcept // NAME: NamedEvent::Open // // PURPOSE: -// Opens a named event object. +// Opens an existing named event object. // // RETURNS: // HRESULT. @@ -141,6 +143,10 @@ HRESULT NamedEvent::Open(const char* const namedEventName) noexcept // void NamedEvent::Close() { + // Windows OS will remove the named event once the last process using it will terminate, just reset the flag. + // + CleanupOnClose = false; + CloseHandle(m_hEvent); m_hEvent = nullptr; } diff --git a/source/Mlos.Core/NamedEvent.Window.h b/source/Mlos.Core/NamedEvent.Window.h index 1ca5ff5b21..8ac5aa8901 100644 --- a/source/Mlos.Core/NamedEvent.Window.h +++ b/source/Mlos.Core/NamedEvent.Window.h @@ -33,7 +33,7 @@ class NamedEvent _Check_return_ HRESULT CreateOrOpen(const char* const namedEventName) noexcept; - // Opens a named event object. + // Opens an existing named event object. // _Check_return_ HRESULT Open(const char* const namedEventName) noexcept; @@ -56,6 +56,11 @@ class NamedEvent // bool IsInvalid(); + // Indicates if we should cleanup OS resources when closing the shared memory map view. + // No-op on Windows. + // + bool CleanupOnClose; + private: HANDLE m_hEvent; }; diff --git a/source/Mlos.Core/SharedMemoryMapView.Linux.cpp b/source/Mlos.Core/SharedMemoryMapView.Linux.cpp index c3c0c95078..3e371650b9 100644 --- a/source/Mlos.Core/SharedMemoryMapView.Linux.cpp +++ b/source/Mlos.Core/SharedMemoryMapView.Linux.cpp @@ -16,19 +16,33 @@ #include "Mlos.Core.h" #include -#include +#include +#include #include +#include -using namespace Mlos::Core; - +namespace Mlos +{ +namespace Core +{ //---------------------------------------------------------------------------- // NAME: SharedMemoryMapView::Constructor. // SharedMemoryMapView::SharedMemoryMapView() noexcept : MemSize(0), m_fdSharedMemory(INVALID_FD_VALUE), - Buffer(nullptr) + m_sharedMemoryMapName(nullptr), + Buffer(nullptr), + CleanupOnClose(false) +{ +} + +//---------------------------------------------------------------------------- +// NAME: SharedMemoryMapView::Destructor +// +SharedMemoryMapView::~SharedMemoryMapView() { + Close(); } //---------------------------------------------------------------------------- @@ -40,7 +54,9 @@ SharedMemoryMapView::SharedMemoryMapView() noexcept SharedMemoryMapView::SharedMemoryMapView(SharedMemoryMapView&& sharedMemoryMapView) noexcept : MemSize(std::exchange(sharedMemoryMapView.MemSize, 0)), m_fdSharedMemory(std::exchange(sharedMemoryMapView.m_fdSharedMemory, INVALID_FD_VALUE)), - Buffer(std::exchange(sharedMemoryMapView.Buffer, nullptr)) + m_sharedMemoryMapName(std::exchange(sharedMemoryMapView.m_sharedMemoryMapName, nullptr)), + Buffer(std::exchange(sharedMemoryMapView.Buffer, nullptr)), + CleanupOnClose(std::exchange(sharedMemoryMapView.CleanupOnClose, 0)) { } @@ -59,6 +75,12 @@ HRESULT SharedMemoryMapView::Create(const char* const sharedMemoryMapName, size_ { shm_unlink(sharedMemoryMapName); + m_sharedMemoryMapName = strdup(sharedMemoryMapName); + if (m_sharedMemoryMapName == nullptr) + { + return E_OUTOFMEMORY; + } + m_fdSharedMemory = shm_open(sharedMemoryMapName, O_EXCL | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); return MapMemoryView(memSize); @@ -77,6 +99,12 @@ HRESULT SharedMemoryMapView::Create(const char* const sharedMemoryMapName, size_ // HRESULT SharedMemoryMapView::CreateOrOpen(const char* const sharedMemoryMapName, size_t memSize) noexcept { + m_sharedMemoryMapName = strdup(sharedMemoryMapName); + if (m_sharedMemoryMapName == nullptr) + { + return E_OUTOFMEMORY; + } + m_fdSharedMemory = shm_open(sharedMemoryMapName, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); return MapMemoryView(memSize); @@ -95,38 +123,112 @@ HRESULT SharedMemoryMapView::CreateOrOpen(const char* const sharedMemoryMapName, // HRESULT SharedMemoryMapView::Open(const char* const sharedMemoryMapName) noexcept { + m_sharedMemoryMapName = strdup(sharedMemoryMapName); + if (m_sharedMemoryMapName == nullptr) + { + return E_OUTOFMEMORY; + } + m_fdSharedMemory = shm_open(sharedMemoryMapName, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + if (m_fdSharedMemory == INVALID_FD_VALUE) + { + return HRESULT_FROM_ERRNO(errno); + } return MapMemoryView(0 /* memSize */); } HRESULT SharedMemoryMapView::MapMemoryView(size_t memSize) noexcept { - if (m_fdSharedMemory == -1) + if (m_fdSharedMemory == INVALID_FD_VALUE) { return HRESULT_FROM_ERRNO(errno); } - if (ftruncate(m_fdSharedMemory, memSize) == -1) + HRESULT hr = S_OK; + + if (memSize == 0) { - return HRESULT_FROM_ERRNO(errno); + // Obtain the size of the shared map. + // + struct stat statBuffer = { 0 }; + if (fstat(m_fdSharedMemory, &statBuffer) != -1) + { + memSize = statBuffer.st_size; + } + else + { + hr = HRESULT_FROM_ERRNO(errno); + } + } + + if (SUCCEEDED(hr)) + { + if (ftruncate(m_fdSharedMemory, memSize) == -1) + { + hr = HRESULT_FROM_ERRNO(errno); + } } - Buffer = mmap(0, memSize, PROT_READ | PROT_WRITE, MAP_SHARED, m_fdSharedMemory, 0); + if (SUCCEEDED(hr)) + { + void* pointer = mmap(0, memSize, PROT_READ | PROT_WRITE, MAP_SHARED, m_fdSharedMemory, 0); + if (pointer != MAP_FAILED) + { + Buffer.Pointer = reinterpret_cast(pointer); + MemSize = memSize; + } + else + { + hr = HRESULT_FROM_ERRNO(errno); + } + } - //#handle failure - MemSize = memSize; + if (FAILED(hr)) + { + Close(); + } - return S_OK; + return hr; } //---------------------------------------------------------------------------- -// NAME: SharedMemoryMapView::Destructor +// NAME: SharedMemoryMapView::Close // -SharedMemoryMapView::~SharedMemoryMapView() +// PURPOSE: +// Closes a shared memory view. +// +void SharedMemoryMapView::Close() { + if (Buffer.Pointer != nullptr) + { + munmap(Buffer.Pointer, MemSize); + Buffer = nullptr; + + MemSize = 0; + } + if (m_fdSharedMemory != INVALID_FD_VALUE) { close(m_fdSharedMemory); + m_fdSharedMemory = INVALID_FD_VALUE; + + if (CleanupOnClose) + { + if (m_sharedMemoryMapName != nullptr) + { + shm_unlink(m_sharedMemoryMapName); + } + + CleanupOnClose = false; + } + } + + if (m_sharedMemoryMapName != nullptr) + { + free(m_sharedMemoryMapName); + m_sharedMemoryMapName = nullptr; } } +} +} diff --git a/source/Mlos.Core/SharedMemoryMapView.Linux.h b/source/Mlos.Core/SharedMemoryMapView.Linux.h index 47ad2cccaf..c8a7e10365 100644 --- a/source/Mlos.Core/SharedMemoryMapView.Linux.h +++ b/source/Mlos.Core/SharedMemoryMapView.Linux.h @@ -43,6 +43,10 @@ class SharedMemoryMapView _Check_return_ HRESULT CreateOrOpen(const char* const sharedMemoryMapName, size_t memSize) noexcept; + // Closes a shared memory view. + // + void Close(); + private: _Check_return_ HRESULT MapMemoryView(size_t memSize) noexcept; @@ -51,8 +55,13 @@ class SharedMemoryMapView size_t MemSize; BytePtr Buffer; + // Indicates if we should cleanup OS resources when closing the shared memory map view. + // + bool CleanupOnClose; + private: int m_fdSharedMemory; + char* m_sharedMemoryMapName; }; } diff --git a/source/Mlos.Core/SharedMemoryMapView.Windows.cpp b/source/Mlos.Core/SharedMemoryMapView.Windows.cpp index 532082b960..7fc9534562 100644 --- a/source/Mlos.Core/SharedMemoryMapView.Windows.cpp +++ b/source/Mlos.Core/SharedMemoryMapView.Windows.cpp @@ -28,7 +28,8 @@ namespace Core SharedMemoryMapView::SharedMemoryMapView() noexcept : MemSize(0), m_hMapFile(nullptr), - Buffer(nullptr) + Buffer(nullptr), + CleanupOnClose(false) { } @@ -41,7 +42,8 @@ SharedMemoryMapView::SharedMemoryMapView() noexcept SharedMemoryMapView::SharedMemoryMapView(SharedMemoryMapView&& sharedMemoryMapView) noexcept : MemSize(std::exchange(sharedMemoryMapView.MemSize, 0)), m_hMapFile(std::exchange(sharedMemoryMapView.m_hMapFile, nullptr)), - Buffer(std::exchange(sharedMemoryMapView.Buffer, nullptr)) + Buffer(std::exchange(sharedMemoryMapView.Buffer, nullptr)), + CleanupOnClose(std::exchange(sharedMemoryMapView.CleanupOnClose, 0)) { } @@ -57,7 +59,7 @@ SharedMemoryMapView::~SharedMemoryMapView() // NAME: SharedMemoryMapView::Create // // PURPOSE: -// Creates a shared memory map view. +// Creates a new shared memory map view. // // RETURNS: // HRESULT. @@ -245,6 +247,10 @@ HRESULT SharedMemoryMapView::MapMemoryView(size_t memSize) noexcept // void SharedMemoryMapView::Close() { + // Windows OS will remove the shared memory map once the last process detaches from it, just reset the flag. + // + CleanupOnClose = false; + UnmapViewOfFile(Buffer.Pointer); Buffer.Pointer = nullptr; diff --git a/source/Mlos.Core/SharedMemoryMapView.Windows.h b/source/Mlos.Core/SharedMemoryMapView.Windows.h index 99555c4d33..5660374e09 100644 --- a/source/Mlos.Core/SharedMemoryMapView.Windows.h +++ b/source/Mlos.Core/SharedMemoryMapView.Windows.h @@ -64,6 +64,11 @@ class SharedMemoryMapView size_t MemSize; BytePtr Buffer; + // Indicates if we should cleanup OS resources when closing the shared memory map view. + // No-op on Windows. + // + bool CleanupOnClose; + private: HANDLE m_hMapFile; }; diff --git a/source/Mlos.Grpc.Binplace/Makefile b/source/Mlos.Grpc.Binplace/Makefile new file mode 100644 index 0000000000..72387073da --- /dev/null +++ b/source/Mlos.Grpc.Binplace/Makefile @@ -0,0 +1,2 @@ +RelativePathToProjectRoot := ../.. +include $(RelativePathToProjectRoot)/build/DotnetWrapper.mk diff --git a/source/Mlos.Grpc.Binplace/Mlos.Grpc.Binplace.csproj b/source/Mlos.Grpc.Binplace/Mlos.Grpc.Binplace.csproj new file mode 100644 index 0000000000..7bb43bee46 --- /dev/null +++ b/source/Mlos.Grpc.Binplace/Mlos.Grpc.Binplace.csproj @@ -0,0 +1,33 @@ + + + + + {83BA54CA-1BC2-4591-9109-314BF2A0190F} + Both + true + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/Mlos.NetCore.UnitTest/HashTableTests.cs b/source/Mlos.NetCore.UnitTest/HashTableTests.cs index 78452928c6..13abfbd65b 100644 --- a/source/Mlos.NetCore.UnitTest/HashTableTests.cs +++ b/source/Mlos.NetCore.UnitTest/HashTableTests.cs @@ -40,6 +40,7 @@ public HashTableTests() public void Insert() { using var sharedMemoryRegionView = SharedMemoryRegionView.Create(SharedMemoryMapName, SharedMemorySize); + sharedMemoryRegionView.CleanupOnClose = true; MlosProxyInternal.SharedConfigMemoryRegion sharedConfigMemoryRegion = sharedMemoryRegionView.MemoryRegion(); diff --git a/source/Mlos.NetCore.UnitTest/Mlos.NetCore.UnitTest.csproj b/source/Mlos.NetCore.UnitTest/Mlos.NetCore.UnitTest.csproj index d8bc52a0ff..a95c6cd6e5 100644 --- a/source/Mlos.NetCore.UnitTest/Mlos.NetCore.UnitTest.csproj +++ b/source/Mlos.NetCore.UnitTest/Mlos.NetCore.UnitTest.csproj @@ -28,6 +28,7 @@ + diff --git a/source/Mlos.NetCore.UnitTest/SharedChannelTests.cs b/source/Mlos.NetCore.UnitTest/SharedChannelTests.cs index bd56bc147d..850396caff 100644 --- a/source/Mlos.NetCore.UnitTest/SharedChannelTests.cs +++ b/source/Mlos.NetCore.UnitTest/SharedChannelTests.cs @@ -19,7 +19,7 @@ namespace Mlos.NetCore.UnitTest { - public class SharedChannelTests : IDisposable + public sealed class SharedChannelTests : IDisposable { private const string GlobalMemoryMapName = "Mlos.NetCore.Global.UnitTest"; private const string SharedChannelMemoryMapName = "Mlos.NetCore.SharedChannelTests.UnitTest"; @@ -40,7 +40,9 @@ public SharedChannelTests() // Initialize shared channel. // globalChannelMemoryRegionView = SharedMemoryRegionView.Create(GlobalMemoryMapName, SharedMemorySize); + globalChannelMemoryRegionView.CleanupOnClose = true; sharedChannelMemoryMapView = SharedMemoryMapView.Create(SharedChannelMemoryMapName, SharedMemorySize); + sharedChannelMemoryMapView.CleanupOnClose = true; MlosProxyInternal.GlobalMemoryRegion globalMemoryRegion = globalChannelMemoryRegionView.MemoryRegion(); @@ -53,7 +55,7 @@ public void Dispose() GC.SuppressFinalize(this); } - protected virtual void Dispose(bool disposing) + private void Dispose(bool disposing) { if (isDisposed || !disposing) { diff --git a/source/Mlos.NetCore.UnitTest/SharedMemoryMapViewTests.cs b/source/Mlos.NetCore.UnitTest/SharedMemoryMapViewTests.cs new file mode 100644 index 0000000000..5d91691e9b --- /dev/null +++ b/source/Mlos.NetCore.UnitTest/SharedMemoryMapViewTests.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root +// for license information. +// +// ----------------------------------------------------------------------- + +using System; +using System.IO; +using System.Threading.Tasks; + +using Mlos.Core; +using Mlos.UnitTest; +using Xunit; + +using MlosProxyInternal = Proxy.Mlos.Core.Internal; +using TestSharedChannel = Mlos.Core.SharedChannel; +using UnitTestProxy = Proxy.Mlos.UnitTest; + +namespace Mlos.NetCore.UnitTest +{ + public sealed class SharedMemoryMapViewTests + { + private const string SharedMemoryMapName = "Mlos.NetCore.SharedMapTest.UnitTest"; + private const int SharedMemorySize = 4096; + + /// + /// Verifies that on Linux, shared memory is unlinked on dispose. + /// + [Fact] + public void VerifySharedMemoryMapUnlink() + { + // Create a new shared memory maps. + // + var newsSharedChannelMemoryMap = SharedMemoryMapView.Create(SharedMemoryMapName, SharedMemorySize); + newsSharedChannelMemoryMap.CleanupOnClose = true; + newsSharedChannelMemoryMap.Dispose(); + + try + { + // Verify we can open already created shared memory. + // + using var openedSharedChannelMemoryMap = SharedMemoryMapView.Open(SharedMemoryMapName, SharedMemorySize); + newsSharedChannelMemoryMap.CleanupOnClose = true; + + Assert.False(true, "Shared memory map should be deleted"); + } + catch (FileNotFoundException) + { + // We are expecting failure. + // + } + } + } +} diff --git a/source/Mlos.NetCore/Codegen/GlobalMemoryRegion.cs b/source/Mlos.NetCore/Codegen/GlobalMemoryRegion.cs index 878336214b..46f6ee18eb 100644 --- a/source/Mlos.NetCore/Codegen/GlobalMemoryRegion.cs +++ b/source/Mlos.NetCore/Codegen/GlobalMemoryRegion.cs @@ -29,6 +29,11 @@ internal partial struct GlobalMemoryRegion /// internal ChannelSynchronization FeedbackChannelSynchronization; + /// + /// Gets or sets information how many processes are using the global memory region. + /// + internal AtomicUInt32 AttachedProcessesCount; + /// /// Total number of regions. /// diff --git a/source/Mlos.NetCore/InterProcessMlosContext.cs b/source/Mlos.NetCore/InterProcessMlosContext.cs new file mode 100644 index 0000000000..db6a9213f3 --- /dev/null +++ b/source/Mlos.NetCore/InterProcessMlosContext.cs @@ -0,0 +1,157 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root +// for license information. +// +// ----------------------------------------------------------------------- + +using MlosProxy = Proxy.Mlos.Core; +using MlosProxyInternal = Proxy.Mlos.Core.Internal; + +namespace Mlos.Core +{ + /// + /// Inter-process MlosContexts. + /// + public class InterProcessMlosContext : MlosContext + { + /// + /// Shared memory mapping name must start with "Host_" prefix, to be accessible from certain applications. + /// TODO: Make these config regions configurable to support multiple processes. + /// + private const string GlobalMemoryMapName = "Host_Mlos.GlobalMemory"; + private const string ControlChannelMemoryMapName = "Host_Mlos.ControlChannel"; + private const string FeedbackChannelMemoryMapName = "Host_Mlos.FeedbackChannel"; + private const string ControlChannelSemaphoreName = @"Global\ControlChannel_Event"; //// FIXME: Use non-backslashes for Linux environments. + private const string FeedbackChannelSemaphoreName = @"Global\FeedbackChannel_Event"; + + private const string SharedConfigMemoryMapName = "Host_Mlos.Config.SharedMemory"; + + private const int SharedMemorySize = 65536; + + /// + /// Always create... + /// + /// InterProcessMlosContext instance. + public static InterProcessMlosContext Create() + { + // Create or open the memory mapped files. + // + SharedMemoryRegionView globalMemoryRegionView = SharedMemoryRegionView.Create(GlobalMemoryMapName, SharedMemorySize); + SharedMemoryMapView controlChannelMemoryMapView = SharedMemoryMapView.Create(ControlChannelMemoryMapName, SharedMemorySize); + SharedMemoryMapView feedbackChannelMemoryMapView = SharedMemoryMapView.Create(FeedbackChannelMemoryMapName, SharedMemorySize); + SharedMemoryRegionView sharedConfigMemoryMapView = SharedMemoryRegionView.Create(SharedConfigMemoryMapName, SharedMemorySize); + + // Create channel synchronization primitives. + // + NamedEvent controlChannelNamedEvent = NamedEvent.CreateOrOpen(ControlChannelSemaphoreName); + NamedEvent feedbackChannelNamedEvent = NamedEvent.CreateOrOpen(FeedbackChannelSemaphoreName); + + return new InterProcessMlosContext( + globalMemoryRegionView, + controlChannelMemoryMapView, + feedbackChannelMemoryMapView, + sharedConfigMemoryMapView, + controlChannelNamedEvent, + feedbackChannelNamedEvent); + } + + /// + /// Initializes a new instance of the class. + /// + /// InterProcessMlosContext instance. + public static InterProcessMlosContext CreateOrOpen() + { + // Create or open the memory mapped files. + // + SharedMemoryRegionView globalMemoryRegionView = SharedMemoryRegionView.CreateOrOpen(GlobalMemoryMapName, SharedMemorySize); + SharedMemoryMapView controlChannelMemoryMapView = SharedMemoryMapView.CreateOrOpen(ControlChannelMemoryMapName, SharedMemorySize); + SharedMemoryMapView feedbackChannelMemoryMapView = SharedMemoryMapView.CreateOrOpen(FeedbackChannelMemoryMapName, SharedMemorySize); + SharedMemoryRegionView sharedConfigMemoryMapView = SharedMemoryRegionView.CreateOrOpen(SharedConfigMemoryMapName, SharedMemorySize); + + // Create channel synchronization primitives. + // + NamedEvent controlChannelNamedEvent = NamedEvent.CreateOrOpen(ControlChannelSemaphoreName); + NamedEvent feedbackChannelNamedEvent = NamedEvent.CreateOrOpen(FeedbackChannelSemaphoreName); + + return new InterProcessMlosContext( + globalMemoryRegionView, + controlChannelMemoryMapView, + feedbackChannelMemoryMapView, + sharedConfigMemoryMapView, + controlChannelNamedEvent, + feedbackChannelNamedEvent); + } + + internal InterProcessMlosContext( + SharedMemoryRegionView globalMemoryRegionView, + SharedMemoryMapView controlChannelMemoryMapView, + SharedMemoryMapView feedbackChannelMemoryMapView, + SharedMemoryRegionView sharedConfigMemoryMapView, + NamedEvent controlChannelNamedEvent, + NamedEvent feedbackChannelNamedEvent) + { + this.globalMemoryRegionView = globalMemoryRegionView; + this.controlChannelMemoryMapView = controlChannelMemoryMapView; + this.feedbackChannelMemoryMapView = feedbackChannelMemoryMapView; + this.sharedConfigMemoryMapView = sharedConfigMemoryMapView; + + this.controlChannelNamedEvent = controlChannelNamedEvent; + this.feedbackChannelNamedEvent = feedbackChannelNamedEvent; + + MlosProxyInternal.GlobalMemoryRegion globalMemoryRegion = globalMemoryRegionView.MemoryRegion(); + + // Increase the usage counter. When closing global shared memory, we will decrease the counter. + // If there is no process using the shared memory, we will clean the OS resources. On Windows OS, + // this is no-op; on Linux, we unlink created files. + // + globalMemoryRegion.AttachedProcessesCount.FetchAdd(1); + + // Create the control channel instance. + // + ControlChannel = new SharedChannel( + buffer: controlChannelMemoryMapView.Buffer, + size: (uint)controlChannelMemoryMapView.MemSize, + sync: globalMemoryRegion.ControlChannelSynchronization) + { + ChannelPolicy = { NotificationEvent = controlChannelNamedEvent }, + }; + + // Create the feedback channel instance. + // + FeedbackChannel = new SharedChannel( + buffer: feedbackChannelMemoryMapView.Buffer, + size: (uint)feedbackChannelMemoryMapView.MemSize, + sync: globalMemoryRegion.FeedbackChannelSynchronization) + { + ChannelPolicy = { NotificationEvent = feedbackChannelNamedEvent }, + }; + } + + /// + protected override void Dispose(bool disposing) + { + if (isDisposed || !disposing) + { + return; + } + + uint usageCount = GlobalMemoryRegion.AttachedProcessesCount.FetchSub(1); + + // The last one out shut off the lights. + // + if (usageCount == 0) + { + globalMemoryRegionView.CleanupOnClose = true; + controlChannelMemoryMapView.CleanupOnClose = true; + feedbackChannelMemoryMapView.CleanupOnClose = true; + sharedConfigMemoryMapView.CleanupOnClose = true; + controlChannelNamedEvent.CleanupOnClose = true; + feedbackChannelNamedEvent.CleanupOnClose = true; + } + + base.Dispose(disposing); + } + } +} diff --git a/source/Mlos.NetCore/Mlos.NetCore.csproj b/source/Mlos.NetCore/Mlos.NetCore.csproj index b8cc5cdb64..f81f50e92d 100644 --- a/source/Mlos.NetCore/Mlos.NetCore.csproj +++ b/source/Mlos.NetCore/Mlos.NetCore.csproj @@ -29,9 +29,6 @@ - - - @@ -42,7 +39,11 @@ + + + + diff --git a/source/Mlos.NetCore/MlosContext.cs b/source/Mlos.NetCore/MlosContext.cs index cb538c8b9d..8ae2144432 100644 --- a/source/Mlos.NetCore/MlosContext.cs +++ b/source/Mlos.NetCore/MlosContext.cs @@ -6,6 +6,10 @@ // // ----------------------------------------------------------------------- +using System; + +using MlosProxyInternal = Proxy.Mlos.Core.Internal; + namespace Mlos.Core { /// @@ -18,12 +22,21 @@ namespace Mlos.Core /// See Also: Mlos.Core/MlosContext.h for the corresponding C++ smart /// component side. /// - public static class MlosContext + public abstract class MlosContext : IDisposable { + #region Shared public objects + + /// + /// Gets or sets the control channel instance. + /// #TODO, those should not be static. Pass a MlosContext to the experiment class. + /// + public static ISharedChannel ControlChannel { get; protected set; } + /// /// Gets or sets the feedback channel instance. + /// #TODO, those should not be static. Pass a MlosContext to the experiment class. /// - public static ISharedChannel FeedbackChannel { get; set; } + public static ISharedChannel FeedbackChannel { get; protected set; } public static ISharedConfigAccessor SharedConfigManager { get; set; } @@ -38,5 +51,95 @@ public static class MlosContext /// example). /// public static IOptimizerFactory OptimizerFactory { get; set; } + + #endregion + + protected SharedMemoryRegionView globalMemoryRegionView; + protected SharedMemoryMapView controlChannelMemoryMapView; + protected SharedMemoryMapView feedbackChannelMemoryMapView; + protected SharedMemoryRegionView sharedConfigMemoryMapView; + + protected NamedEvent controlChannelNamedEvent; + protected NamedEvent feedbackChannelNamedEvent; + + protected bool isDisposed; + + protected virtual void Dispose(bool disposing) + { + if (isDisposed || !disposing) + { + return; + } + + // Close shared memory. + // + globalMemoryRegionView?.Dispose(); + globalMemoryRegionView = null; + + controlChannelMemoryMapView?.Dispose(); + controlChannelMemoryMapView = null; + + feedbackChannelMemoryMapView?.Dispose(); + feedbackChannelMemoryMapView = null; + + sharedConfigMemoryMapView?.Dispose(); + sharedConfigMemoryMapView = null; + + controlChannelNamedEvent?.Dispose(); + controlChannelNamedEvent = null; + + feedbackChannelNamedEvent?.Dispose(); + feedbackChannelNamedEvent = null; + + isDisposed = true; + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + public MlosProxyInternal.GlobalMemoryRegion GlobalMemoryRegion => globalMemoryRegionView.MemoryRegion(); + + public MlosProxyInternal.SharedConfigMemoryRegion SharedConfigMemoryRegion => sharedConfigMemoryMapView.MemoryRegion(); + + /// + /// Terminate the control channel. + /// + public void TerminateControlChannel() + { + // Terminate the channel to avoid deadlocks if the buffer is full, and there is no active reader thread. + // + ControlChannel.SyncObject.TerminateChannel.Store(true); + controlChannelNamedEvent.Signal(); + } + + /// + /// Terminates the feedback channel. + /// + public void TerminateFeedbackChannel() + { + FeedbackChannel.SyncObject.TerminateChannel.Store(true); + feedbackChannelNamedEvent.Signal(); + } + + /// + /// Checks if the control channel is still active. + /// + /// + public bool IsControlChannelActive() + { + return !ControlChannel.SyncObject.TerminateChannel.Load(); + } + + /// + /// Checks if the feedback channel is still active. + /// + /// + public bool IsFeedbackChannelActive() + { + return !FeedbackChannel.SyncObject.TerminateChannel.Load(); + } } } diff --git a/source/Mlos.NetCore/NamedEvent.Windows.cs b/source/Mlos.NetCore/NamedEvent.Windows.cs index 55750a71d2..7ab3261876 100644 --- a/source/Mlos.NetCore/NamedEvent.Windows.cs +++ b/source/Mlos.NetCore/NamedEvent.Windows.cs @@ -70,18 +70,15 @@ public override bool Wait() /// protected override void Dispose(bool disposing) { - if (disposed) + if (isDisposed || !disposing) { return; } - if (disposing) - { - eventHandle?.Dispose(); - eventHandle = null; - } + eventHandle?.Dispose(); + eventHandle = null; - disposed = true; + isDisposed = true; } private EventSafeHandle eventHandle; diff --git a/source/Mlos.NetCore/NamedEvent.cs b/source/Mlos.NetCore/NamedEvent.cs index 20ea64d0c6..441433fa8f 100644 --- a/source/Mlos.NetCore/NamedEvent.cs +++ b/source/Mlos.NetCore/NamedEvent.cs @@ -38,11 +38,15 @@ public static NamedEvent CreateOrOpen(string name) } } + /// + /// Finalizes an instance of the class. + /// ~NamedEvent() { Dispose(false); } + /// public void Dispose() { // Dispose of unmanaged resources. @@ -75,6 +79,12 @@ public void Dispose() /// /// True if object has been disposed. /// - protected bool disposed = false; + protected bool isDisposed = false; + + /// + /// Indicates if we should cleanup OS resources when closing the shared memory map view. + /// + /// + public bool CleanupOnClose; } -} \ No newline at end of file +} diff --git a/source/Mlos.NetCore/NamedSemaphore.Linux.cs b/source/Mlos.NetCore/NamedSemaphore.Linux.cs index 30da670265..ba4daaedd1 100644 --- a/source/Mlos.NetCore/NamedSemaphore.Linux.cs +++ b/source/Mlos.NetCore/NamedSemaphore.Linux.cs @@ -6,10 +6,15 @@ // // ----------------------------------------------------------------------- -using System.Threading; +using System.ComponentModel; +using System.IO; +using System.Runtime.InteropServices; namespace Mlos.Core.Linux { + /// + /// Named semaphore. + /// public class NamedSemaphore : Mlos.Core.NamedEvent { /// @@ -31,6 +36,15 @@ private NamedSemaphore(string name, Native.OpenFlags openFlags) openFlags, Native.ModeFlags.S_IRUSR | Native.ModeFlags.S_IWUSR, 0); + + if (semaphoreHandle.IsInvalid) + { + throw new IOException( + $"Failed to create a NamedSemaphore {name}", + innerException: new Win32Exception(Marshal.GetLastWin32Error())); + } + + semaphoreName = name; } /// @@ -55,19 +69,30 @@ public override bool Wait() /// protected override void Dispose(bool disposing) { - if (disposed) + if (isDisposed || !disposing) { return; } - if (disposing) + semaphoreHandle?.Dispose(); + + if (CleanupOnClose) { - semaphoreHandle?.Dispose(); + // Unlink semaphore. Ignore the errors. + // + if (semaphoreName != null) + { + _ = Native.SemaphoreUnlink(semaphoreName); + } + + CleanupOnClose = false; } - disposed = true; + isDisposed = true; } private readonly SemaphoreSafeHandle semaphoreHandle; + + private readonly string semaphoreName; } -} \ No newline at end of file +} diff --git a/source/Mlos.NetCore/SharedChannel.cs b/source/Mlos.NetCore/SharedChannel.cs index 2c479dd6e4..91f8dd6333 100644 --- a/source/Mlos.NetCore/SharedChannel.cs +++ b/source/Mlos.NetCore/SharedChannel.cs @@ -32,6 +32,11 @@ void SendMessage(ref TMessage msg) /// /// void ProcessMessages(ref DispatchEntry[] dispatchTable); + + /// + /// Channel synchronization object. + /// + internal MlosProxy.ChannelSynchronization SyncObject { get; } } /// @@ -763,5 +768,10 @@ internal bool HasReadersInWaitingState() /// Channel control policy. /// public TChannelPolicy ChannelPolicy; + + /// + /// Channel synchronization object. + /// + MlosProxy.ChannelSynchronization ISharedChannel.SyncObject => Sync; } } diff --git a/source/Mlos.NetCore/SharedMemoryMapView.Linux.cs b/source/Mlos.NetCore/SharedMemoryMapView.Linux.cs index 5ff16b8d54..efd31a81a1 100644 --- a/source/Mlos.NetCore/SharedMemoryMapView.Linux.cs +++ b/source/Mlos.NetCore/SharedMemoryMapView.Linux.cs @@ -13,6 +13,9 @@ namespace Mlos.Core.Linux { + /// + /// Linux implementation of shared memory map view. + /// public sealed class SharedMemoryMapView : Mlos.Core.SharedMemoryMapView { /// @@ -121,27 +124,32 @@ private SharedMemoryMapView(string sharedMemoryMapName, ulong sharedMemorySize, /// protected override void Dispose(bool disposing) { - if (disposed) + if (isDisposed || !disposing) { return; } - if (disposing) - { - // Close shared memory. - // - sharedMemoryHandle?.Dispose(); + // Close shared memory. + // + sharedMemoryHandle?.Dispose(); + if (CleanupOnClose) + { // Unlink shared map. Ignore the errors. // - _ = Native.SharedMemoryUnlink(sharedMemoryMapName); + if (sharedMemoryMapName != null) + { + _ = Native.SharedMemoryUnlink(sharedMemoryMapName); + } + + CleanupOnClose = false; } - disposed = true; + isDisposed = true; } - private readonly string sharedMemoryMapName; - private SharedMemorySafeHandle sharedMemoryHandle; + + private readonly string sharedMemoryMapName; } } diff --git a/source/Mlos.NetCore/SharedMemoryMapView.Windows.cs b/source/Mlos.NetCore/SharedMemoryMapView.Windows.cs index 2c08c33543..5012c0c5ac 100644 --- a/source/Mlos.NetCore/SharedMemoryMapView.Windows.cs +++ b/source/Mlos.NetCore/SharedMemoryMapView.Windows.cs @@ -12,6 +12,9 @@ namespace Mlos.Core.Windows { + /// + /// Windows implementation of shared memory map view. + /// public class SharedMemoryMapView : Mlos.Core.SharedMemoryMapView { /// @@ -115,23 +118,20 @@ private SharedMemoryMapView(SharedMemorySafeHandle sharedMemoryHandle, ulong sha /// protected override void Dispose(bool disposing) { - if (disposed) + if (isDisposed || !disposing) { return; } - if (disposing) - { - // Close the memory mapping. - // - memoryMappingHandle?.Dispose(); + // Close the memory mapping. + // + memoryMappingHandle?.Dispose(); - // Close the shared memory. - // - sharedMemoryHandle?.Dispose(); - } + // Close the shared memory. + // + sharedMemoryHandle?.Dispose(); - disposed = true; + isDisposed = true; } private readonly MemoryMappingSafeHandle memoryMappingHandle; diff --git a/source/Mlos.NetCore/SharedMemoryMapView.cs b/source/Mlos.NetCore/SharedMemoryMapView.cs index cf54bda0db..9654c8bc70 100644 --- a/source/Mlos.NetCore/SharedMemoryMapView.cs +++ b/source/Mlos.NetCore/SharedMemoryMapView.cs @@ -1,112 +1,118 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root -// for license information. -// -// ----------------------------------------------------------------------- - -using System; -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; - -namespace Mlos.Core -{ - public abstract class SharedMemoryMapView : CriticalFinalizerObject, IDisposable - { - /// - /// Creates a new shared memory view. - /// - /// - /// - /// Thrown when executed on unsupported OS. - /// - public static SharedMemoryMapView Create(string sharedMemoryMapName, ulong sharedMemorySize) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return Windows.SharedMemoryMapView.Create(sharedMemoryMapName, sharedMemorySize); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return Linux.SharedMemoryMapView.Create(sharedMemoryMapName, sharedMemorySize); - } - else - { - throw new InvalidOperationException("Unsupported OS."); - } - } - - /// - /// Creates or opens a shared memory view. - /// - /// - /// - /// - public static SharedMemoryMapView CreateOrOpen(string sharedMemoryMapName, ulong sharedMemorySize) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return Windows.SharedMemoryMapView.CreateOrOpen(sharedMemoryMapName, sharedMemorySize); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return Linux.SharedMemoryMapView.CreateOrOpen(sharedMemoryMapName, sharedMemorySize); - } - else - { - throw new InvalidOperationException("Unsupported OS."); - } - } - - /// - /// Opens an existing shared memory view. - /// - /// - /// - /// - public static SharedMemoryMapView Open(string sharedMemoryMapName, ulong sharedMemorySize) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return Windows.SharedMemoryMapView.Open(sharedMemoryMapName, sharedMemorySize); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return Linux.SharedMemoryMapView.Open(sharedMemoryMapName, sharedMemorySize); - } - else - { - throw new InvalidOperationException("Unsupported OS."); - } - } - - ~SharedMemoryMapView() - { - Dispose(false); - } - - public void Dispose() - { - // Dispose of unmanaged resources. - // - Dispose(true); - - // Suppress finalization. - // - GC.SuppressFinalize(this); - } - - /// - /// Protected implementation of Dispose pattern. - /// - /// - protected abstract void Dispose(bool disposing); - - public IntPtr Buffer; - - public ulong MemSize; - - protected bool disposed = false; - } -} \ No newline at end of file +// ----------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root +// for license information. +// +// ----------------------------------------------------------------------- + +using System; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; + +namespace Mlos.Core +{ + public abstract class SharedMemoryMapView : CriticalFinalizerObject, IDisposable + { + /// + /// Creates a new shared memory view. + /// + /// + /// + /// Thrown when executed on unsupported OS. + /// + public static SharedMemoryMapView Create(string sharedMemoryMapName, ulong sharedMemorySize) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Windows.SharedMemoryMapView.Create(sharedMemoryMapName, sharedMemorySize); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return Linux.SharedMemoryMapView.Create(sharedMemoryMapName, sharedMemorySize); + } + else + { + throw new InvalidOperationException("Unsupported OS."); + } + } + + /// + /// Creates or opens a shared memory view. + /// + /// + /// + /// + public static SharedMemoryMapView CreateOrOpen(string sharedMemoryMapName, ulong sharedMemorySize) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Windows.SharedMemoryMapView.CreateOrOpen(sharedMemoryMapName, sharedMemorySize); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return Linux.SharedMemoryMapView.CreateOrOpen(sharedMemoryMapName, sharedMemorySize); + } + else + { + throw new InvalidOperationException("Unsupported OS."); + } + } + + /// + /// Opens an existing shared memory view. + /// + /// + /// + /// + public static SharedMemoryMapView Open(string sharedMemoryMapName, ulong sharedMemorySize) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Windows.SharedMemoryMapView.Open(sharedMemoryMapName, sharedMemorySize); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return Linux.SharedMemoryMapView.Open(sharedMemoryMapName, sharedMemorySize); + } + else + { + throw new InvalidOperationException("Unsupported OS."); + } + } + + /// + /// Finalizes an instance of the class. + /// + ~SharedMemoryMapView() + { + Dispose(false); + } + + /// + public void Dispose() + { + // Dispose of unmanaged resources. + // + Dispose(true); + + // Suppress finalization. + // + GC.SuppressFinalize(this); + } + + /// + /// Protected implementation of Dispose pattern. + /// + /// + protected abstract void Dispose(bool disposing); + + public IntPtr Buffer; + + public ulong MemSize; + + public bool CleanupOnClose; + + protected bool isDisposed; + } +} diff --git a/source/Mlos.NetCore/SharedMemoryRegionView.cs b/source/Mlos.NetCore/SharedMemoryRegionView.cs index 4bf941d5ea..1fafaa0726 100644 --- a/source/Mlos.NetCore/SharedMemoryRegionView.cs +++ b/source/Mlos.NetCore/SharedMemoryRegionView.cs @@ -92,6 +92,9 @@ public SharedMemoryRegionView(SharedMemoryMapView sharedMemoryMap) }; } + /// + /// Size of the shared memory map. + /// public ulong MemSize => sharedMemoryMap.MemSize; /// @@ -108,18 +111,15 @@ public T MemoryRegion() private void Dispose(bool disposing) { - if (disposed) + if (isDisposed || !disposing) { return; } - if (disposing) - { - sharedMemoryMap?.Dispose(); - sharedMemoryMap = null; - } + sharedMemoryMap?.Dispose(); + sharedMemoryMap = null; - disposed = true; + isDisposed = true; } /// @@ -131,6 +131,15 @@ public void Dispose() private SharedMemoryMapView sharedMemoryMap; - private bool disposed = false; + private bool isDisposed = false; + + /// + /// Indicates if we should cleanup OS resources when closing the shared memory map view. + /// + public bool CleanupOnClose + { + get { return sharedMemoryMap.CleanupOnClose; } + set { sharedMemoryMap.CleanupOnClose = value; } + } } }