diff --git a/Tapeti.Annotations/DurableQueueAttribute.cs b/Tapeti.Annotations/DurableQueueAttribute.cs
index d6fe89d..281d91f 100644
--- a/Tapeti.Annotations/DurableQueueAttribute.cs
+++ b/Tapeti.Annotations/DurableQueueAttribute.cs
@@ -1,4 +1,5 @@
using System;
+using JetBrains.Annotations;
namespace Tapeti.Annotations
{
@@ -13,6 +14,7 @@ namespace Tapeti.Annotations
/// for deploy-time management of durable queues (shameless plug intended).
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+ [MeansImplicitUse]
public class DurableQueueAttribute : Attribute
{
public string Name { get; set; }
diff --git a/Tapeti.Annotations/DynamicQueueAttribute.cs b/Tapeti.Annotations/DynamicQueueAttribute.cs
index f7de921..3743edf 100644
--- a/Tapeti.Annotations/DynamicQueueAttribute.cs
+++ b/Tapeti.Annotations/DynamicQueueAttribute.cs
@@ -1,4 +1,5 @@
using System;
+using JetBrains.Annotations;
namespace Tapeti.Annotations
{
@@ -8,6 +9,7 @@ namespace Tapeti.Annotations
/// on an entire MessageController class or on individual methods.
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+ [MeansImplicitUse]
public class DynamicQueueAttribute : Attribute
{
public string Prefix { get; set; }
diff --git a/Tapeti.Annotations/MessageControllerAttribute.cs b/Tapeti.Annotations/MessageControllerAttribute.cs
index f4a4723..150fefc 100644
--- a/Tapeti.Annotations/MessageControllerAttribute.cs
+++ b/Tapeti.Annotations/MessageControllerAttribute.cs
@@ -1,4 +1,5 @@
using System;
+using JetBrains.Annotations;
namespace Tapeti.Annotations
{
@@ -8,6 +9,7 @@ namespace Tapeti.Annotations
/// when using the RegisterAllControllers method. It is not required when manually registering a controller.
///
[AttributeUsage(AttributeTargets.Class)]
+ [MeansImplicitUse]
public class MessageControllerAttribute : Attribute
{
}
diff --git a/Tapeti.Annotations/MessageHandlerAttribute.cs b/Tapeti.Annotations/MessageHandlerAttribute.cs
new file mode 100644
index 0000000..d13724e
--- /dev/null
+++ b/Tapeti.Annotations/MessageHandlerAttribute.cs
@@ -0,0 +1,17 @@
+using System;
+using JetBrains.Annotations;
+
+namespace Tapeti.Annotations
+{
+ ///
+ ///
+ /// This attribute does nothing in runtime and is not required. It is only used as
+ /// a hint to ReSharper, and maybe developers as well, to indicate the method is
+ /// indeed used.
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ [MeansImplicitUse]
+ public class MessageHandlerAttribute : Attribute
+ {
+ }
+}
diff --git a/Tapeti.Annotations/ReSharper/JetBrains.Annotations.cs b/Tapeti.Annotations/ReSharper/JetBrains.Annotations.cs
new file mode 100644
index 0000000..9ec2401
--- /dev/null
+++ b/Tapeti.Annotations/ReSharper/JetBrains.Annotations.cs
@@ -0,0 +1,179 @@
+/*
+ * Stripped version of the ReSharper Annotations source. Enables hinting without referencing the
+ * ReSharper.Annotations NuGet package.
+ *
+ * If you need more annotations, this code was generated using
+ * ReSharper - Options - Code Annotations - Copy C# implementation to clipboard
+ */
+
+
+/* MIT License
+
+Copyright (c) 2016 JetBrains http://www.jetbrains.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE. */
+
+using System;
+
+#pragma warning disable 1591
+// ReSharper disable UnusedMember.Global
+// ReSharper disable MemberCanBePrivate.Global
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+// ReSharper disable IntroduceOptionalParameters.Global
+// ReSharper disable MemberCanBeProtected.Global
+// ReSharper disable InconsistentNaming
+
+// ReSharper disable once CheckNamespace
+namespace JetBrains.Annotations
+{
+ ///
+ /// Indicates that the value of the marked element could be null sometimes,
+ /// so the check for null is necessary before its usage.
+ ///
+ ///
+ /// [CanBeNull] object Test() => null;
+ ///
+ /// void UseTest() {
+ /// var p = Test();
+ /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException'
+ /// }
+ ///
+ [AttributeUsage(
+ AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
+ AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event |
+ AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)]
+ internal sealed class CanBeNullAttribute : Attribute { }
+
+ ///
+ /// Indicates that the value of the marked element could never be null.
+ ///
+ ///
+ /// [NotNull] object Foo() {
+ /// return null; // Warning: Possible 'null' assignment
+ /// }
+ ///
+ [AttributeUsage(
+ AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
+ AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event |
+ AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)]
+ internal sealed class NotNullAttribute : Attribute { }
+
+ ///
+ /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library),
+ /// so this symbol will not be marked as unused (as well as by other usage inspections).
+ ///
+ [AttributeUsage(AttributeTargets.All)]
+ internal sealed class UsedImplicitlyAttribute : Attribute
+ {
+ public UsedImplicitlyAttribute()
+ : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { }
+
+ public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags)
+ : this(useKindFlags, ImplicitUseTargetFlags.Default) { }
+
+ public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags)
+ : this(ImplicitUseKindFlags.Default, targetFlags) { }
+
+ public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)
+ {
+ UseKindFlags = useKindFlags;
+ TargetFlags = targetFlags;
+ }
+
+ public ImplicitUseKindFlags UseKindFlags { get; private set; }
+
+ public ImplicitUseTargetFlags TargetFlags { get; private set; }
+ }
+
+ ///
+ /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes
+ /// as unused (as well as by other usage inspections)
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)]
+ internal sealed class MeansImplicitUseAttribute : Attribute
+ {
+ public MeansImplicitUseAttribute()
+ : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { }
+
+ public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags)
+ : this(useKindFlags, ImplicitUseTargetFlags.Default) { }
+
+ public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags)
+ : this(ImplicitUseKindFlags.Default, targetFlags) { }
+
+ public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)
+ {
+ UseKindFlags = useKindFlags;
+ TargetFlags = targetFlags;
+ }
+
+ [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; }
+
+ [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; }
+ }
+
+ [Flags]
+ internal enum ImplicitUseKindFlags
+ {
+ Default = Access | Assign | InstantiatedWithFixedConstructorSignature,
+ /// Only entity marked with attribute considered used.
+ Access = 1,
+ /// Indicates implicit assignment to a member.
+ Assign = 2,
+ ///
+ /// Indicates implicit instantiation of a type with fixed constructor signature.
+ /// That means any unused constructor parameters won't be reported as such.
+ ///
+ InstantiatedWithFixedConstructorSignature = 4,
+ /// Indicates implicit instantiation of a type.
+ InstantiatedNoFixedConstructorSignature = 8,
+ }
+
+ ///
+ /// Specify what is considered used implicitly when marked
+ /// with or .
+ ///
+ [Flags]
+ internal enum ImplicitUseTargetFlags
+ {
+ Default = Itself,
+ Itself = 1,
+ /// Members of entity marked with attribute are considered used.
+ Members = 2,
+ /// Entity marked with attribute and all its members considered used.
+ WithMembers = Itself | Members
+ }
+
+ ///
+ /// This attribute is intended to mark publicly available API
+ /// which should not be removed and so is treated as used.
+ ///
+ [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)]
+ internal sealed class PublicAPIAttribute : Attribute
+ {
+ public PublicAPIAttribute() { }
+
+ public PublicAPIAttribute([NotNull] string comment)
+ {
+ Comment = comment;
+ }
+
+ [CanBeNull] public string Comment { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/Tapeti.Flow/Annotations/StartAttribute.cs b/Tapeti.Flow/Annotations/StartAttribute.cs
index 32f56f7..3f8e767 100644
--- a/Tapeti.Flow/Annotations/StartAttribute.cs
+++ b/Tapeti.Flow/Annotations/StartAttribute.cs
@@ -1,8 +1,10 @@
using System;
+using JetBrains.Annotations;
namespace Tapeti.Flow.Annotations
{
[AttributeUsage(AttributeTargets.Method)]
+ [MeansImplicitUse]
public class StartAttribute : Attribute
{
}
diff --git a/Tapeti.Flow/ReSharper/JetBrains.Annotations.cs b/Tapeti.Flow/ReSharper/JetBrains.Annotations.cs
new file mode 100644
index 0000000..9ec2401
--- /dev/null
+++ b/Tapeti.Flow/ReSharper/JetBrains.Annotations.cs
@@ -0,0 +1,179 @@
+/*
+ * Stripped version of the ReSharper Annotations source. Enables hinting without referencing the
+ * ReSharper.Annotations NuGet package.
+ *
+ * If you need more annotations, this code was generated using
+ * ReSharper - Options - Code Annotations - Copy C# implementation to clipboard
+ */
+
+
+/* MIT License
+
+Copyright (c) 2016 JetBrains http://www.jetbrains.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE. */
+
+using System;
+
+#pragma warning disable 1591
+// ReSharper disable UnusedMember.Global
+// ReSharper disable MemberCanBePrivate.Global
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+// ReSharper disable IntroduceOptionalParameters.Global
+// ReSharper disable MemberCanBeProtected.Global
+// ReSharper disable InconsistentNaming
+
+// ReSharper disable once CheckNamespace
+namespace JetBrains.Annotations
+{
+ ///
+ /// Indicates that the value of the marked element could be null sometimes,
+ /// so the check for null is necessary before its usage.
+ ///
+ ///
+ /// [CanBeNull] object Test() => null;
+ ///
+ /// void UseTest() {
+ /// var p = Test();
+ /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException'
+ /// }
+ ///
+ [AttributeUsage(
+ AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
+ AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event |
+ AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)]
+ internal sealed class CanBeNullAttribute : Attribute { }
+
+ ///
+ /// Indicates that the value of the marked element could never be null.
+ ///
+ ///
+ /// [NotNull] object Foo() {
+ /// return null; // Warning: Possible 'null' assignment
+ /// }
+ ///
+ [AttributeUsage(
+ AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
+ AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event |
+ AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)]
+ internal sealed class NotNullAttribute : Attribute { }
+
+ ///
+ /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library),
+ /// so this symbol will not be marked as unused (as well as by other usage inspections).
+ ///
+ [AttributeUsage(AttributeTargets.All)]
+ internal sealed class UsedImplicitlyAttribute : Attribute
+ {
+ public UsedImplicitlyAttribute()
+ : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { }
+
+ public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags)
+ : this(useKindFlags, ImplicitUseTargetFlags.Default) { }
+
+ public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags)
+ : this(ImplicitUseKindFlags.Default, targetFlags) { }
+
+ public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)
+ {
+ UseKindFlags = useKindFlags;
+ TargetFlags = targetFlags;
+ }
+
+ public ImplicitUseKindFlags UseKindFlags { get; private set; }
+
+ public ImplicitUseTargetFlags TargetFlags { get; private set; }
+ }
+
+ ///
+ /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes
+ /// as unused (as well as by other usage inspections)
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)]
+ internal sealed class MeansImplicitUseAttribute : Attribute
+ {
+ public MeansImplicitUseAttribute()
+ : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { }
+
+ public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags)
+ : this(useKindFlags, ImplicitUseTargetFlags.Default) { }
+
+ public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags)
+ : this(ImplicitUseKindFlags.Default, targetFlags) { }
+
+ public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)
+ {
+ UseKindFlags = useKindFlags;
+ TargetFlags = targetFlags;
+ }
+
+ [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; }
+
+ [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; }
+ }
+
+ [Flags]
+ internal enum ImplicitUseKindFlags
+ {
+ Default = Access | Assign | InstantiatedWithFixedConstructorSignature,
+ /// Only entity marked with attribute considered used.
+ Access = 1,
+ /// Indicates implicit assignment to a member.
+ Assign = 2,
+ ///
+ /// Indicates implicit instantiation of a type with fixed constructor signature.
+ /// That means any unused constructor parameters won't be reported as such.
+ ///
+ InstantiatedWithFixedConstructorSignature = 4,
+ /// Indicates implicit instantiation of a type.
+ InstantiatedNoFixedConstructorSignature = 8,
+ }
+
+ ///
+ /// Specify what is considered used implicitly when marked
+ /// with or .
+ ///
+ [Flags]
+ internal enum ImplicitUseTargetFlags
+ {
+ Default = Itself,
+ Itself = 1,
+ /// Members of entity marked with attribute are considered used.
+ Members = 2,
+ /// Entity marked with attribute and all its members considered used.
+ WithMembers = Itself | Members
+ }
+
+ ///
+ /// This attribute is intended to mark publicly available API
+ /// which should not be removed and so is treated as used.
+ ///
+ [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)]
+ internal sealed class PublicAPIAttribute : Attribute
+ {
+ public PublicAPIAttribute() { }
+
+ public PublicAPIAttribute([NotNull] string comment)
+ {
+ Comment = comment;
+ }
+
+ [CanBeNull] public string Comment { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/Tapeti/Connection/TapetiWorker.cs b/Tapeti/Connection/TapetiWorker.cs
index 796bea1..3a6a85b 100644
--- a/Tapeti/Connection/TapetiWorker.cs
+++ b/Tapeti/Connection/TapetiWorker.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -37,6 +38,9 @@ namespace Tapeti.Connection
private IModel channelInstance;
private ulong lastDeliveryTag;
private DateTime connectedDateTime;
+
+ // These fields must be locked, since the callbacks for BasicAck/BasicReturn can run in a different thread
+ private readonly object confirmLock = new Object();
private readonly Dictionary confirmMessages = new Dictionary();
private readonly Dictionary returnRoutingKeys = new Dictionary();
@@ -211,6 +215,9 @@ namespace Tapeti.Connection
async (handler, next) => await handler.Handle(context, next),
() => taskQueue.Value.Add(async () =>
{
+ if (Thread.CurrentThread.ManagedThreadId != 3)
+ Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
+
var body = messageSerializer.Serialize(context.Message, context.Properties);
Task publishResultTask = null;
@@ -228,7 +235,16 @@ namespace Tapeti.Connection
{
lastDeliveryTag++;
- confirmMessages.Add(lastDeliveryTag, messageInfo);
+ Monitor.Enter(confirmLock);
+ try
+ {
+ confirmMessages.Add(lastDeliveryTag, messageInfo);
+ }
+ finally
+ {
+ Monitor.Exit(confirmLock);
+ }
+
publishResultTask = messageInfo.CompletionSource.Task;
}
else
@@ -329,7 +345,17 @@ namespace Tapeti.Connection
if (config.UsePublisherConfirms)
{
lastDeliveryTag = 0;
- confirmMessages.Clear();
+
+ Monitor.Enter(confirmLock);
+ try
+ {
+ confirmMessages.Clear();
+ }
+ finally
+ {
+ Monitor.Exit(confirmLock);
+ }
+
channelInstance.ConfirmSelect();
}
@@ -406,35 +432,51 @@ namespace Tapeti.Connection
private void HandleBasicAck(object sender, BasicAckEventArgs e)
{
- foreach (var deliveryTag in GetDeliveryTags(e))
+ Monitor.Enter(confirmLock);
+ try
{
- if (!confirmMessages.TryGetValue(deliveryTag, out var messageInfo))
- continue;
-
- if (returnRoutingKeys.TryGetValue(messageInfo.ReturnKey, out var returnInfo))
+ foreach (var deliveryTag in GetDeliveryTags(e))
{
- messageInfo.CompletionSource.SetResult(returnInfo.FirstReplyCode);
+ if (!confirmMessages.TryGetValue(deliveryTag, out var messageInfo))
+ continue;
- returnInfo.RefCount--;
- if (returnInfo.RefCount == 0)
- returnRoutingKeys.Remove(messageInfo.ReturnKey);
+ if (returnRoutingKeys.TryGetValue(messageInfo.ReturnKey, out var returnInfo))
+ {
+ messageInfo.CompletionSource.SetResult(returnInfo.FirstReplyCode);
+
+ returnInfo.RefCount--;
+ if (returnInfo.RefCount == 0)
+ returnRoutingKeys.Remove(messageInfo.ReturnKey);
+ }
+
+ messageInfo.CompletionSource.SetResult(0);
+ confirmMessages.Remove(deliveryTag);
}
-
- messageInfo.CompletionSource.SetResult(0);
- confirmMessages.Remove(deliveryTag);
+ }
+ finally
+ {
+ Monitor.Exit(confirmLock);
}
}
private void HandleBasicNack(object sender, BasicNackEventArgs e)
{
- foreach (var deliveryTag in GetDeliveryTags(e))
+ Monitor.Enter(confirmLock);
+ try
{
- if (!confirmMessages.TryGetValue(deliveryTag, out var messageInfo))
- continue;
+ foreach (var deliveryTag in GetDeliveryTags(e))
+ {
+ if (!confirmMessages.TryGetValue(deliveryTag, out var messageInfo))
+ continue;
- messageInfo.CompletionSource.SetCanceled();
- confirmMessages.Remove(e.DeliveryTag);
+ messageInfo.CompletionSource.SetCanceled();
+ confirmMessages.Remove(e.DeliveryTag);
+ }
+ }
+ finally
+ {
+ Monitor.Exit(confirmLock);
}
}