diff --git a/src/contrib/cluster/Akka.Cluster.Sharding/Serialization/ClusterShardingMessageSerializer.cs b/src/contrib/cluster/Akka.Cluster.Sharding/Serialization/ClusterShardingMessageSerializer.cs index 8ea1c155431..f0cdb80420a 100644 --- a/src/contrib/cluster/Akka.Cluster.Sharding/Serialization/ClusterShardingMessageSerializer.cs +++ b/src/contrib/cluster/Akka.Cluster.Sharding/Serialization/ClusterShardingMessageSerializer.cs @@ -11,6 +11,7 @@ using System.IO; using System.IO.Compression; using System.Linq; +using System.Runtime.Serialization; using Akka.Actor; using Akka.Serialization; using Google.Protobuf; @@ -157,7 +158,7 @@ public override object FromBinary(byte[] bytes, string manifest) if (_fromBinaryMap.TryGetValue(manifest, out var factory)) return factory(bytes); - throw new ArgumentException($"Unimplemented deserialization of message with manifest [{manifest}] in [{this.GetType()}]"); + throw new SerializationException($"Unimplemented deserialization of message with manifest [{manifest}] in [{this.GetType()}]"); } /// diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Client/Serialization/ClusterClientMessageSerializer.cs b/src/contrib/cluster/Akka.Cluster.Tools/Client/Serialization/ClusterClientMessageSerializer.cs index 9363159c9c3..38064a09d0e 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Client/Serialization/ClusterClientMessageSerializer.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Client/Serialization/ClusterClientMessageSerializer.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Runtime.Serialization; using Akka.Actor; using Akka.Serialization; using Google.Protobuf; @@ -76,7 +77,7 @@ public override object FromBinary(byte[] bytes, string manifest) if (_fromBinaryMap.TryGetValue(manifest, out var deserializer)) return deserializer(bytes); - throw new ArgumentException($"Unimplemented deserialization of message with manifest [{manifest}] in serializer {nameof(ClusterClientMessageSerializer)}"); + throw new SerializationException($"Unimplemented deserialization of message with manifest [{manifest}] in serializer {nameof(ClusterClientMessageSerializer)}"); } /// diff --git a/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/Serialization/DistributedPubSubMessageSerializer.cs b/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/Serialization/DistributedPubSubMessageSerializer.cs index 4cecba34427..2960e137d91 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/Serialization/DistributedPubSubMessageSerializer.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/Serialization/DistributedPubSubMessageSerializer.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Runtime.CompilerServices; +using System.Runtime.Serialization; using Akka.Actor; using Akka.Cluster.Tools.PublishSubscribe.Internal; using Akka.Remote.Serialization; @@ -89,7 +90,7 @@ public override object FromBinary(byte[] bytes, string manifest) if (_fromBinaryMap.TryGetValue(manifest, out var deserializer)) return deserializer(bytes); - throw new ArgumentException($"Unimplemented deserialization of message with manifest [{manifest}] in serializer {nameof(DistributedPubSubMessageSerializer)}"); + throw new SerializationException($"Unimplemented deserialization of message with manifest [{manifest}] in serializer {nameof(DistributedPubSubMessageSerializer)}"); } /// diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/Serialization/ClusterSingletonMessageSerializer.cs b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/Serialization/ClusterSingletonMessageSerializer.cs index c5b0a28684b..e29c19fb77f 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/Serialization/ClusterSingletonMessageSerializer.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/Serialization/ClusterSingletonMessageSerializer.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Runtime.Serialization; using Akka.Actor; using Akka.Serialization; @@ -73,7 +74,7 @@ public override object FromBinary(byte[] bytes, string manifest) if (_fromBinaryMap.TryGetValue(manifest, out var mapper)) return mapper(bytes); - throw new ArgumentException($"Unimplemented deserialization of message with manifest [{manifest}] in [{GetType()}]"); + throw new SerializationException($"Unimplemented deserialization of message with manifest [{manifest}] in [{GetType()}]"); } /// diff --git a/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatedDataSerializer.cs b/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatedDataSerializer.cs index 48138afce73..7162db7119f 100644 --- a/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatedDataSerializer.cs +++ b/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatedDataSerializer.cs @@ -7,6 +7,7 @@ using System; using System.IO; +using System.Runtime.Serialization; using Akka.Actor; using Akka.Util; using Hyperion; @@ -17,7 +18,7 @@ namespace Akka.DistributedData.Serialization public sealed class ReplicatedDataSerializer : Serializer { private readonly Hyperion.Serializer _serializer; - + public ReplicatedDataSerializer(ExtendedActorSystem system) : base(system) { var akkaSurrogate = @@ -63,10 +64,25 @@ public override byte[] ToBinary(object obj) /// The object contained in the array public override object FromBinary(byte[] bytes, Type type) { - using (var ms = new MemoryStream(bytes)) + try + { + using (var ms = new MemoryStream(bytes)) + { + var res = _serializer.Deserialize(ms); + return res; + } + } + catch (TypeLoadException e) + { + throw new SerializationException(e.Message, e); + } + catch (NotSupportedException e) + { + throw new SerializationException(e.Message, e); + } + catch (ArgumentException e) { - var res = _serializer.Deserialize(ms); - return res; + throw new SerializationException(e.Message, e); } } } diff --git a/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatorMessageSerializer.cs b/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatorMessageSerializer.cs index f0b661c1ec8..db76dc398c2 100644 --- a/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatorMessageSerializer.cs +++ b/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatorMessageSerializer.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.Serialization; using Akka.Actor; using Akka.DistributedData.Internal; using Akka.Util; @@ -120,9 +121,9 @@ private TVal Get(TKey key, int startIndex) } #endregion - + public static readonly Type WriteAckType = typeof(WriteAck); - + private readonly SmallCache readCache; private readonly SmallCache writeCache; private readonly Hyperion.Serializer serializer; @@ -181,7 +182,7 @@ public override byte[] ToBinary(object obj) if (obj is Write) return writeCache.GetOrAdd((Write) obj); if (obj is Read) return readCache.GetOrAdd((Read)obj); if (obj is WriteAck) return writeAckBytes; - + return Serialize(obj); } @@ -198,9 +199,24 @@ private byte[] Serialize(object obj) public override object FromBinary(byte[] bytes, Type type) { if (type == WriteAckType) return WriteAck.Instance; - using (var stream = new MemoryStream(bytes)) + try + { + using (var stream = new MemoryStream(bytes)) + { + return serializer.Deserialize(stream); + } + } + catch (TypeLoadException e) + { + throw new SerializationException(e.Message, e); + } + catch (NotSupportedException e) + { + throw new SerializationException(e.Message, e); + } + catch (ArgumentException e) { - return serializer.Deserialize(stream); + throw new SerializationException(e.Message, e); } } } diff --git a/src/contrib/serializers/Akka.Serialization.Hyperion/HyperionSerializer.cs b/src/contrib/serializers/Akka.Serialization.Hyperion/HyperionSerializer.cs index 5f9930306f9..a465baffd6f 100644 --- a/src/contrib/serializers/Akka.Serialization.Hyperion/HyperionSerializer.cs +++ b/src/contrib/serializers/Akka.Serialization.Hyperion/HyperionSerializer.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.Serialization; using Akka.Actor; using Akka.Configuration; using Akka.Util; @@ -33,7 +34,7 @@ public class HyperionSerializer : Serializer /// Initializes a new instance of the class. /// /// The actor system to associate with this serializer. - public HyperionSerializer(ExtendedActorSystem system) + public HyperionSerializer(ExtendedActorSystem system) : this(system, HyperionSerializerSettings.Default) { } @@ -43,7 +44,7 @@ public HyperionSerializer(ExtendedActorSystem system) /// /// The actor system to associate with this serializer. /// Configuration passed from related HOCON config path. - public HyperionSerializer(ExtendedActorSystem system, Config config) + public HyperionSerializer(ExtendedActorSystem system, Config config) : this(system, HyperionSerializerSettings.Create(config)) { } @@ -70,7 +71,7 @@ public HyperionSerializer(ExtendedActorSystem system, HyperionSerializerSettings preserveObjectReferences: settings.PreserveObjectReferences, versionTolerance: settings.VersionTolerance, surrogates: new[] { akkaSurrogate }, - knownTypes: provider.GetKnownTypes(), + knownTypes: provider.GetKnownTypes(), ignoreISerializable:true)); } @@ -106,10 +107,25 @@ public override byte[] ToBinary(object obj) /// The object contained in the array public override object FromBinary(byte[] bytes, Type type) { - using (var ms = new MemoryStream(bytes)) + try { - var res = _serializer.Deserialize(ms); - return res; + using (var ms = new MemoryStream(bytes)) + { + var res = _serializer.Deserialize(ms); + return res; + } + } + catch (TypeLoadException e) + { + throw new SerializationException(e.Message, e); + } + catch(NotSupportedException e) + { + throw new SerializationException(e.Message, e); + } + catch (ArgumentException e) + { + throw new SerializationException(e.Message, e); } } @@ -169,23 +185,23 @@ public static HyperionSerializerSettings Create(Config config) } /// - /// When true, it tells to keep + /// When true, it tells to keep /// track of references in serialized/deserialized object graph. /// public readonly bool PreserveObjectReferences; /// - /// When true, it tells to encode + /// When true, it tells to encode /// a list of currently serialized fields into type manifest. /// public readonly bool VersionTolerance; /// - /// A type implementing , that will - /// be used when is being constructed - /// to provide a list of message types that are supposed to be known - /// implicitly by all communicating parties. Implementing class must - /// provide either a default constructor or a constructor taking + /// A type implementing , that will + /// be used when is being constructed + /// to provide a list of message types that are supposed to be known + /// implicitly by all communicating parties. Implementing class must + /// provide either a default constructor or a constructor taking /// as its only parameter. /// public readonly Type KnownTypesProvider; diff --git a/src/core/Akka.Cluster/Serialization/ClusterMessageSerializer.cs b/src/core/Akka.Cluster/Serialization/ClusterMessageSerializer.cs index 1f59ec54442..f98a4df1a83 100644 --- a/src/core/Akka.Cluster/Serialization/ClusterMessageSerializer.cs +++ b/src/core/Akka.Cluster/Serialization/ClusterMessageSerializer.cs @@ -10,6 +10,7 @@ using System.Collections.Immutable; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.Serialization; using Akka.Actor; using Akka.Cluster.Routing; using Akka.Serialization; @@ -26,7 +27,7 @@ public class ClusterMessageSerializer : Serializer public ClusterMessageSerializer(ExtendedActorSystem system) : base(system) { - + _fromBinaryMap = new Dictionary> { [typeof(ClusterHeartbeatSender.Heartbeat)] = bytes => new ClusterHeartbeatSender.Heartbeat(AddressFrom(AddressData.Parser.ParseFrom(bytes))), @@ -87,7 +88,7 @@ public override object FromBinary(byte[] bytes, Type type) if (_fromBinaryMap.TryGetValue(type, out var factory)) return factory(bytes); - throw new ArgumentException($"{nameof(ClusterMessageSerializer)} cannot deserialize object of type {type}"); + throw new SerializationException($"{nameof(ClusterMessageSerializer)} cannot deserialize object of type {type}"); } // @@ -265,11 +266,11 @@ private static Gossip GossipFrom(Proto.Msg.Gossip gossip) var roleMapping = gossip.AllRoles.ToList(); var hashMapping = gossip.AllHashes.ToList(); - Member MemberFromProto(Proto.Msg.Member member) => + Member MemberFromProto(Proto.Msg.Member member) => Member.Create( - addressMapping[member.AddressIndex], - member.UpNumber, - (MemberStatus)member.Status, + addressMapping[member.AddressIndex], + member.UpNumber, + (MemberStatus)member.Status, member.RolesIndexes.Select(x => roleMapping[x]).ToImmutableHashSet()); var members = gossip.Members.Select((Func)MemberFromProto).ToImmutableSortedSet(Member.Ordering); @@ -342,7 +343,7 @@ private static Proto.Msg.VectorClock VectorClockToProto(VectorClock vectorClock, private static VectorClock VectorClockFrom(Proto.Msg.VectorClock version, IList hashMapping) { - return VectorClock.Create(version.Versions.ToImmutableSortedDictionary(version1 => + return VectorClock.Create(version.Versions.ToImmutableSortedDictionary(version1 => VectorClock.Node.FromHash(hashMapping[version1.HashIndex]), version1 => version1.Timestamp)); } diff --git a/src/core/Akka.Remote.Tests/TransientSerializationErrorSpec.cs b/src/core/Akka.Remote.Tests/TransientSerializationErrorSpec.cs new file mode 100644 index 00000000000..5d02ff8dfd9 --- /dev/null +++ b/src/core/Akka.Remote.Tests/TransientSerializationErrorSpec.cs @@ -0,0 +1,215 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2018 Lightbend Inc. +// Copyright (C) 2013-2018 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using Akka.Actor; +using Akka.Configuration; +using Akka.TestKit; +using Xunit; +using Akka.Serialization; +using System.Runtime.Serialization; + +namespace Akka.Remote.Tests +{ + + public abstract class AbstractTransientSerializationErrorSpec : AkkaSpec + { + internal class ManifestNotSerializable + { + public static readonly ManifestNotSerializable Instance = new ManifestNotSerializable(); + + private ManifestNotSerializable() { } + } + + internal class ManifestIllegal + { + public static readonly ManifestIllegal Instance = new ManifestIllegal(); + + private ManifestIllegal() { } + } + + internal class ToBinaryNotSerializable + { + public static readonly ToBinaryNotSerializable Instance = new ToBinaryNotSerializable(); + + private ToBinaryNotSerializable() { } + } + + internal class ToBinaryIllegal + { + public static readonly ToBinaryIllegal Instance = new ToBinaryIllegal(); + + private ToBinaryIllegal() { } + } + + internal class NotDeserializable + { + public static readonly NotDeserializable Instance = new NotDeserializable(); + + private NotDeserializable() { } + } + + internal class IllegalOnDeserialize + { + public static readonly IllegalOnDeserialize Instance = new IllegalOnDeserialize(); + + private IllegalOnDeserialize() { } + } + + internal class TestSerializer : SerializerWithStringManifest + { + public override int Identifier => 666; + + public TestSerializer(ExtendedActorSystem system) + : base(system) + { + } + + public override string Manifest(object o) + { + switch (o) + { + case ManifestNotSerializable _: + throw new SerializationException(); + case ManifestIllegal _: + throw new ArgumentException(); + case ToBinaryNotSerializable _: + return "TBNS"; + case ToBinaryIllegal _: + return "TI"; + case NotDeserializable _: + return "ND"; + case IllegalOnDeserialize _: + return "IOD"; + } + throw new InvalidOperationException(); + } + + public override object FromBinary(byte[] bytes, string manifest) + { + switch (manifest) + { + case "ND": + throw new SerializationException(); + case "IOD": + throw new ArgumentException(); + } + throw new InvalidOperationException(); + } + + public override byte[] ToBinary(object obj) + { + switch (obj) + { + case ToBinaryNotSerializable _: + throw new SerializationException(); + case ToBinaryIllegal _: + throw new ArgumentException(); + default: + return new byte[0]; + } + } + } + + internal class EchoActor : ActorBase + { + public static Props Props() + { + return Actor.Props.Create(() => new EchoActor()); + } + + protected override bool Receive(object message) + { + Sender.Tell(message); + return true; + } + } + + private readonly ActorSystem system2; + private readonly Address system2Address; + + public AbstractTransientSerializationErrorSpec(Config config) + : base(config.WithFallback(ConfigurationFactory.ParseString(GetConfig()))) + { + var port = ((ExtendedActorSystem)Sys).Provider.DefaultAddress.Port; + + system2 = ActorSystem.Create(Sys.Name, Sys.Settings.Config); + system2Address = ((ExtendedActorSystem)system2).Provider.DefaultAddress; + } + + private static string GetConfig() + { + return @" + akka { + loglevel = info + actor { + provider = remote + serializers { + test = ""Akka.Remote.Tests.AbstractTransientSerializationErrorSpec+TestSerializer, Akka.Remote.Tests"" + } + serialization-bindings { + ""Akka.Remote.Tests.AbstractTransientSerializationErrorSpec+ManifestNotSerializable, Akka.Remote.Tests"" = test + ""Akka.Remote.Tests.AbstractTransientSerializationErrorSpec+ManifestIllegal, Akka.Remote.Tests"" = test + ""Akka.Remote.Tests.AbstractTransientSerializationErrorSpec+ToBinaryNotSerializable, Akka.Remote.Tests"" = test + ""Akka.Remote.Tests.AbstractTransientSerializationErrorSpec+ToBinaryIllegal, Akka.Remote.Tests"" = test + ""Akka.Remote.Tests.AbstractTransientSerializationErrorSpec+NotDeserializable, Akka.Remote.Tests"" = test + ""Akka.Remote.Tests.AbstractTransientSerializationErrorSpec+IllegalOnDeserialize, Akka.Remote.Tests"" = test + } + #serialization-identifiers { + # ""Akka.Remote.Tests.AbstractTransientSerializationErrorSpec+TestSerializer, Akka.Remote.Tests"" = 666 + #} + } + } + "; + } + + protected override void AfterTermination() + { + Shutdown(system2); + } + + + [Fact] + public void The_transport_must_stay_alive_after_a_transient_exception_from_the_serializer() + { + system2.ActorOf(EchoActor.Props(), "echo"); + + var selection = Sys.ActorSelection(new RootActorPath(system2Address) / "user" / "echo"); + + selection.Tell("ping", this.TestActor); + ExpectMsg("ping"); + + // none of these should tear down the connection + selection.Tell(ManifestIllegal.Instance, this.TestActor); + selection.Tell(ManifestNotSerializable.Instance, this.TestActor); + selection.Tell(ToBinaryIllegal.Instance, this.TestActor); + selection.Tell(ToBinaryNotSerializable.Instance, this.TestActor); + selection.Tell(NotDeserializable.Instance, this.TestActor); + selection.Tell(IllegalOnDeserialize.Instance, this.TestActor); + + // make sure we still have a connection + selection.Tell("ping", this.TestActor); + ExpectMsg("ping"); + } + } + + + + public class TransientSerializationErrorSpec : AbstractTransientSerializationErrorSpec + { + public TransientSerializationErrorSpec() + : base(ConfigurationFactory.ParseString(@" + akka.remote.dot-netty.tcp { + hostname = localhost + port = 0 + } + ")) + { + } + } +} + diff --git a/src/core/Akka.Remote/Endpoint.cs b/src/core/Akka.Remote/Endpoint.cs index afbcfd80b5a..0254773cedf 100644 --- a/src/core/Akka.Remote/Endpoint.cs +++ b/src/core/Akka.Remote/Endpoint.cs @@ -478,7 +478,7 @@ public ReliableDeliverySupervisor( /// UID matches the expected one, pending Acks can be processed or must be dropped. It is guaranteed that for any inbound /// connections (calling ) the first message from that connection is , therefore it serves /// a separator. - /// + /// /// If we already have an inbound handle then UID is initially confirmed. /// (This actor is never restarted.) /// @@ -1477,7 +1477,18 @@ private bool WriteSend(EndpointManager.Send send) } catch (SerializationException ex) { - _log.Error(ex, "Transient association error (association remains live)"); + _log.Error( + ex, + "Serializer not defined for message type [{0}]. Transient association error (association remains live)", + send.Message.GetType()); + return true; + } + catch(ArgumentException ex) + { + _log.Error( + ex, + "Serializer not defined for message type [{0}]. Transient association error (association remains live)", + send.Message.GetType()); return true; } catch (EndpointException ex) @@ -1911,10 +1922,25 @@ private void Reading() } else { - _msgDispatch.Dispatch(ackAndMessage.MessageOption.Recipient, - ackAndMessage.MessageOption.RecipientAddress, - ackAndMessage.MessageOption.SerializedMessage, - ackAndMessage.MessageOption.SenderOptional); + try + { + _msgDispatch.Dispatch(ackAndMessage.MessageOption.Recipient, + ackAndMessage.MessageOption.RecipientAddress, + ackAndMessage.MessageOption.SerializedMessage, + ackAndMessage.MessageOption.SenderOptional); + } + catch (SerializationException e) + { + LogTransientSerializationError(ackAndMessage.MessageOption, e); + } + catch(ArgumentException e) + { + LogTransientSerializationError(ackAndMessage.MessageOption, e); + } + catch(Exception e) + { + throw e; + } } } } @@ -1927,6 +1953,17 @@ private void Reading() }); } + private void LogTransientSerializationError(Message msg, Exception error) + { + var sm = msg.SerializedMessage; + _log.Warning( + "Serializer not defined for message with serializer id [{0}] and manifest [{1}]. " + + "Transient association error (association remains live). {2}", + sm.SerializerId, + sm.MessageManifest.IsEmpty ? "" : sm.MessageManifest.ToStringUtf8(), + error.Message); + } + private void NotReading() { Receive(disassociated => HandleDisassociated(disassociated.Info)); diff --git a/src/core/Akka/Serialization/Serializer.cs b/src/core/Akka/Serialization/Serializer.cs index c5d3c10353a..1123453a6aa 100644 --- a/src/core/Akka/Serialization/Serializer.cs +++ b/src/core/Akka/Serialization/Serializer.cs @@ -8,6 +8,7 @@ using System; using System.Linq; using System.Reflection; +using System.Runtime.Serialization; using Akka.Actor; using Akka.Annotations; using Akka.Util; @@ -117,6 +118,15 @@ protected SerializerWithStringManifest(ExtendedActorSystem system) : base(system /// /// Deserializes a byte array into an object of type . + /// + /// It's recommended to throw in + /// if the manifest is unknown.This makes it possible to introduce new message + /// types and send them to nodes that don't know about them. This is typically + /// needed when performing rolling upgrades, i.e.running a cluster with mixed + /// versions for while. is treated as a transient + /// problem in the TCP based remoting layer.The problem will be logged + /// and message is dropped.Other exceptions will tear down the TCP connection + /// because it can be an indication of corrupt bytes from the underlying transport. /// /// The array containing the serialized object /// The type of object contained in the array @@ -129,6 +139,15 @@ public sealed override object FromBinary(byte[] bytes, Type type) /// /// Deserializes a byte array into an object using an optional (type hint). + /// + /// It's recommended to throw in + /// if the manifest is unknown.This makes it possible to introduce new message + /// types and send them to nodes that don't know about them. This is typically + /// needed when performing rolling upgrades, i.e.running a cluster with mixed + /// versions for while. is treated as a transient + /// problem in the TCP based remoting layer.The problem will be logged + /// and message is dropped.Other exceptions will tear down the TCP connection + /// because it can be an indication of corrupt bytes from the underlying transport. /// /// The array containing the serialized object /// The type hint used to deserialize the object contained in the array. @@ -137,7 +156,7 @@ public sealed override object FromBinary(byte[] bytes, Type type) /// /// Returns the manifest (type hint) that will be provided in the method. - /// + /// /// /// This method returns if a manifest is not needed. ///