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 f9d577a..df0f3d7 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(); @@ -208,6 +212,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; @@ -225,7 +232,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 @@ -326,7 +342,17 @@ namespace Tapeti.Connection if (config.UsePublisherConfirms) { lastDeliveryTag = 0; - confirmMessages.Clear(); + + Monitor.Enter(confirmLock); + try + { + confirmMessages.Clear(); + } + finally + { + Monitor.Exit(confirmLock); + } + channelInstance.ConfirmSelect(); } @@ -403,35 +429,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); } }