Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ConcurrentProgramming/Communication/Communication.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Fundamentals\Fundamentals.csproj" />
</ItemGroup>

</Project>
189 changes: 189 additions & 0 deletions ConcurrentProgramming/Communication/Port.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
//____________________________________________________________________________________________________________________________________
//
// Copyright (C) 2025, Mariusz Postol LODZ POLAND.
//
// To be in touch join the community by pressing the `Watch` button and get started commenting using the discussion panel at
//
// https://github.com/mpostol/TP/discussions/182
//
// by introducing yourself and telling us what you do with this community.
//_____________________________________________________________________________________________________________________________________

using TP.ConcurrentProgramming.Fundamentals;

namespace TP.ConcurrentProgramming.Communication
{
/// <summary>
/// Provides access to a queue of <seealso cref="IEnvelope"/> messages.
/// Thread Safety: Instances members of this type are safe for multi-threaded operations.
/// </summary>
// TODO implement UT
public sealed class Port : HoareMonitor
{
#region ctor

public Port()
{
m_AtLeastOneMessageInQueue = CreateCondition();
}

#endregion ctor

#region public

/// <summary>
/// Gets the number of elements contained in the Port queue.
/// </summary>
public int Count
{
get
{
int retval = 0;
EnterMonitor();
try
{
if (!this.m_Openned)
throw new InvalidOperationException("Port is closed");
retval = m_NumOfMess;
}
finally
{
//ExitMonitor();
}
return retval;
}
}

/// <summary>
/// Opens this instance.
/// </summary>
public void Open()
{
EnterMonitor();
m_Openned = true;
ExitMonitor();
}

/// <summary>
/// Closes this instance.
/// </summary>
public void Close()
{
EnterMonitor();
m_Openned = false;
//TODO implement NotifyAll m_AtLeastOneMessageInQueue.NotifyAll();
ExitMonitor();
}

/// <summary>
/// Clears this instance.
/// </summary>
public void Clear()
{
EnterMonitor();
try
{
IEnvelope _messToReturn;
while (m_NumOfMess != 0)
{
_messToReturn = Dequeue();
_messToReturn.ReturnEmptyEnvelope();
}
//m_AtLeastOneMessageInQueue.NotifyAll();
}
finally
{
ExitMonitor();
}
}

/// <summary>
/// Sends the message to the 'port'. If there is a process waiting in 'port' it
/// will be resumed from the 'port' queue. If there is no process, the message will be queued.
/// </summary>
/// <param name="mess">Message to be sent</param>
public void SendMsg(ref IEnvelope mess)
{
EnterMonitor();
try
{
if (!m_Openned) throw new InvalidOperationException("Port is closed");
m_Queue.Enqueue(mess);
mess = null;
m_NumOfMess++;
m_AtLeastOneMessageInQueue?.Send();
}
finally
{
ExitMonitor();
}
}

/// <summary>
/// Receive message from 'port'. If there is no message in the 'port', the calling
/// thread will be blocked until it receives a message or a specified amount of
/// time elapses.
/// </summary>
/// <param name="mess">UMessage removed from the beginning of the port Queue</param>
/// <param name="timeOut">The number of milliseconds to wait before this method returns. 0 means wait forever.
/// </param>
/// <param name="callingMonitor">TODO: add some description</param>
/// <returns>
/// true if the message was received before the specified time elapsed; otherwise, false
/// </returns>
public bool WaitMsg(out IEnvelope? mess)
{
bool res = false;
EnterMonitor();
mess = null;
try
{
if ((m_NumOfMess == 0) & m_Openned)
m_AtLeastOneMessageInQueue?.Wait();
if (m_NumOfMess != 0)
{
mess = Dequeue();
res = true;
}
}
finally
{
ExitMonitor();
}
return res;
}

#endregion public

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}

#region private

private bool m_Openned = false;
private Queue<IEnvelope> m_Queue = new Queue<IEnvelope>();
private readonly ICondition? m_AtLeastOneMessageInQueue = null;
private int m_NumOfMess = 0;

private IEnvelope Dequeue()
{
if (m_NumOfMess <= 0)
throw new InvalidOperationException("No message in the queue");
m_NumOfMess--;
return m_Queue.Dequeue();
}

#endregion private

#region HoareMonitor

protected override ISignal CreateSignal()
{
throw new NotImplementedException();
}

#endregion HoareMonitor
}
}
20 changes: 20 additions & 0 deletions ConcurrentProgramming/CommunicationTest/CommunicationTest.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="MSTest.Sdk/3.6.4">

<PropertyGroup>
<TargetFramework>net9.0-windows</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseVSTest>true</UseVSTest>
<RootNamespace>TP.ConcurrentProgramming.Communication.Test</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Moq" Version="4.20.72" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Communication\Communication.csproj" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions ConcurrentProgramming/CommunicationTest/MSTestSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
132 changes: 132 additions & 0 deletions ConcurrentProgramming/CommunicationTest/PortTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//____________________________________________________________________________________________________________________________________
//
// Copyright (C) 2025, Mariusz Postol LODZ POLAND.
//
// To be in touch join the community by pressing the `Watch` button and get started commenting using the discussion panel at
//
// https://github.com/mpostol/TP/discussions/182
//
//_____________________________________________________________________________________________________________________________________

using Moq;
using TP.ConcurrentProgramming.Fundamentals;

namespace TP.ConcurrentProgramming.Communication.Test
{
[TestClass]
public class PortTest
{
/// <summary>
/// Verifies that the Count property returns 0 when no messages are in the queue.
/// </summary>
[TestMethod]
public void Count_ShouldReturnZero_WhenNoMessages()
{
int count = -1;
// Arrange
using (Port _port = new Port())
{
// Act, Test
Assert.ThrowsException<InvalidOperationException>(() => count = _port.Count); //indirectly checking if port is open
Assert.AreEqual(-1, count);
}
}

/// <summary>
/// Verifies that the port can be opened and indirectly checks if the port is open by checking the count.
/// </summary>
[TestMethod]
public void Open_ShouldSetOpenedToTrue()
{
Mock<IEnvelope> _mockEnvelope = new Mock<IEnvelope>();
// Arrange
using (Port _port = new Port())
{
// Act
_port.Open();
// Test
Assert.IsTrue(_port.Count == 0); //indirectly checking if port is open
}
}

/// <summary>
/// Verifies that the port can be closed and indirectly checks if the port is closed by checking the count.
/// </summary>
[TestMethod]
public void Close_ShouldSetOpenedToFalse()
{
// Arrange
using (Port _port = new Port())
{
_port.Open();
// Act
_port.Close();
// Test
Assert.ThrowsException<InvalidOperationException>(() => _port.Count);
}
}

/// <summary>
/// Verifies that the port can be cleared and all messages are returned to the pool.
/// </summary>
[TestMethod]
public void Clear_ShouldEmptyTheQueue()
{
// Arrange
Mock<IEnvelope> _mockEnvelope = new Mock<IEnvelope>();
IEnvelope _envelope = _mockEnvelope.Object;
using (Port _port = new Port())
{
_port.Open();
_port.SendMsg(ref _envelope);
// Act
_port.Clear();
// Test
Assert.IsNull(_envelope);
Assert.AreEqual(0, _port.Count);
}
}

/// <summary>
/// Verifies that a message can be sent to the port.
/// </summary>
[TestMethod]
public void SendMsg_ShouldAddMessageToQueue()
{
// Arrange
Mock<IEnvelope> _mockEnvelope = new Mock<IEnvelope>();
IEnvelope _envelope = _mockEnvelope.Object;
using (Port _port = new Port())
{
_port.Open();
// Act
_port.SendMsg(ref _envelope);
// Test
Assert.AreEqual(1, _port.Count);
}
}

/// <summary>
/// Verifies that a message can be received from the port.
/// </summary>
[TestMethod]
public void WaitMsg_ShouldRetrieveMessageFromQueue()
{
// Arrange
Mock<IEnvelope> _mockEnvelope = new Mock<IEnvelope>();
IEnvelope _envelope = _mockEnvelope.Object;
IEnvelope? receivedMessage = null;
using (Port _port = new Port())
{
_port.Open();
_port.SendMsg(ref _envelope);
// Act
bool result = _port.WaitMsg(out receivedMessage);
// Test
Assert.IsTrue(result);
Assert.IsNotNull(receivedMessage);
Assert.AreSame(_mockEnvelope.Object, receivedMessage);
}
}
}
}
7 changes: 7 additions & 0 deletions ConcurrentProgramming/ConcurrentProgramming.sln
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataTest", "ReactiveInterac
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SynchronizationTest", "SynchronizationTest\SynchronizationTest.csproj", "{EB493346-6735-4F21-9D42-442778A7B3E2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunicationTest", "CommunicationTest\CommunicationTest.csproj", "{CBAA9B93-A98C-4545-B950-CACF033ECF9C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -135,6 +137,10 @@ Global
{EB493346-6735-4F21-9D42-442778A7B3E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB493346-6735-4F21-9D42-442778A7B3E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB493346-6735-4F21-9D42-442778A7B3E2}.Release|Any CPU.Build.0 = Release|Any CPU
{CBAA9B93-A98C-4545-B950-CACF033ECF9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBAA9B93-A98C-4545-B950-CACF033ECF9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBAA9B93-A98C-4545-B950-CACF033ECF9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBAA9B93-A98C-4545-B950-CACF033ECF9C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -158,6 +164,7 @@ Global
{8760446B-8BD6-4254-9977-475EAEE45B0B} = {C3B041B9-20C2-4920-B0E5-CFE5784FC6E4}
{FD3FA340-EB7B-4037-B3AB-710E6817AC9D} = {C3B041B9-20C2-4920-B0E5-CFE5784FC6E4}
{EB493346-6735-4F21-9D42-442778A7B3E2} = {6D532753-A18A-4AB7-8A6A-830F6668B744}
{CBAA9B93-A98C-4545-B950-CACF033ECF9C} = {20429409-B6E9-4781-B409-A0A09A59BFB1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4ACB1EF2-3819-4CE2-9F11-F8DBD7CDDFEC}
Expand Down
Loading