Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Azure.Core.Amqp] Lazy Allocation for All Sections #21791

Merged
merged 1 commit into from
Jun 17, 2021
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
31 changes: 31 additions & 0 deletions sdk/core/Azure.Core.Amqp/Azure.Core.Amqp.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
jsquire marked this conversation as resolved.
Show resolved Hide resolved
# Visual Studio Version 16
VisualStudioVersion = 16.0.31205.134
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Amqp", "src\Azure.Core.Amqp.csproj", "{B5AD38C8-DAB6-40E6-AB79-41A8ED9D297B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Amqp.Tests", "tests\Azure.Core.Amqp.Tests.csproj", "{61ABA08F-FA50-4DEA-8C97-00B7B057BDEF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B5AD38C8-DAB6-40E6-AB79-41A8ED9D297B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5AD38C8-DAB6-40E6-AB79-41A8ED9D297B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5AD38C8-DAB6-40E6-AB79-41A8ED9D297B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5AD38C8-DAB6-40E6-AB79-41A8ED9D297B}.Release|Any CPU.Build.0 = Release|Any CPU
{61ABA08F-FA50-4DEA-8C97-00B7B057BDEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{61ABA08F-FA50-4DEA-8C97-00B7B057BDEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{61ABA08F-FA50-4DEA-8C97-00B7B057BDEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{61ABA08F-FA50-4DEA-8C97-00B7B057BDEF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1DC074C7-A8F0-4778-B791-323040BDF4C5}
EndGlobalSection
EndGlobal
12 changes: 4 additions & 8 deletions sdk/core/Azure.Core.Amqp/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

## 1.2.0-beta.1 (Unreleased)

### Features Added

### Breaking Changes

### Key Bugs Fixed

### Fixed

### Added
- All section properties of the `AmqpAnnotatedMessage` are now lazily allocated to reflect that they are defined as optional in the AMQP specification, section 3.2.
jsquire marked this conversation as resolved.
Show resolved Hide resolved

- The `HasSection` method has been added to `AmqpAnnotatedMessage` to allow inspecting the property for a section to determine if it is populated without triggering an allocation.

## 1.1.0 (2021-06-16)

Expand Down
11 changes: 11 additions & 0 deletions sdk/core/Azure.Core.Amqp/api/Azure.Core.Amqp.netstandard2.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public AmqpAnnotatedMessage(Azure.Core.Amqp.AmqpMessageBody body) { }
public Azure.Core.Amqp.AmqpMessageHeader Header { get { throw null; } }
public System.Collections.Generic.IDictionary<string, object> MessageAnnotations { get { throw null; } }
public Azure.Core.Amqp.AmqpMessageProperties Properties { get { throw null; } }
public bool HasSection(Azure.Core.Amqp.AmqpMessageSection section) { throw null; }
}
public partial class AmqpMessageBody
{
Expand Down Expand Up @@ -87,4 +88,14 @@ internal AmqpMessageProperties() { }
public Azure.Core.Amqp.AmqpAddress? To { get { throw null; } set { } }
public System.ReadOnlyMemory<byte>? UserId { get { throw null; } set { } }
}
public enum AmqpMessageSection
{
Header = 0,
DeliveryAnnotations = 1,
MessageAnnotations = 2,
Properties = 3,
ApplicationProperties = 4,
Body = 5,
Footer = 6,
}
}
46 changes: 44 additions & 2 deletions sdk/core/Azure.Core.Amqp/src/AmqpAnnotatedMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,19 @@ public AmqpAnnotatedMessage(AmqpMessageBody body)
/// Gets the header of the AMQP message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-header" />
/// </summary>
public AmqpMessageHeader Header { get; } = new AmqpMessageHeader();
public AmqpMessageHeader Header
{
get
{
if (_header == null)
{
_header = new AmqpMessageHeader();
}
return _header;
}
}

private AmqpMessageHeader? _header;

/// <summary>
/// Gets the footer of the AMQP message.
Expand Down Expand Up @@ -86,7 +98,19 @@ public IDictionary<string, object> MessageAnnotations
/// <summary>
/// Gets the properties of the AMQP message.
/// </summary>
public AmqpMessageProperties Properties { get; } = new AmqpMessageProperties();
public AmqpMessageProperties Properties
{
get
{
if (_properties == null)
{
_properties = new AmqpMessageProperties();
}
return _properties;
}
}

private AmqpMessageProperties? _properties;

/// <summary>
/// Gets the application properties of the AMQP message.
Expand All @@ -111,5 +135,23 @@ public IDictionary<string, object> ApplicationProperties
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-data"/>
/// </summary>
public AmqpMessageBody Body { get; set; }

/// <summary>
/// Determines whether the specified section is present for the AMQP message.
/// </summary>
/// <param name="section">The section to consider.</param>
/// <returns><c>true</c> if the specified <paramref name="section"/> is populated for the AMQP message; otherwise, <c>false</c>.</returns>
public bool HasSection(AmqpMessageSection section) =>
section switch
{
AmqpMessageSection.Header => (_header != null),
AmqpMessageSection.DeliveryAnnotations => (_deliveryAnnotations != null),
AmqpMessageSection.MessageAnnotations => (_messageAnnotations != null),
AmqpMessageSection.Properties => (_properties != null),
AmqpMessageSection.ApplicationProperties => (_applicationProperties != null),
AmqpMessageSection.Body => (Body != null),
AmqpMessageSection.Footer => (_footer != null),
_ => throw new ArgumentException($"Unknown AMQP message section: { section }.", nameof(section))
};
}
}
56 changes: 56 additions & 0 deletions sdk/core/Azure.Core.Amqp/src/AmqpMessageSection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Azure.Core.Amqp
{
/// <summary>
/// Represents the sections of an AMQP message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#section-message-format"/>
/// </summary>
public enum AmqpMessageSection
{
/// <summary>
/// The header section of the message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-header"/>
/// <seealso cref="AmqpMessageHeader" />
/// </summary>
Header,

/// <summary>
/// The delivery annotations section of the message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-delivery-annotations"/>
/// </summary>
DeliveryAnnotations,

/// <summary>
/// The message annotations section of the message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-message-annotations"/>
/// </summary>
MessageAnnotations,

/// <summary>
/// The properties section of the message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-properties"/>
/// <seealso cref="AmqpMessageProperties"/>
/// </summary>
Properties,

/// <summary>
/// The application properties section of the message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-application-properties"/>
/// </summary>
ApplicationProperties,

/// <summary>
/// The body of the message, representing one specific <see cref="AmqpMessageBodyType" /> sections.
/// <seealso cref="AmqpMessageBody"/>
/// </summary>
Body,

/// <summary>
/// The footer section of the message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-footer"/>
/// </summary>
Footer
}
}
89 changes: 89 additions & 0 deletions sdk/core/Azure.Core.Amqp/tests/AmqpAnnotatedMessageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace Azure.Core.Amqp.Tests
{
public class AmqpAnnotatedMessageTests
{
private static readonly AmqpMessageBody EmptyDataBody = AmqpMessageBody.FromData(new ReadOnlyMemory<byte>[] { Array.Empty<byte>() });

[Test]
public void CanCreateAnnotatedMessage()
{
Expand Down Expand Up @@ -65,5 +67,92 @@ public void CanCreateAnnotatedMessage()
Assert.AreEqual("to", message.Properties.To.ToString());
Assert.AreEqual("userId", Encoding.UTF8.GetString(message.Properties.UserId.Value.ToArray()));
}

[Test]
public void HeaderIsCreatedOnDemand()
{
var message = new AmqpAnnotatedMessage(EmptyDataBody);
Assert.False(message.HasSection(AmqpMessageSection.Header));

message.Header.DeliveryCount = 99;
Assert.True(message.HasSection(AmqpMessageSection.Header));
Assert.NotNull(message.Header);
}

[Test]
public void DeliveryAnnotationsAreCreatedOnDemand()
{
var message = new AmqpAnnotatedMessage(EmptyDataBody);
Assert.False(message.HasSection(AmqpMessageSection.DeliveryAnnotations));

message.DeliveryAnnotations.Add("test", new object());
Assert.True(message.HasSection(AmqpMessageSection.DeliveryAnnotations));
Assert.NotNull(message.DeliveryAnnotations);
}

[Test]
public void MessageAnnotationsAreCreatedOnDemand()
{
var message = new AmqpAnnotatedMessage(EmptyDataBody);
Assert.False(message.HasSection(AmqpMessageSection.MessageAnnotations));

message.MessageAnnotations.Add("test", new object());
Assert.True(message.HasSection(AmqpMessageSection.MessageAnnotations));
Assert.NotNull(message.MessageAnnotations);
}

[Test]
public void PropertiesAreCreatedOnDemand()
{
var message = new AmqpAnnotatedMessage(EmptyDataBody);
Assert.False(message.HasSection(AmqpMessageSection.Properties));

message.Properties.ContentType = "test/unit";
Assert.True(message.HasSection(AmqpMessageSection.Properties));
Assert.NotNull(message.Properties);
}

[Test]
public void ApplicationPropertiesAreCreatedOnDemand()
{
var message = new AmqpAnnotatedMessage(EmptyDataBody);
Assert.False(message.HasSection(AmqpMessageSection.ApplicationProperties));

message.ApplicationProperties.Add("test", new object());
Assert.True(message.HasSection(AmqpMessageSection.ApplicationProperties));
Assert.NotNull(message.ApplicationProperties);
}

[Test]
public void FooterIsCreatedOnDemand()
{
var message = new AmqpAnnotatedMessage(EmptyDataBody);
Assert.False(message.HasSection(AmqpMessageSection.Footer));

message.Footer.Add("test", new object());
Assert.True(message.HasSection(AmqpMessageSection.Footer));
Assert.NotNull(message.Footer);
}

[Test]
public void BodyIsDetectedByHasSection()
{
var message = new AmqpAnnotatedMessage(EmptyDataBody);

message.Body = null;
Assert.False(message.HasSection(AmqpMessageSection.Body));

message.Body = AmqpMessageBody.FromValue("this is a string value");
Assert.True(message.HasSection(AmqpMessageSection.Body));
}

[Test]
public void HasSectionValidatesTheSection()
{
var invalidSection = (AmqpMessageSection)int.MinValue;
var message = new AmqpAnnotatedMessage(EmptyDataBody);

Assert.Throws<ArgumentException>(() => message.HasSection(invalidSection));
}
}
}