使用 SqlScaleoutConfiguration 的 SignalR 通知为 Rebus.Transport.TransactionContext 不可序列化提供随机异常
SignalR notification using SqlScaleoutConfiguration gives random exception for Rebus.Transport.TransactionContext not serializable
我使用:
SqlScaleoutConfiguration 中的 SignalR 2.2.2
画布 3.0.1
存储在 Rebus 中的一些事件由通知中心处理并使用 signalR 推送到客户端。
一切正常,但今天早上,在发布新版本后,none 的客户收到了 "new version" 消息,可能是因为以下异常:
10:39:04.586| |ERROR| |ProcessId=8196| |ThreadId=5| |SignalR.SqlMessageBus| |Stream 0 : Error starting SQL notification listener: System.Runtime.Serialization.SerializationException: Type 'Rebus.Transport.TransactionContext' in Assembly 'Rebus, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.
Server stack trace:
at System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(RuntimeType type)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context)
at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitMemberInfo()
at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder)
at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo)
at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
at System.Runtime.Remoting.Channels.CrossAppDomainSerializer.SerializeMessageParts(ArrayList argsToSerialize)
at System.Runtime.Remoting.Messaging.SmuggledMethodCallMessage..ctor(IMethodCallMessage mcm)
at System.Runtime.Remoting.Messaging.SmuggledMethodCallMessage.SmuggleIfPossible(IMessage msg)
at System.Runtime.Remoting.Channels.CrossAppDomainSink.SyncProcessMessage(IMessage reqMsg)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at System._AppDomain.CreateInstance(String assemblyName, String typeName)
at System.Data.SqlClient.SqlDependency.CreateProcessDispatcher(_AppDomain masterDomain)
at System.Data.SqlClient.SqlDependency.ObtainProcessDispatcher()
at System.Data.SqlClient.SqlDependency.Start(String connectionString, String queue, Boolean useDefaults)
at Microsoft.AspNet.SignalR.SqlServer.ObservableDbOperation.StartSqlDependencyListener()
Rebus 队列中的消息得到正确处理。
处理程序是这样的:
public async Task Handle(ApplicationVersionEvent message)
{
await
Clients.All.CheckApplicationVersion(new ApplicationCurrentVersionNotification
{
CurrentVersion = message.CurrentVersion
});
}
重启解决了,但我需要了解发生了什么。
我的类似问题是:
- https://github.com/SignalR/SignalR/issues/3401
- https://github.com/SignalR/SignalR/issues/3404
- SQL Query Notifications do not always work in scaleout setup (SQL Server)
- https://github.com/rebus-org/Rebus/issues/493
但我认为这不是同一种情况。
除了您已经发现的内容之外,我真的很难告诉您这里发生了什么:SignalR 出于某些奇怪的原因 似乎想要序列化隐藏在当前执行上下文,其中一个值是 Rebus 的当前事务上下文。
正如您所包含的链接中所解释的那样,Rebus 在处理消息时以这种方式存储 "ambient transaction",从而允许将其所有操作纳入同一工作单元。
您可以使用解释的方法,其中以安全的方式临时删除事务上下文
public async Task Handle(SomeMessage message)
{
var transactionContext = AmbientTransactionContext.Current;
AmbientTransactionContext.Current = null;
try
{
JuggleWithAppDomainsInHere();
}
finally
{
AmbientTransactionContext.Current = transactionContext;
}
}
可能将相关位分别移动到实现 IDisposable
的 class 中的构造函数/Dispose
方法,使更平滑 API:
using(new DismantleAmbientRebusStuff())
{
JuggleWithAppDomainsInHere();
}
我认为如果我们要找出真正发生的事情,需要对 SignalR 了解很多的人插话。
我忘记了这个问题,不过后来我通过变通方法解决了它。
线索是 SqlMessageBus 在初始化时序列化上下文,这发生在第一次检索调用 GetHubContext 时,所以我在执行任何命令之前强制初始化它。
app.MapSignalR();
var context = new OwinContext(app.Properties);
var token = context.Get<CancellationToken>("host.OnAppDisposing");
if (token != CancellationToken.None)
{
token.Register(() =>
{
log.Info("host.OnAppDisposing");
// code to run when server shuts down
BackendMessageBusConfig.DisposeContainers();
});
}
// this code brings forward SignalR SqlMessageBus initialization
var forceInit = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
BackendMessageBusConfig.Register();
我使用: SqlScaleoutConfiguration 中的 SignalR 2.2.2 画布 3.0.1
存储在 Rebus 中的一些事件由通知中心处理并使用 signalR 推送到客户端。
一切正常,但今天早上,在发布新版本后,none 的客户收到了 "new version" 消息,可能是因为以下异常:
10:39:04.586| |ERROR| |ProcessId=8196| |ThreadId=5| |SignalR.SqlMessageBus| |Stream 0 : Error starting SQL notification listener: System.Runtime.Serialization.SerializationException: Type 'Rebus.Transport.TransactionContext' in Assembly 'Rebus, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.
Server stack trace:
at System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(RuntimeType type)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context)
at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitMemberInfo()
at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder)
at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo)
at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
at System.Runtime.Remoting.Channels.CrossAppDomainSerializer.SerializeMessageParts(ArrayList argsToSerialize)
at System.Runtime.Remoting.Messaging.SmuggledMethodCallMessage..ctor(IMethodCallMessage mcm)
at System.Runtime.Remoting.Messaging.SmuggledMethodCallMessage.SmuggleIfPossible(IMessage msg)
at System.Runtime.Remoting.Channels.CrossAppDomainSink.SyncProcessMessage(IMessage reqMsg)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at System._AppDomain.CreateInstance(String assemblyName, String typeName)
at System.Data.SqlClient.SqlDependency.CreateProcessDispatcher(_AppDomain masterDomain)
at System.Data.SqlClient.SqlDependency.ObtainProcessDispatcher()
at System.Data.SqlClient.SqlDependency.Start(String connectionString, String queue, Boolean useDefaults)
at Microsoft.AspNet.SignalR.SqlServer.ObservableDbOperation.StartSqlDependencyListener()
Rebus 队列中的消息得到正确处理。
处理程序是这样的:
public async Task Handle(ApplicationVersionEvent message)
{
await
Clients.All.CheckApplicationVersion(new ApplicationCurrentVersionNotification
{
CurrentVersion = message.CurrentVersion
});
}
重启解决了,但我需要了解发生了什么。
我的类似问题是:
- https://github.com/SignalR/SignalR/issues/3401
- https://github.com/SignalR/SignalR/issues/3404
- SQL Query Notifications do not always work in scaleout setup (SQL Server)
- https://github.com/rebus-org/Rebus/issues/493
但我认为这不是同一种情况。
除了您已经发现的内容之外,我真的很难告诉您这里发生了什么:SignalR 出于某些奇怪的原因 似乎想要序列化隐藏在当前执行上下文,其中一个值是 Rebus 的当前事务上下文。
正如您所包含的链接中所解释的那样,Rebus 在处理消息时以这种方式存储 "ambient transaction",从而允许将其所有操作纳入同一工作单元。
您可以使用解释的方法
public async Task Handle(SomeMessage message)
{
var transactionContext = AmbientTransactionContext.Current;
AmbientTransactionContext.Current = null;
try
{
JuggleWithAppDomainsInHere();
}
finally
{
AmbientTransactionContext.Current = transactionContext;
}
}
可能将相关位分别移动到实现 IDisposable
的 class 中的构造函数/Dispose
方法,使更平滑 API:
using(new DismantleAmbientRebusStuff())
{
JuggleWithAppDomainsInHere();
}
我认为如果我们要找出真正发生的事情,需要对 SignalR 了解很多的人插话。
我忘记了这个问题,不过后来我通过变通方法解决了它。
线索是 SqlMessageBus 在初始化时序列化上下文,这发生在第一次检索调用 GetHubContext 时,所以我在执行任何命令之前强制初始化它。
app.MapSignalR();
var context = new OwinContext(app.Properties);
var token = context.Get<CancellationToken>("host.OnAppDisposing");
if (token != CancellationToken.None)
{
token.Register(() =>
{
log.Info("host.OnAppDisposing");
// code to run when server shuts down
BackendMessageBusConfig.DisposeContainers();
});
}
// this code brings forward SignalR SqlMessageBus initialization
var forceInit = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
BackendMessageBusConfig.Register();