Questo post analizza un errore a runtime di .NET Remoting.

WCF sta rapidamente diventando l’infrastruttura di comunicazione preferita per le applicazioni .NET, ma .NET Remoting è ancora utile in alcuni casi. Specificamente, quando l’interoperabilità non è richiesta ed è necessario passare gli oggetti per riferimento invece che per valore.

Il problema

A runtime, un client .NET Remoting riceve una eccezione RemotingException durante la chiamata ad un oggetto remoto di tipo Server.Obj. L’oggetto remoto viene acceduto tramite un canale TCP.

L’eccezione completa ricevuta dal client è la seguente:

Unhandled Exception: System.Runtime.Remoting.RemotingException: This remoting proxy has no channel sink which means either the server has no registered server channels that are listening, or this application has no suitable client channel to talk to the server.
at System.Runtime.Remoting.Proxies.RemotingProxy.InternalInvoke(IMethodCallMessage reqMcmMsg, Boolean useDispatchMessage, Int32 callType)
at System.Runtime.Remoting.Proxies.RemotingProxy.Invoke(IMessage reqMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at Itfs.IObj.DoNothing()
at Client.Program.Main()

Nota: il problema riportato dal cliente si verificava in una applicazione piuttosto complessa. Nel seguito usiamo un esempio molto più semplice per poterci focalizzare solo sul problema in oggetto. Useremo tuttavia le stesse tecniche di troubleshooting che avremmo usato in applicazioni complesse, delle quali non abbiamo conoscenza del codice: invece di partire con un’analisi esaustiva del codice sorgente, che richiede troppo tempo, partiremo dall’eccezione e dall’ analisi delle strutture dati usate da .NET Remoting a runtime per trovare il problema. Sì, avete indovinato Smile: questo significa analizzare dump con il debugger WinDbg e la debugger extension sos.

Il messaggio dell’eccezione è piuttosto accurato: oltre a descrivere il problema (the proxy does not have a channel sink), riporta anche 2 possibili cause: il server non ha un canale server registrato, oppure il client non dispone di un canale client adatto. Per comprendere il significato di questi messaggi, abbiamo bisogno di alcune nozioni su come funzionano i canali in .NET Remoting. Più avanti useremo queste info per il nostro troubleshooting.

Funzionamento dei Canali in .NET Remoting

I canali di .NET Remoting sono classi che implementano l’interfaccia IChannel. Sono registrati per-AppDomain mediante ChannelServices.RegisterChannel(). Esistono in realtà 2 tipi di canali: i “receiver channel” implementano IChannelReceiver, i “sender channel” implementano IChannelSender. Possono anche essere chiamati “canali server” e  “canali client”, rispettivamente - è la terminologia che useremo nel seguito. TcpServerChannel e HttpServerChannel sono canali server, mentre TcpClientChannel e HttpClientChannel sono canali client.
.NET Remoting sceglie il canale di comunicazione in questo modo: quando un riferimento ad un oggetto MarshalByRefObject viene “trasferito” al di fuori del proprio AppDomain, l’ObjRef contiene informazioni sui canali server registrati in quell’AppDomain, in ordine di priorità (proprietà IChannel.ChannelPriority). 
Quando l’oggetto ObjRef viene deserializzato nel client, ogni canale client registrato nell’AppDomain corrente viene interrogato (in ordine di priorità) per verificare se può gestire l’URL contenuto nella sezione channelData dell’ObjRef. Viene usato il primo canale che risponde positivamente.

.NET Remoting registra implicitamente i canali client TCP e HTTP, se non sono registrati esplicitamente.

Il canale CrossAppDomainChannel, non documentato, è anch’esso automaticamente registrato, in modo da permettere l’utilizzo di .NET Remoting attraverso AppDomain diversi nello stesso processo.

Infine, le classi TcpChannel e HttpChannel, implementando sia IChannelReceiver che IChannelSender, svolgono il ruolo sia di canali client che di canali server.

Troubleshooting del client

Partiamo dal dump del client, il processo che riceve l’eccezione RemotingException. Per questo tipo di problema e di applicazione, l’analisi del dump è più veloce che un’analisi esaustiva del codice, come già riportato sopra.

Esiste, in realtà, un altro motivo per partire dall’analisi di un dump, vale a dire dall’analisi delle strutture dati del .NET Remoting a runtime: le configurazioni .NET possono essere fatte in 2 modi: imperativo nel codice, o dichiarativo nei file di configurazione. La configurazione di .NET Remoting segue anch’essa questa logica, pertanto i canali e le altre informazioni di configurazioni possono trovarsi SIA nel codice CHE nei config files. Diventa quindi complicato capire, dall’analisi dei file sorgente e di configurazione, quale sarà la reale configurazione a runtime.

Note: alcune delle strutture dati che analizzeremo nei dump sono interne e soggette a modifica nelle prossime versioni della .NET Framework. L’analisi che segue si applica alle versioni 2.0-3.5 della .NET Framework.

Iniziamo a verificare che il client ha solo il default AppDomain, oltre al System Domain e al Shared Domain, che sono AppDomain di sistema:

0:000> !dumpdomain
--------------------------------------
System Domain: 6e4ce1f8
LowFrequencyHeap: 6e4ce21c
HighFrequencyHeap: 6e4ce268
StubHeap: 6e4ce2b4
Stage: OPEN
Name: None
--------------------------------------
Shared Domain: 6e4cdb48
LowFrequencyHeap: 6e4cdb6c
HighFrequencyHeap: 6e4cdbb8
StubHeap: 6e4cdc04
Stage: OPEN
Name: None
Assembly: 002e72d0
--------------------------------------
Domain 1: 002a1df8
LowFrequencyHeap: 002a1e1c
HighFrequencyHeap: 002a1e68
StubHeap: 002a1eb4
Stage: OPEN
SecurityDescriptor: 002a3120
Name: Client.exe
Assembly: 002e72d0 [C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll]
ClassLoader: 002e7350
SecurityDescriptor: 002e3900
Module Name
6d481000 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll
00272358 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\sortkey.nlp
00272010 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\sorttbls.nlp

...
 
Possiamo trovare i canali registrati per il default AppDomain nelle istanze di RegisteredChannelList “rooted” nell’AppDomain 002a1df8:
 
0:000> !dumpheap -type RegisteredChannelList
Address MT Size
01f448f0 6d6cc1bc 12
01f449a4 6d6cc1bc 12
01f44ad0 6d6cc1bc 12
total 3 objects
Statistics:
MT Count TotalSize Class Name
6d6cc1bc 3 36 System.Runtime.Remoting.Channels.RegisteredChannelList
Total 3 objects
0:000> !gcroot 01f448f0
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 1164
Scan Thread 2 OSTHread 267c
Scan Thread 3 OSTHread 1f88
Scan Thread 7 OSTHread 235c
0:000> !gcroot 01f449a4
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 1164
Scan Thread 2 OSTHread 267c
Scan Thread 3 OSTHread 1f88
Scan Thread 7 OSTHread 235c
0:000> !gcroot 01f44ad0
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 1164
Scan Thread 2 OSTHread 267c
Scan Thread 3 OSTHread 1f88
Scan Thread 7 OSTHread 235c
DOMAIN(002A1DF8):HANDLE(Pinned):1313fc:Root:02eb1010(System.Object[])->
01f44ad0(System.Runtime.Remoting.Channels.RegisteredChannelList)

L’istanza RegisteredChannelList per il default AppDomain è quindi 01f44ad0; vediamo cosa contiene:
 
0:000> !do 01f44ad0
Name: System.Runtime.Remoting.Channels.RegisteredChannelList
MethodTable: 6d6cc1bc
EEClass: 6d4a2ee4
Size: 12(0xc) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
6d6c4eec 4001f1d 4 System.Object[] 0 instance 01f44aa8 _channels
0:000> !da 01f44aa8
Name: System.Runtime.Remoting.Channels.RegisteredChannel[]
MethodTable: 6d6c4eec
EEClass: 6d4aa8a0
Size: 24(0x18) bytes
Array: Rank 1, Number of elements 2, Type CLASS
Element Methodtable: 6d6cc204
[0] 01f44ac0
[1] 01f44994
0:000> !do 01f44ac0
Name: System.Runtime.Remoting.Channels.RegisteredChannel
MethodTable: 6d6cc204
EEClass: 6d507784
Size: 16(0x10) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
6d6cc248 4001f1b 4 ...Channels.IChannel 0 instance 01f44a4c channel
6d6eb3e0 4001f1c 8 System.Byte 1 instance 3 flags
0:000> !do 01f44a4c
Name: System.Runtime.Remoting.Channels.CrossAppDomainChannel
MethodTable: 6d6cc0e4
EEClass: 6d507720
Size: 12(0xc) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
6d6e84dc 4001f7e 664 System.Object 0 shared static staticSyncObject
>> Domain:Value 002a1df8:01f44a58 <<
6d6ea584 4001f7f 668 ...ity.PermissionSet 0 shared static s_fullTrust
>> Domain:Value 002a1df8:01f44a64 <<
0:000> !do 01f44994
Name: System.Runtime.Remoting.Channels.RegisteredChannel
MethodTable: 6d6cc204
EEClass: 6d507784
Size: 16(0x10) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
6d6cc248 4001f1b 4 ...Channels.IChannel 0 instance 01f4487c channel
6d6eb3e0 4001f1c 8 System.Byte 1 instance 3 flags
0:000> !do 01f4487c
Name: System.Runtime.Remoting.Channels.Tcp.TcpChannel
MethodTable: 61f476f4
EEClass: 61eb5bec
Size: 24(0x18) bytes
(C:\Windows\assembly\GAC_MSIL\System.Runtime.Remoting\2.0.0.0__b77a5c561934e089\System.Runtime.Remoting.dll)
Fields:
MT Field Offset Type VT Attr Value Name
61f47794 4000160 4 ....TcpClientChannel 0 instance 01f448ac _clientChannel
61f4781c 4000161 8 ....TcpServerChannel 0 instance 00000000 _serverChannel
6d6eab0c 4000162 10 System.Int32 1 instance 1 _channelPriority
6d6e88c0 4000163 c System.String 0 instance 01f44894 _channelName

Ci sono 2 canali registrati: il canale CrossAppDomainChannel e il canale TcpChannel; entrambi sono send E receive channels (RegisteredChannel.flags = 3). Inoltre, il canale TcpChannel ha un _clientChannel e non ha un _serverChannel.
Sappiamo a questo punto che questa è una configurazione valida per un client di .NET Remoting. In altre parole, questa analisi non ha evidenziato nulla di anomalo nella configurazione dei canali del processo client. Come avrete indovinato Smile, è venuto il momento di analizzare il server.

Troubleshooting del server

Sappiamo che l’oggetto di remoting nel server è di tipo Server.Obj. Vediamo a che AppDomain appartiente:

0:013> !dumpheap -type Server.Obj
Address MT Size
01d79960 03d035a8 12
total 1 objects
Statistics:
MT Count TotalSize Class Name
03d035a8 1 12 Server.Obj
Total 1 objects
0:013> !dumpmt 03d035a8
EEClass: 03d012f0
Module: 03d02d68
Name: Server.Obj
mdToken: 02000004 (d:\MSDNBlog\RemotingExceptionWithChannels\Repro\Server\bin\Debug\Server.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 1
Slots in VTable: 9
0:013> !dumpmodule 03d02d68
Name: d:\MSDNBlog\RemotingExceptionWithChannels\Repro\Server\bin\Debug\Server.exe
Attributes: PEFile
Assembly: 00490bb0
LoaderHeap: 00000000
TypeDefToMethodTableMap: 03d00038
TypeRefToMethodTableMap: 03d0004c
MethodDefToDescMap: 03d000d0
FieldDefToDescMap: 03d000ec
MemberRefToDescMap: 03d000f4
FileReferencesMap: 03d00178
AssemblyReferencesMap: 03d0017c
MetaData start address: 010f211c (2380 bytes)
0:013> !dumpassembly 00490bb0
Parent Domain: 004d4880
Name: d:\MSDNBlog\RemotingExceptionWithChannels\Repro\Server\bin\Debug\Server.exe
ClassLoader: 00490c20
SecurityDescriptor: 004f4600
Module Name
03d02d68 d:\MSDNBlog\RemotingExceptionWithChannels\Repro\Server\bin\Debug\Server.exe

Abbiamo trovato l’AppDomain partendo dall’oggetto e passando attraverso la sua MethodTable, il modulo e l’assembly. L’AppDomain ha indirizzo 0x004d4880.

In modo del tutto analogo a quanto fatto in precedenza per il client, analizziamo il contenuto degli oggetti RegisteredChannelList per ogni AppDomain:

0:013> !dumpheap -type RegisteredChannelList
Address MT Size
01d30ffc 6d6cc1bc 12
01d3422c 6d6cc1bc 12
01d4727c 6d6cc1bc 12
01d4b554 6d6cc1bc 12
01d71758 6d6cc1bc 12
01d71798 6d6cc1bc 12
total 6 objects
Statistics:
MT Count TotalSize Class Name
6d6cc1bc 6 72 System.Runtime.Remoting.Channels.RegisteredChannelList
Total 6 objects
0:013> !gcroot 01d30ffc
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 1254
Scan Thread 2 OSTHread 11f4
Scan Thread 6 OSTHread 2380
Scan Thread 7 OSTHread 1904
Scan Thread 9 OSTHread 1130
Scan Thread 10 OSTHread 221c
Scan Thread 12 OSTHread 2424
Scan Thread 11 OSTHread 1594
DOMAIN(0044A330):HANDLE(Pinned):1713fc:Root:02d01010(System.Object[])->
01d30ffc(System.Runtime.Remoting.Channels.RegisteredChannelList)
0:013> !gcroot 01d3422c
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 1254
Scan Thread 2 OSTHread 11f4
Scan Thread 6 OSTHread 2380
Scan Thread 7 OSTHread 1904
Scan Thread 9 OSTHread 1130
Scan Thread 10 OSTHread 221c
Scan Thread 12 OSTHread 2424
Scan Thread 11 OSTHread 1594
DOMAIN(004A4908):HANDLE(Pinned):8112fc:Root:02d06280(System.Object[])->
01d3422c(System.Runtime.Remoting.Channels.RegisteredChannelList)
0:013> !gcroot 01d4727c
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 1254
Scan Thread 2 OSTHread 11f4
Scan Thread 6 OSTHread 2380
Scan Thread 7 OSTHread 1904
Scan Thread 9 OSTHread 1130
Scan Thread 10 OSTHread 221c
Scan Thread 12 OSTHread 2424
Scan Thread 11 OSTHread 1594
DOMAIN(004BB1D0):HANDLE(Pinned):9412fc:Root:02d086a0(System.Object[])->
01d4727c(System.Runtime.Remoting.Channels.RegisteredChannelList)
0:013> !gcroot 01d4b554
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 1254
Scan Thread 2 OSTHread 11f4
Scan Thread 6 OSTHread 2380
Scan Thread 7 OSTHread 1904
Scan Thread 9 OSTHread 1130
Scan Thread 10 OSTHread 221c
Scan Thread 12 OSTHread 2424
Scan Thread 11 OSTHread 1594
DOMAIN(004C3D18):HANDLE(Pinned):db12fc:Root:02d096b0(System.Object[])->
01d4b554(System.Runtime.Remoting.Channels.RegisteredChannelList)
0:013> !gcroot 01d71758
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 1254
Scan Thread 2 OSTHread 11f4
Scan Thread 6 OSTHread 2380
Scan Thread 7 OSTHread 1904
Scan Thread 9 OSTHread 1130
Scan Thread 10 OSTHread 221c
Scan Thread 12 OSTHread 2424
Scan Thread 11 OSTHread 1594
0:013> !gcroot 01d71798
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 1254
Scan Thread 2 OSTHread 11f4
Scan Thread 6 OSTHread 2380
Scan Thread 7 OSTHread 1904
Scan Thread 9 OSTHread 1130
Scan Thread 10 OSTHread 221c
Scan Thread 12 OSTHread 2424
Scan Thread 11 OSTHread 1594
DOMAIN(004D4880):HANDLE(Pinned):3d112fc:Root:02d0a6c0(System.Object[])->
01d71798(System.Runtime.Remoting.Channels.RegisteredChannelList)
 
5 oggetti RegisteredChannelList sono “rooted” in qualche AppDomain, ma nessuno di essi è rooted nell’AppDomain 004d4880, quello che contiene il nostro oggetto di remoting Server.Obj. Questo significa che la configurazione dei canali sul server non è corretta: nessun canale è registrato nell’AppDomain che ospita l’oggetto Server.Obj, l’oggetto è inaccessibile da un client remoto. Questo spiega il perchè della RemotingException ricevuta dal client.
Questi sono gli AppDomain del server:
 
0:013> !dumpdomain
--------------------------------------
System Domain: 6e4ce1f8
LowFrequencyHeap: 6e4ce21c
HighFrequencyHeap: 6e4ce268
StubHeap: 6e4ce2b4
Stage: OPEN
Name: None
--------------------------------------
Shared Domain: 6e4cdb48
LowFrequencyHeap: 6e4cdb6c
HighFrequencyHeap: 6e4cdbb8
StubHeap: 6e4cdc04
Stage: OPEN
Name: None
Assembly: 0045f0a8
--------------------------------------
Domain 1: 0044a330
LowFrequencyHeap: 0044a354
HighFrequencyHeap: 0044a3a0
StubHeap: 0044a3ec
Stage: OPEN
SecurityDescriptor: 0044b018
Name: Server.exe
Assembly: 0045f0a8 [C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll]
ClassLoader: 0045f118
SecurityDescriptor: 0045c3c0
Module Name
6d481000 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll
001b2358 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\sortkey.nlp
001b2010 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\sorttbls.nlp
...

--------------------------------------
Domain 2: 004a4908
LowFrequencyHeap: 004a492c
HighFrequencyHeap: 004a4978
StubHeap: 004a49c4
Stage: OPEN
SecurityDescriptor: 00461e70
Name: a52aab56-8c58-4b32-b298-d22914734328
Assembly: 0045f0a8 [C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll]
ClassLoader: 0045f118
SecurityDescriptor: 004944b0
Module Name
6d481000 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll
001b2358 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\sortkey.nlp
001b2010 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\sorttbls.nlp
...

--------------------------------------
Domain 3: 004bb1d0
LowFrequencyHeap: 004bb1f4
HighFrequencyHeap: 004bb240
StubHeap: 004bb28c
Stage: OPEN
SecurityDescriptor: 00462130
Name: 471b56c5-5b8c-44aa-a3e6-5d14fe3c4d91
Assembly: 0045f0a8 [C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll]
ClassLoader: 0045f118
SecurityDescriptor: 004948f0
Module Name
6d481000 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll
001b2358 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\sortkey.nlp
001b2010 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\sorttbls.nlp
...

--------------------------------------
Domain 4: 004c3d18
LowFrequencyHeap: 004c3d3c
HighFrequencyHeap: 004c3d88
StubHeap: 004c3dd4
Stage: OPEN
SecurityDescriptor: 004b70d0
Name: 970ee547-afb3-491b-b159-359af030978a
Assembly: 0045f0a8 [C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll]
ClassLoader: 0045f118
SecurityDescriptor: 00494b98
Module Name
6d481000 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll
001b2358 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\sortkey.nlp
001b2010 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\sorttbls.nlp
...

--------------------------------------
Domain 5: 004d4880
LowFrequencyHeap: 004d48a4
HighFrequencyHeap: 004d48f0
StubHeap: 004d493c
Stage: OPEN
SecurityDescriptor: 00461ec8
Name: b343ebfd-7820-45c5-8a25-1fa0738d59bb
Assembly: 0045f0a8 [C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll]
ClassLoader: 0045f118
SecurityDescriptor: 00494db8
Module Name
6d481000 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll
001b2358 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\sortkey.nlp
001b2010 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\sorttbls.nlp
...
 
Il default AppDomain è 0x0044a330 e la lista dei suoi canali, come mostrato dai precedenti comandi !gcroot, è 01d30ffc. Vediamone il contenuto:
 
0:013> !do 01d30ffc
Name: System.Runtime.Remoting.Channels.RegisteredChannelList
MethodTable: 6d6cc1bc
EEClass: 6d4a2ee4
Size: 12(0xc) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
6d6c4eec 4001f1d 4 System.Object[] 0 instance 01d30fd4 _channels
0:013> !da 01d30fd4
Name: System.Runtime.Remoting.Channels.RegisteredChannel[]
MethodTable: 6d6c4eec
EEClass: 6d4aa8a0
Size: 24(0x18) bytes
Array: Rank 1, Number of elements 2, Type CLASS
Element Methodtable: 6d6cc204
[0] 01d30fec
[1] 01d30ecc
0:013> !do 01d30fec
Name: System.Runtime.Remoting.Channels.RegisteredChannel
MethodTable: 6d6cc204
EEClass: 6d507784
Size: 16(0x10) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
6d6cc248 4001f1b 4 ...Channels.IChannel 0 instance 01d30f78 channel
6d6eb3e0 4001f1c 8 System.Byte 1 instance 3 flags
0:013> !do 01d30f78
Name: System.Runtime.Remoting.Channels.CrossAppDomainChannel
MethodTable: 6d6cc0e4
EEClass: 6d507720
Size: 12(0xc) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
6d6e84dc 4001f7e 664 System.Object 0 shared static staticSyncObject
>> Domain:Value 0044a330:01d30f84 004a4908:01d341cc 004bb1d0:01d4721c 004c3d18:01d4b4f4 004d4880:01d7171c <<
6d6ea584 4001f7f 668 ...ity.PermissionSet 0 shared static s_fullTrust
>> Domain:Value 0044a330:01d30f90 004a4908:01d341d8 004bb1d0:01d47228 004c3d18:01d4b500 004d4880:01d71728 <<
0:013> !do 01d30ecc
Name: System.Runtime.Remoting.Channels.RegisteredChannel
MethodTable: 6d6cc204
EEClass: 6d507784
Size: 16(0x10) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
6d6cc248 4001f1b 4 ...Channels.IChannel 0 instance 01d19c9c channel
6d6eb3e0 4001f1c 8 System.Byte 1 instance 3 flags
0:013> !do 01d19c9c
Name: System.Runtime.Remoting.Channels.Tcp.TcpChannel
MethodTable: 61f476f4
EEClass: 61eb5bec
Size: 24(0x18) bytes
(C:\Windows\assembly\GAC_MSIL\System.Runtime.Remoting\2.0.0.0__b77a5c561934e089\System.Runtime.Remoting.dll)
Fields:
MT Field Offset Type VT Attr Value Name
61f47794 4000160 4 ....TcpClientChannel 0 instance 01d19ccc _clientChannel
61f4781c 4000161 8 ....TcpServerChannel 0 instance 01d19d04 _serverChannel
6d6eab0c 4000162 10 System.Int32 1 instance 1 _channelPriority
6d6e88c0 4000163 c System.String 0 instance 01d19cb4 _channelName
0:013> !do 01d19d04
Name: System.Runtime.Remoting.Channels.Tcp.TcpServerChannel
MethodTable: 61f4781c
EEClass: 61eb5cb4
Size: 68(0x44) bytes
(C:\Windows\assembly\GAC_MSIL\System.Runtime.Remoting\2.0.0.0__b77a5c561934e089\System.Runtime.Remoting.dll)
Fields:
MT Field Offset Type VT Attr Value Name
6d6eab0c 4000192 2c System.Int32 1 instance 1 _channelPriority
6d6e88c0 4000193 4 System.String 0 instance 01d19cb4 _channelName
6d6e88c0 4000194 8 System.String 0 instance 01d30900 _machineName
6d6eab0c 4000195 30 System.Int32 1 instance 5555 _port
6d6e73e4 4000196 c ....ChannelDataStore 0 instance 01d3092c _channelData
6d6e88c0 4000197 10 System.String 0 instance 00000000 _forcedMachineName
6d6eeadc 4000198 38 System.Boolean 1 instance 1 _bUseIpAddress
6ce5e13c 4000199 14 System.Net.IPAddress 0 instance 01d2d9e4 _bindToAddr
6d6eeadc 400019a 39 System.Boolean 1 instance 0 _bSuppressChannelData
6d6eeadc 400019b 3a System.Boolean 1 instance 0 _impersonate
6ce622b8 400019c 34 System.Int32 1 instance 2 _protectionLevel
6d6eeadc 400019d 3b System.Boolean 1 instance 0 _secure
6d6cfd5c 400019e 18 System.AsyncCallback 0 instance 01d30a78 _acceptSocketCallback
61f45ac8 400019f 1c ...emotingConnection 0 instance 00000000 _authorizeRemotingConnection
6d6eeadc 40001a0 3c System.Boolean 1 instance 0 authSet
6d6e7434 40001a1 20 ...annelSinkProvider 0 instance 01d309c8 _sinkProvider
61f478e0 40001a2 24 ...rverTransportSink 0 instance 01d30a68 _transportSink
61f46478 40001a3 28 ...lusiveTcpListener 0 instance 01d30a98 _tcpListener
6d6eeadc 40001a4 3d System.Boolean 1 instance 1 _bExclusiveAddressUse
6d6eeadc 40001a5 3e System.Boolean 1 instance 1 _bListening

La differenza con la configurazione del client consiste nel fatto che in questa configurazione c’é un canale server tcp, in ascolto sulla porta 5555. Questo è normale, visto che questo processo svolge il ruolo di un server di .NET Remoting.
In definitiva, la configurazion dei canali per il default AppDomain è corretta, ma la configurazione dei canali per l”AppDomain che contiene l’oggetto di remoting Server.Obj non lo è.
Con queste informazioni, andiamo a vedere il codice dell’applicazione.

Alla ricerca del problema nel codice

Questo è il frammento di codice che crea l’oggetto di remoting:
 
public IObj GetObj(string sName)
{
return m_ObjsAppDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(Obj).ToString()) as IObj;
}

Questo conferma quanto abbiamo visto nell’analisi precedente: l’oggetto viene creato in un nuovo AppDomain. GetObj() è un metodo di un’altro oggetto di remoting, ospitato nel default AppDomain.
Ecco come il server registra i canali all’avvio:
 

static void Main()
{
TcpChannel tcpCh = new TcpChannel(Config.TcpPort);
ChannelServices.RegisterChannel(tcpCh, false);
...

}


Questo codice registra il canale tcp nell’AppDomain corrente, che all’avvio è il default AppDomain. Quando l’AppDomain del nuovo oggetto di remoting viene creato, nessun canale di remoting viene registrato per quell’AppDomain.
Potreste chiedervi perchè il problema si verifica al momento della chiamata del metodo e non al momento del marshaling dell’oggetto. Il motivo è che il canale viene creato solo al momento della prima chiamata.
La risoluzione del problema diventa, a questo punto, piuttosto semplice:
  • Creare gli oggetti di remoting nel default AppDomain, in modo da usare la configurazione dei canali del default AppDomain.
  • Configurare esplicitamente nel codice la configurazione dei canali per ogni nuovo AppDomain che ospita oggetti di remoting.

Nota: la configurazione dichiarativa nei file di configurazione si applica solo al default AppDomain.

Riassunto

Nel seguito alcuni dei punti principali di questo post:

  • Il troubleshooting di eccezioni di .NET Remoting come per esempio System.Runtime.Remoting.RemotingException non richiedono la conoscenza del codice dell’applicazione. L’analisi dei dump e una buona conoscenza delle strutture di runtime di .NET Remoting può essere sufficiente
  • La registrazione dei canali di .NET Remoting è per-AppDomain. Se un’applicazione server di .NET Remoting crea AppDomain esplicitamente, allora è responsabile della registrazione dei canali per questi AppDomain.
  • La configurazione imperativa nel codice si applica all’AppDomain corrente. La configurazione dichiarativa nei file di configurazione si applica solo al default AppDomain.
  • Partite da System.Runtime.Remoting.Channels.RegisteredChannelList per fare il dump dei canali di .NET Remoting registrati negli AppDomain.

(tradotto e adattato da http://blogs.msdn.com/carlos/archive/2010/01/11/troubleshooting-a-remotingexception-by-dumping-out-remoting-channel-configuration.aspx)