Protobuf.net 异常 - 检查元数据时超时

Posted

技术标签:

【中文标题】Protobuf.net 异常 - 检查元数据时超时【英文标题】:Protobuf.net Exception - Timeout while inspecting metadata 【发布时间】:2011-09-10 15:07:04 【问题描述】:

有时在尝试使用 protobuf.net 反序列化对象时收到以下异常。我很惊讶,因为我从来没有超过一个线程同时反序列化同一个对象,并且 protobuf.net 源似乎没有使用任何静态对象进行反序列化。例外确实提出了一个解决方案,但我不确定如何实施,所以欢迎举个例子。

Base Exception Type:
System.TimeoutException: Timeout while inspecting metadata; this may indicate a deadlock. This can often be avoided by preparing necessary serializers during application initialization, rather than allowing multiple threads to perform the initial metadata inspection
at ProtoBuf.Meta.RuntimeTypeModel.TakeLock(Boolean& lockTaken)
at ProtoBuf.Meta.RuntimeTypeModel.FindOrAddAuto(Type type, Boolean demand, Boolean addWithContractOnly, Boolean addEvenIfAutoDisabled)
at ProtoBuf.Meta.RuntimeTypeModel.GetKey(Type type, Boolean demand, Boolean getBaseKey)

Inner Exception Type:
System.TimeoutException: Timeout while inspecting metadata; this may indicate a deadlock. This can often be avoided by preparing necessary serializers during application initialization, rather than allowing multiple threads to perform the initial metadata inspection
at ProtoBuf.Meta.RuntimeTypeModel.TakeLock(Boolean& lockTaken)
at ProtoBuf.Meta.RuntimeTypeModel.FindOrAddAuto(Type type, Boolean demand, Boolean addWithContractOnly, Boolean addEvenIfAutoDisabled)
at ProtoBuf.Meta.RuntimeTypeModel.GetKey(Type type, Boolean demand, Boolean getBaseKey)

Stack Trace: 
at ProtoBuf.Meta.RuntimeTypeModel.GetKey(Type type, Boolean demand, Boolean getBaseKey)
at ProtoBuf.Meta.TypeModel.GetKey(Type& type)
at ProtoBuf.Meta.TypeModel.Deserialize(Stream source, Object value, Type type)

问候, 马克

编辑添加:我这样定义我的可序列化对象:

[ProtoContract]
public class Job

    [ProtoMember(1)]
    public long JobId  get; private set;  

我很难在每个可序列化对象上轻松调用 PrepareSerialiser,因为我在不同的命名空间中有很多。但是考虑一下,如果要求 protobuf 在完全相同的时间反序列化两个相同类型的对象,这是它以前从未见过的类型,会发生什么?

【问题讨论】:

嘿,马克。如果我能可靠地重现该问题,我肯定会提供更多信息。不幸的是,这种情况发生在如此小比例 (0.01%) 的情况下,事实证明非常很难确定。我序列化的所有对象都相对较小,可能只有 10 个值类型和几个列表。我会找出可以在哪里添加 prepareSerializer 调用,如果问题仍然存在,请告诉您。 我很感兴趣...我将尝试浏览代码以查找可能导致此问题的任何内容(感谢包含完整的堆栈跟踪,顺便说一句 - 这很有帮助)。这……很奇怪。 值得补充的是,虽然我只有一个线程反序列化一个唯一的字节 [],但我可能有多达 100 个线程同时反序列化 100 个唯一的字节 []。可能是与 CPU 负载有关的问题,即开始反序列化和结束之间的时间太长? 【参考方案1】:

老问题,但如果有人碰巧得到这个错误,请检查你正在使用的 DLL 版本。此异常出现在便携版本中的可能性非常高。

便携版有几个与此问题相关的 PR,它们是 https://github.com/mgravell/protobuf-net/pull/98 和 https://github.com/mgravell/protobuf-net/pull/114。

【讨论】:

我在线程环境中使用 2.0.0.640 PCL 库的经历非常一致。在更高级别的项目中添加对显式版本的引用可以通过将 PCL 版本替换为特定于该更高级别程序集(SL、.Net 40 等)的版本来解决此问题,从而立即解决问题。 这个答案和评论为我节省了很多时间。很棒的见解,朋友们。 PCL 版本 2.0.0.668 对我来说始终出现此错误。我还尝试增加上面建议的超时@MarcGravell,但这并不能解决问题。除了切换到 .DLL 的各个 CLR 特定版本之外,还有其他线索吗? 在 2016 年,这仍然是个问题。我在一次部署中不小心用 PCL 版本替换了正常的 2.0.0.668,并且立即出现了许多超时错误。删除 PCL 有助于解决问题。【参考方案2】:

RuntimeTypeModel.Default(默认模型)是静态的,是静态 Serializer 类的基础(即没有任何静态状态的注释)。尽管由于偏执而添加了此检查,但我从未看到此错误出现。我非常希望看到一个可以重现这一点的例子。你确定你不是线程?如果不是线程,我只能想知道:类型模型真的真的大吗?

确实,即使有许多线程在启动时积极地攻击它(即在 *** 上),它也表现良好。正如错误消息提示的那样,您可以尝试在应用启动期间调用 Serializer.PrepareSerializer,这将预先初始化所有内容,避免任何线程问题。

但是,嘿!至少没有死锁!

然而,这里的奇怪之处在于它仍然不应该可能死锁 - 它故意使用粗锁来避免锁定顺序的问题。再说一次 - 我真的很想看看样品。

【讨论】:

@MarcF - 作为一个随机的东西,你可以尝试将 RuntimeTypeModel.Default.MetadataTimeoutMilliseconds 设置为一个愚蠢的大东西,看看这是否是一个真正的死锁与比正常时间更长的东西? 抱歉这个回复拖了这么久。我刚刚浏览了一些旧的 *** 帖子。我确实将 MetadataTimeoutMilliseconds 增加到 '150000' 并且我认为从那以后我没有看到错误。 只是想补充一下,我也遇到了这个错误。在数千次测试中只有 1 次,但我确实有多个线程在做这项工作。 (4 个大气压,但会随着更多事物的出现而增长) 这只是在我对所有内核执行 CPU 密集型工作时发生的。我的整个机器很慢。许多线程试图获取锁,其中一个线程处于某种“编译”方法中。 @AgileJon 如果你想完全消除它,你可以使用单独的预编译器 exe 生成一个序列化 dll,然后永远不需要做任何元数据检查【参考方案3】:

为我准备序列化器使用

Serializer.PrepareSerializer<Type>();

并没有真正帮助我。

对我来说,解决方案(又名解决方法)是在启动时序列化类型,这会在应用启动时造成问题:

MessageSerialization.Serialize(new Type());

其中MessageSerialization.Serialize是使用protobuf Serializer.Serialize(stream, o)的序列化方法

【讨论】:

【参考方案4】:

我的服务器上也出现了同样的错误。虽然我不确定是什么导致了错误。几天前,当我们的服务器处于我们经历过的最高负载时,它每隔几个小时就发生了两次。运行 8 台服务器,所有服务器的 CPU 在几秒钟内从 70% 上升到 100%,但时间略有不同。例如,每个服务器可能在第一个服务器后 1-5 分钟就开始了这个峰值。

以前从未见过这种情况,我已经在生产中使用了几个月的代码。仍然无法重现它,我不知道是否抛出错误是因为服务器处于 100% CPU,或者这是否是导致服务器峰值的原因。停止与服务器的所有连接并让 cpu 回落到 0 解决了这个问题。不需要重新启动 iis。

当 IIS 启动时,我为每种类型运行一次以下代码。

var type = this.GetType();
RuntimeTypeModel.Default.Add(type, true);
Int32 i = 1;
foreach(PropertyInfo info in type.GetProperties())

    if(info.CanWrite)
    
        RuntimeTypeModel.Default[type].AddField(i++, info.Name);
    

【讨论】:

以上是关于Protobuf.net 异常 - 检查元数据时超时的主要内容,如果未能解决你的问题,请参考以下文章

Protobuf-net - 啥被序列化了?

Protobuf.net “一旦生成了序列化程序,就无法更改类型”

Protobuf-net 序列化错误 =“一旦生成了序列化程序,就无法更改类型”

访问 IIS 元数据库失败

protobuf -net 不知道如何序列化 type = object 的数据成员

Protobuf Net 和从二进制文件存储/检索数据块