diff --git a/Directory.Packages.props b/Directory.Packages.props
index 17a4a3c9b..18fab1c05 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -53,6 +53,7 @@
+
diff --git a/src/fdc3/dotnet/AppDirectory/test/AppDirectory.Tests/AppDirectory.GetApps.Tests.cs b/src/fdc3/dotnet/AppDirectory/test/AppDirectory.Tests/AppDirectory.GetApps.Tests.cs
index 159047bce..97ba6efe9 100644
--- a/src/fdc3/dotnet/AppDirectory/test/AppDirectory.Tests/AppDirectory.GetApps.Tests.cs
+++ b/src/fdc3/dotnet/AppDirectory/test/AppDirectory.Tests/AppDirectory.GetApps.Tests.cs
@@ -41,7 +41,7 @@ public async Task GetApps_loads_the_data_from_a_file(
apps.Should().BeEquivalentTo(GetAppsExpectation);
}
- [Theory (Skip ="Fail"), CombinatorialData]
+ [Theory, CombinatorialData]
public async Task GetApps_reloads_the_data_if_the_source_file_has_changed(bool useApiSchema)
{
var source = "/apps.json";
@@ -62,8 +62,8 @@ await fileSystem.File.WriteAllTextAsync(
useApiSchema ? GetAppsApiResponseChanged : GetAppsJsonArrayChanged,
Encoding.UTF8);
- await TaskExtensions.WaitForBackgroundTasksAsync(TimeSpan.FromMilliseconds(100));
-
+ await TaskExtensions.WaitForBackgroundTasksAsync(TimeSpan.FromSeconds(20));
+
var apps = await appDirectory.GetApps();
apps.Should().BeEquivalentTo(GetAppsExpectationChanged);
diff --git a/src/fdc3/dotnet/DesktopAgent/tests/DesktopAgent.Tests/Infrastructure/Internal/Fdc3DesktopAgentMessageRouterService.Tests.cs b/src/fdc3/dotnet/DesktopAgent/tests/DesktopAgent.Tests/Infrastructure/Internal/Fdc3DesktopAgentMessageRouterService.Tests.cs
index 83fc1b419..3b721b2ee 100644
--- a/src/fdc3/dotnet/DesktopAgent/tests/DesktopAgent.Tests/Infrastructure/Internal/Fdc3DesktopAgentMessageRouterService.Tests.cs
+++ b/src/fdc3/dotnet/DesktopAgent/tests/DesktopAgent.Tests/Infrastructure/Internal/Fdc3DesktopAgentMessageRouterService.Tests.cs
@@ -405,19 +405,19 @@ public async Task StoreIntentResult_succeeds_with_channel()
var target = await _mockModuleLoader.Object.StartModule(new("appId4"));
var targetFdc3InstanceId = Fdc3InstanceIdRetriever.Get(target);
var raiseIntentRequest = new RaiseIntentRequest()
- {
- MessageId = int.MaxValue,
- Fdc3InstanceId = Guid.NewGuid().ToString(),
- Intent = "intentMetadata4",
- Selected = false,
- Context = new Context("context2"),
- TargetAppIdentifier = new AppIdentifier() { AppId = "appId4", InstanceId = targetFdc3InstanceId }
- };
+ {
+ MessageId = int.MaxValue,
+ Fdc3InstanceId = Guid.NewGuid().ToString(),
+ Intent = "intentMetadata4",
+ Selected = false,
+ Context = new Context("context2"),
+ TargetAppIdentifier = new AppIdentifier() { AppId = "appId4", InstanceId = targetFdc3InstanceId }
+ };
var raiseIntentResult = await _fdc3.HandleRaiseIntent(raiseIntentRequest, new MessageContext());
raiseIntentResult.Should().NotBeNull();
-
- raiseIntentResult.Should().NotBeNull();
+ raiseIntentResult!.Error.Should().BeNull();
+ raiseIntentResult.AppMetadata.Should().NotBeNull();
raiseIntentResult!.AppMetadata.Should().HaveCount(1);
var storeIntentRequest = new StoreIntentResultRequest()
diff --git a/src/messaging/dotnet/Messaging.sln b/src/messaging/dotnet/Messaging.sln
index e56f5423f..218114cd7 100644
--- a/src/messaging/dotnet/Messaging.sln
+++ b/src/messaging/dotnet/Messaging.sln
@@ -31,7 +31,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{B7E63957-3C1
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MorganStanley.ComposeUI.Testing", "..\..\shared\dotnet\MorganStanley.ComposeUI.Testing\MorganStanley.ComposeUI.Testing.csproj", "{AE71CBC4-FD4E-4C66-B894-D7C31DE4D1BE}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MorganStanley.ComposeUI.Messaging.Host.Tests", "test\Host.Tests\MorganStanley.ComposeUI.Messaging.Host.Tests.csproj", "{CEF78D3F-C645-4471-BAD2-9C538A0CA763}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MorganStanley.ComposeUI.Messaging.Host.Tests", "test\Host.Tests\MorganStanley.ComposeUI.Messaging.Host.Tests.csproj", "{CEF78D3F-C645-4471-BAD2-9C538A0CA763}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/src/messaging/dotnet/Messaging.sln.DotSettings b/src/messaging/dotnet/Messaging.sln.DotSettings
index 249114a50..a919bc949 100644
--- a/src/messaging/dotnet/Messaging.sln.DotSettings
+++ b/src/messaging/dotnet/Messaging.sln.DotSettings
@@ -10,4 +10,5 @@ to in writing, software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language governing permissions
and limitations under the License.
+ True
True
\ No newline at end of file
diff --git a/src/messaging/dotnet/src/Client/Client/MessageRouterClient.cs b/src/messaging/dotnet/src/Client/Client/MessageRouterClient.cs
index 6d0a683bf..14e9f4ad3 100644
--- a/src/messaging/dotnet/src/Client/Client/MessageRouterClient.cs
+++ b/src/messaging/dotnet/src/Client/Client/MessageRouterClient.cs
@@ -17,6 +17,7 @@
using Microsoft.Extensions.Logging.Abstractions;
using MorganStanley.ComposeUI.Messaging.Client.Abstractions;
using MorganStanley.ComposeUI.Messaging.Exceptions;
+using MorganStanley.ComposeUI.Messaging.Instrumentation;
using MorganStanley.ComposeUI.Messaging.Protocol;
using MorganStanley.ComposeUI.Messaging.Protocol.Messages;
using Nito.AsyncEx;
@@ -191,8 +192,8 @@ private Topic GetTopic(string topicName)
private readonly StateChangeEvents _stateChangeEvents = new();
private readonly AsyncLock _mutex = new();
- private readonly Channel _sendChannel =
- Channel.CreateUnbounded(new UnboundedChannelOptions {SingleReader = true});
+ private readonly Channel> _sendChannel =
+ Channel.CreateUnbounded>(new UnboundedChannelOptions {SingleReader = true});
private readonly ConcurrentDictionary> _pendingRequests = new();
private readonly ConcurrentDictionary _endpointHandlers = new();
@@ -235,23 +236,27 @@ private async ValueTask ConnectAsyncCore(CancellationToken cancellationToken)
try
{
+ OnConnectStart();
await _connection.ConnectAsync(cancellationToken);
_stateChangeEvents.SendReceiveCompleted = Task.WhenAll(SendMessagesAsync(), ReceiveMessagesAsync());
await _sendChannel.Writer.WriteAsync(
- new ConnectRequest {AccessToken = _options.AccessToken},
+ new MessageWrapper(
+ new ConnectRequest { AccessToken = _options.AccessToken })
+ ,
cancellationToken);
await _stateChangeEvents.Connected.Task;
}
- catch (MessageRouterException)
+ catch (MessageRouterException e)
{
throw;
}
catch (Exception e)
{
await CloseAsyncCore(e);
+ OnConnectStop(e);
throw ThrowHelper.ConnectionFailed(e);
}
@@ -270,18 +275,18 @@ private void HandleMessage(Message message)
{
/*
Design notes
-
- While the public API supports async subscribers and other callbacks,
+
+ While the public API supports async subscribers and other callbacks,
we avoid (make impossible) to block message processing by an async handler.
In general, we process everything synchronously until the point where the
actual user code is called, and we do not await it. Subscribers have their own
- async message queues so that they can't block each other.
+ async message queues so that they can't block each other.
This can lead to memory issues if a badly written subscriber can't process its messages
fast enough, and its queue starts to grow infinitely. We might add some configuration
options to fine-tune this behavior later, eg. set a max queue size (in that case, we
must signal the subscriber in some way, possibly with a flag in MessageContext, or a
dedicated callback).
-
+
*/
switch (message)
@@ -314,7 +319,7 @@ async message queues so that they can't block each other.
private void HandleConnectResponse(ConnectResponse message)
{
- _ = Task.Run(
+ _ = Task.Factory.StartNew(
async () =>
{
using (await _mutex.LockAsync())
@@ -322,21 +327,29 @@ private void HandleConnectResponse(ConnectResponse message)
if (message.Error != null)
{
_connectionState = ConnectionState.Closed;
- _stateChangeEvents.Connected.TrySetException(new MessageRouterException(message.Error));
+ var exception = new MessageRouterException(message.Error);
+ _stateChangeEvents.Connected.TrySetException(exception);
+ OnCloseStart();
+ OnCloseStop(exception);
+ OnConnectStop(exception);
}
else
{
_clientId = message.ClientId;
_connectionState = ConnectionState.Connected;
_stateChangeEvents.Connected.TrySetResult();
+ OnConnectStop();
}
}
- });
+ },
+ TaskCreationOptions.RunContinuationsAsynchronously);
}
private void HandleInvokeRequest(InvokeRequest message)
{
- _ = Task.Run(
+ OnRequestStart(message);
+
+ _ = Task.Factory.StartNew(
async () =>
{
try
@@ -385,6 +398,8 @@ private void HandleInvokeRequest(InvokeRequest message)
await SendMessageAsync(
response,
+ (state, exception) => OnRequestStop((Message) state!, exception),
+ message,
_stateChangeEvents.CloseRequested.Token);
}
catch (Exception e)
@@ -393,8 +408,10 @@ await SendMessageAsync(
e,
$"Unhandled exception while processing an {nameof(InvokeRequest)}: {{ExceptionMessage}}",
e.Message);
+ OnRequestStop(message, e);
}
- });
+ },
+ TaskCreationOptions.RunContinuationsAsynchronously);
}
private void HandleResponse(AbstractResponse message)
@@ -404,7 +421,8 @@ private void HandleResponse(AbstractResponse message)
if (message.Error != null)
{
- tcs.TrySetException(new MessageRouterException(message.Error));
+ var exception = new MessageRouterException(message.Error);
+ tcs.TrySetException(exception);
}
else
{
@@ -414,7 +432,7 @@ private void HandleResponse(AbstractResponse message)
private void HandleTopicMessage(Protocol.Messages.TopicMessage message)
{
- if (!_topics.TryGetValue(message.Topic, out var topic))
+ if (!_topics.TryGetValue(message.Topic, out var topic))
return;
var topicMessage = new TopicMessage(
@@ -427,7 +445,26 @@ private void HandleTopicMessage(Protocol.Messages.TopicMessage message)
CorrelationId = message.CorrelationId
});
- topic.OnNext(topicMessage);
+ OnRequestStart(message);
+
+ var wrapper = new MessageWrapper(
+ topicMessage,
+ OnRequestStop,
+ message);
+
+ try
+ {
+ if (!topic.OnNext(wrapper))
+ {
+ OnRequestStop(message);
+ }
+ }
+ catch (Exception e)
+ {
+ OnRequestStop(message, e);
+
+ throw;
+ }
}
private string GenerateRequestId() => Guid.NewGuid().ToString("N");
@@ -464,10 +501,30 @@ private async Task SendRequestAsync(
return (TResponse) await tcs.Task;
}
- private async ValueTask SendMessageAsync(Message message, CancellationToken cancellationToken)
+ private ValueTask SendMessageAsync(Message message, CancellationToken cancellationToken)
{
- await ConnectAsync(CancellationToken.None);
- await _sendChannel.Writer.WriteAsync(message, CancellationToken.None);
+ return SendMessageAsync(message, onDequeued: null, state: null, cancellationToken);
+ }
+
+ private async ValueTask SendMessageAsync(Message message, Action