The Labs \ Source Viewer \ SSCLI \ System.Runtime.Remoting.Proxies \ AgileAsyncWorkerItem

  1. // ==++==
  2. //
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. //
  14. // ==--==
  15. /*============================================================
  16. **
  17. ** File:    RemotingProxy.cs
  18. **
  19. **
  20. ** Purpose: Defines the general purpose remoting proxy
  21. **
  22. **
  23. ===========================================================*/
  24. namespace System.Runtime.Remoting.Proxies
  25. {
  26.     using System.Threading;
  27.     using System.Runtime.Remoting.Activation;
  28.     using System.Runtime.Remoting.Messaging;
  29.     using System.Runtime.Remoting.Contexts;
  30.     using System.Runtime.Remoting.Channels;
  31.     using System;
  32.     using MethodInfo = System.Reflection.MethodInfo;
  33.     using MethodBase = System.Reflection.MethodBase;
  34.     using System.Globalization;
  35.     // Remoting proxy
  36.     internal class RemotingProxy : RealProxy, IRemotingTypeInfo
  37.     {
  38.         // Static Fields
  39.         private static MethodInfo _getTypeMethod = typeof(object).GetMethod("GetType");
  40.         private static MethodInfo _getHashCodeMethod = typeof(object).GetMethod("GetHashCode");
  41.        
  42.         private static Type s_typeofObject = typeof(object);
  43.         private static Type s_typeofMarshalByRefObject = typeof(System.MarshalByRefObject);
  44.        
  45.         //*******************WARNING******************************************
  46.         // If you change the names of these fields then change the corresponding
  47.         // names in remoting.cpp
  48.         //********************************************************************
  49.         private ConstructorCallMessage _ccm;
  50.         private int _ctorThread;
  51.        
  52.        
  53.         // Constructor
  54.         public RemotingProxy(Type serverType) : base(serverType)
  55.         {
  56.         }
  57.        
  58.         private RemotingProxy()
  59.         {
  60.             // Prevent anyone from creating a blank instance of a proxy
  61.             // without the underlying server type
  62.         }
  63.        
  64.         internal int CtorThread {
  65.             get { return _ctorThread; }
  66. //NOTE : the assert below is correct for activated objects.
  67. //But for a connected object (where new XXX() does a Connect()
  68. //the InternalActivate codepath may execute twice .. since
  69. //we would be returning the same proxy for multiple calls to
  70. //new XXX() & JIT would try to execute the default .ctor on
  71. //the returned proxy.
  72. //BCLDebug.Assert(_ctorThread == 0, "ctorThread already set??");
  73.             set { _ctorThread = value; }
  74.         }
  75.        
  76.         // This is used when a TP is called with SyncProcessMessage
  77.         static internal IMessage CallProcessMessage(IMessageSink ms, IMessage reqMsg, ArrayWithSize proxySinks, Thread currentThread, Context currentContext, bool bSkippingContextChain)
  78.         {
  79.             // Notify Dynamic Sinks: CALL starting
  80.             if (proxySinks != null) {
  81.                     // bCliSide
  82.                     // bStart
  83.                 DynamicPropertyHolder.NotifyDynamicSinks(reqMsg, proxySinks, true, true, false);
  84.                 // bAsync
  85.             }
  86.            
  87.             bool bHasDynamicSinks = false;
  88.             if (bSkippingContextChain) {
  89.                 // this would have been done in the client context terminator sink
  90.                     // bCliSide
  91.                     // bStart
  92.                     // bAsync
  93.                 bHasDynamicSinks = currentContext.NotifyDynamicSinks(reqMsg, true, true, false, true);
  94.                 // bNotifyGlobals
  95.                 ChannelServices.NotifyProfiler(reqMsg, RemotingProfilerEvent.ClientSend);
  96.             }
  97.            
  98.             if (ms == null) {
  99.                 throw new RemotingException(Environment.GetResourceString("Remoting_Proxy_NoChannelSink"));
  100.             }
  101.            
  102.             IMessage retMsg = ms.SyncProcessMessage(reqMsg);
  103.            
  104.             if (bSkippingContextChain) {
  105.                 // this would have been done in the client context terminator sink
  106.                 ChannelServices.NotifyProfiler(retMsg, RemotingProfilerEvent.ClientReceive);
  107.                
  108.                 if (bHasDynamicSinks) {
  109.                         // bCliSide
  110.                         // bStart
  111.                         // bAsync
  112.                     currentContext.NotifyDynamicSinks(retMsg, true, false, false, true);
  113.                     // bNotifyGlobals
  114.                 }
  115.             }
  116.            
  117.             IMethodReturnMessage mrm = retMsg as IMethodReturnMessage;
  118.             if (retMsg == null || mrm == null) {
  119.                 throw new RemotingException(Environment.GetResourceString("Remoting_Message_BadType"));
  120.             }
  121.            
  122.             // notify DynamicSinks: CALL returned
  123.             if (proxySinks != null) {
  124.                     // bCliSide
  125.                     // bStart
  126.                 DynamicPropertyHolder.NotifyDynamicSinks(retMsg, proxySinks, true, false, false);
  127.                 // bAsync
  128.             }
  129.            
  130.            
  131.             return retMsg;
  132.         }
  133.        
  134.         // Implement Invoke
  135.         public override IMessage Invoke(IMessage reqMsg)
  136.         {
  137.             // Dispatch based on whether its a constructor call
  138.             // or a method call
  139.            
  140.             IConstructionCallMessage ccm = reqMsg as IConstructionCallMessage;
  141.            
  142.             if (ccm != null) {
  143.                 // Activate
  144.                 return InternalActivate(ccm);
  145.             }
  146.             else {
  147.                 // Handle regular method calls
  148.                
  149.                 // Check that the initialization has completed
  150.                 if (!Initialized) {
  151.                     // This covers the case where an object may call out
  152.                     // on another object passing its "this" pointer during its
  153.                     // .ctor.
  154.                     // The other object attempting to call on the this pointer
  155.                     // (in x-context case) would be calling on a proxy not
  156.                     // marked fully initialized.
  157.                    
  158.                     // Let the original constructor thread go through but
  159.                     // throw for other threads.
  160.                     if (CtorThread == Thread.CurrentThread.GetHashCode()) {
  161.                         ServerIdentity srvId = IdentityObject as ServerIdentity;
  162.                         BCLDebug.Assert(srvId != null && ((ServerIdentity)IdentityObject).ServerContext != null, "Wrap may associate with wrong context!");
  163.                        
  164.                         // If we are here, the server object passed itself
  165.                         // out to another x-context object during the .ctor
  166.                         // That guy is calling us back. Let us call Wrap()
  167.                         // earlier than usual so that envoy & channel sinks
  168.                         // get set up!
  169.                         RemotingServices.Wrap((ContextBoundObject)this.UnwrappedServerObject);
  170.                        
  171.                     }
  172.                     else {
  173.                         // Throw an exception to indicate that we are
  174.                         // calling on a proxy while the constructor call
  175.                         // is still running.
  176.                         throw new RemotingException(Environment.GetResourceString("Remoting_Proxy_InvalidCall"));
  177.                     }
  178.                    
  179.                 }
  180.                
  181.                 // Dispatch
  182.                 int callType = Message.Sync;
  183.                 Message msg = reqMsg as Message;
  184.                 if (msg != null) {
  185.                     callType = msg.GetCallType();
  186.                 }
  187.                
  188.                 return InternalInvoke((IMethodCallMessage)reqMsg, false, callType);
  189.             }
  190.            
  191.         }
  192.         // Invoke
  193.        
  194.         // This is called for all remoted calls on a TP except Ctors
  195.         // The method called may be Sync, Async or OneWay(special case of Async)
  196.         // In the Async case we come here for both BeginInvoke & EndInvoke
  197.         internal virtual IMessage InternalInvoke(IMethodCallMessage reqMcmMsg, bool useDispatchMessage, int callType)
  198.         {
  199.             Message reqMsg = reqMcmMsg as Message;
  200.             if ((reqMsg == null) && (callType != Message.Sync)) {
  201.                 // Only the synchronous call type is supported for messages that
  202.                 // aren't of type Message.
  203.                 throw new RemotingException(Environment.GetResourceString("Remoting_Proxy_InvalidCallType"));
  204.             }
  205.            
  206.             IMessage retMsg = null;
  207.             Thread currentThread = Thread.CurrentThread;
  208.            
  209.             // pick up call context from the thread
  210.             LogicalCallContext cctx = currentThread.GetLogicalCallContext();
  211.            
  212.             Identity idObj = IdentityObject;
  213.             ServerIdentity serverID = idObj as ServerIdentity;
  214.             if ((null != serverID) && idObj.IsFullyDisconnected()) {
  215.                 throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("Remoting_ServerObjectNotFound"), reqMcmMsg.Uri));
  216.             }
  217.            
  218.             // Short-circuit calls to Object::GetType and Object::GetHashCode
  219.             MethodBase mb = reqMcmMsg.MethodBase;
  220.             if (_getTypeMethod == mb) {
  221.                 // Time to load the true type of the remote object....
  222.                 Type t = GetProxiedType();
  223.                 return new ReturnMessage(t, null, 0, cctx, reqMcmMsg);
  224.             }
  225.            
  226.             if (_getHashCodeMethod == mb) {
  227.                 int hashCode = idObj.GetHashCode();
  228.                 return new ReturnMessage(hashCode, null, 0, cctx, reqMcmMsg);
  229.             }
  230.            
  231.             // check for channel sink
  232.             if (idObj.ChannelSink == null) {
  233.                 IMessageSink chnlSink = null;
  234.                 IMessageSink envoySink = null;
  235.                 // If channelSink is null try to Create them again
  236.                 // the objref should be correctly fixed up at this point
  237.                 if (!idObj.ObjectRef.IsObjRefLite()) {
  238.                     RemotingServices.CreateEnvoyAndChannelSinks(null, idObj.ObjectRef, out chnlSink, out envoySink);
  239.                 }
  240.                 else {
  241.                     RemotingServices.CreateEnvoyAndChannelSinks(idObj.ObjURI, null, out chnlSink, out envoySink);
  242.                 }
  243.                 // Set the envoy and channel sinks in a thread safe manner
  244.                 RemotingServices.SetEnvoyAndChannelSinks(idObj, chnlSink, envoySink);
  245.                
  246.                 // If the channel sink is still null then throw
  247.                 if (idObj.ChannelSink == null) {
  248.                     throw new RemotingException(Environment.GetResourceString("Remoting_Proxy_NoChannelSink"));
  249.                 }
  250.             }
  251.            
  252.             // Set the identity in the message object
  253.             IInternalMessage iim = (IInternalMessage)reqMcmMsg;
  254.             iim.IdentityObject = idObj;
  255.            
  256.             if (null != serverID) {
  257.                 Message.DebugOut("Setting serveridentity on message \n");
  258.                 iim.ServerIdentityObject = serverID;
  259.                
  260.             }
  261.             else {
  262.                 // We need to set the URI only for identities that
  263.                 // are not the server identities. The uri is used to
  264.                 // dispatch methods for objects outside the appdomain.
  265.                 // Inside the appdomain (xcontext case) we dispatch
  266.                 // by getting the server object from the server identity.
  267.                 iim.SetURI(idObj.URI);
  268.             }
  269.            
  270.             Message.DebugOut("InternalInvoke. Dispatching based on class type\n");
  271.             AsyncResult ar = null;
  272.             switch (callType) {
  273.                 case Message.Sync:
  274.                     Message.DebugOut("RemotingProxy.Invoke Call: SyncProcessMsg\n");
  275.                     BCLDebug.Assert(!useDispatchMessage, "!useDispatchMessage");
  276.                     bool bSkipContextChain = false;
  277.                     Context currentContext = currentThread.GetCurrentContextInternal();
  278.                     IMessageSink nextSink = idObj.EnvoyChain;
  279.                    
  280.                     // if we are in the default context, there can be no
  281.                     // client context chain, so we can skip the intermediate
  282.                     // calls if there are no envoy sinks
  283.                    
  284.                     if (currentContext.IsDefaultContext) {
  285.                         if (nextSink is EnvoyTerminatorSink) {
  286.                             bSkipContextChain = true;
  287.                            
  288.                             // jump directly to the channel sink
  289.                             nextSink = idObj.ChannelSink;
  290.                         }
  291.                     }
  292.                    
  293.                     retMsg = CallProcessMessage(nextSink, reqMcmMsg, idObj.ProxySideDynamicSinks, currentThread, currentContext, bSkipContextChain);
  294.                    
  295.                     break;
  296.                 case Message.BeginAsync:
  297.                 case Message.BeginAsync | Message.OneWay:
  298.                    
  299.                     // For async calls we clone the call context from the thread
  300.                     // This is a limited clone (we dont deep copy the user data)
  301.                     cctx = (LogicalCallContext)cctx.Clone();
  302.                     iim.SetCallContext(cctx);
  303.                    
  304.                     ar = new AsyncResult(reqMsg);
  305.                    
  306.                     InternalInvokeAsync(ar, reqMsg, useDispatchMessage, callType);
  307.                    
  308.                     Message.DebugOut("Propagate out params for BeginAsync\n");
  309.                         /*cctx*/                    retMsg = new ReturnMessage(ar, null, 0, null, reqMsg);
  310.                     break;
  311.                 case Message.OneWay:
  312.                    
  313.                     // For async calls we clone the call context from the thread
  314.                     // This is a limited clone (we dont deep copy the user data)
  315.                     cctx = (LogicalCallContext)cctx.Clone();
  316.                     iim.SetCallContext(cctx);
  317.                     InternalInvokeAsync(null, reqMsg, useDispatchMessage, callType);
  318.                         /*cctx*/                    retMsg = new ReturnMessage(null, null, 0, null, reqMcmMsg);
  319.                     break;
  320.                 case (Message.EndAsync | Message.OneWay):
  321.                    
  322.                         /*cctx*/                    retMsg = new ReturnMessage(null, null, 0, null, reqMcmMsg);
  323.                     break;
  324.                 case Message.EndAsync:
  325.                    
  326.                     // For endAsync, we merge back the returned callContext
  327.                     // into the thread's callContext
  328.                     retMsg = RealProxy.EndInvokeHelper(reqMsg, true);
  329.                     break;
  330.             }
  331.            
  332.             return retMsg;
  333.         }
  334.        
  335.         // This is called from InternalInvoke above when someone makes an
  336.         // Async (or a one way) call on a TP
  337.         internal void InternalInvokeAsync(IMessageSink ar, Message reqMsg, bool useDispatchMessage, int callType)
  338.         {
  339.             IMessageCtrl cc = null;
  340.             Identity idObj = IdentityObject;
  341.             ServerIdentity serverID = idObj as ServerIdentity;
  342.             MethodCall cpyMsg = new MethodCall(reqMsg);
  343.             IInternalMessage iim = ((IInternalMessage)cpyMsg);
  344.            
  345.             // Set the identity in the message object
  346.             iim.IdentityObject = idObj;
  347.             if (null != serverID) {
  348.                 Message.DebugOut("Setting SrvID on deser msg\n");
  349.                 iim.ServerIdentityObject = serverID;
  350.             }
  351.            
  352.             if (useDispatchMessage) {
  353.                 Message.DebugOut("RemotingProxy.Invoke: Calling AsyncDispatchMessage\n");
  354.                
  355.                 BCLDebug.Assert(ar != null, "ar != null");
  356.                 BCLDebug.Assert((callType & Message.BeginAsync) != 0, "BeginAsync flag not set!");
  357.                
  358.                 Message.DebugOut("Calling AsynDispatchMessage \n");
  359.                 cc = ChannelServices.AsyncDispatchMessage(cpyMsg, ((callType & Message.OneWay) != 0) ? null : ar);
  360.             }
  361.             else if (null != idObj.EnvoyChain) {
  362.                 Message.DebugOut("RemotingProxy.Invoke: Calling AsyncProcessMsg on the envoy chain\n");
  363.                
  364.                 cc = idObj.EnvoyChain.AsyncProcessMessage(cpyMsg, ((callType & Message.OneWay) != 0) ? null : ar);
  365.             }
  366.             else {
  367.                 // Channel sink cannot be null since it is the last sink in
  368.                 // the client context
  369.                 // Assert if Invoke is called without a channel sink
  370.                 BCLDebug.Assert(false, "How did we get here?");
  371.                 throw new ExecutionEngineException(Environment.GetResourceString("Remoting_Proxy_InvalidState"));
  372.             }
  373.            
  374.             if ((callType & Message.BeginAsync) != 0) {
  375.                
  376.                 if ((callType & Message.OneWay) != 0) {
  377.                     ar.SyncProcessMessage(null);
  378.                 }
  379.             }
  380.         }
  381.        
  382.         // New method for activators.
  383.        
  384.         // This gets called during remoting intercepted activation when
  385.         // JIT tries to run a constructor on a TP (which remoting gave it
  386.         // in place of an actual uninitialized instance of the expected type)
  387.         private IConstructionReturnMessage InternalActivate(IConstructionCallMessage ctorMsg)
  388.         {
  389.             // Remember the hashcode of the constructor thread.
  390.             CtorThread = Thread.CurrentThread.GetHashCode();
  391.            
  392.             IConstructionReturnMessage ctorRetMsg = ActivationServices.Activate(this, ctorMsg);
  393.            
  394.             // Set the flag to indicate that the object is initialized
  395.             // Note: this assert is not valid for WKOs
  396.             //BCLDebug.Assert(!Initialized, "Proxy marked as initialized before activation call completed");
  397.             Initialized = true;
  398.            
  399.             return ctorRetMsg;
  400.         }
  401.        
  402.         // Invoke for case where call is in the same context as the server object
  403.         // (This special static method is used for AsyncDelegate-s ... this is called
  404.         // directly from the EE)
  405.         private static void Invoke(object NotUsed, ref MessageData msgData)
  406.         {
  407.             Message m = new Message();
  408.             m.InitFields(msgData);
  409.            
  410.             object thisPtr = m.GetThisPtr();
  411.             Delegate d;
  412.             if ((d = thisPtr as Delegate) != null) {
  413.                 RemotingProxy rp = (RemotingProxy)RemotingServices.GetRealProxy(d.Target);
  414.                
  415.                 if (rp != null) {
  416.                     rp.InternalInvoke(m, true, m.GetCallType());
  417.                 }
  418.                 else {
  419.                     int callType = m.GetCallType();
  420.                     AsyncResult ar;
  421.                     switch (callType) {
  422.                         case Message.BeginAsync:
  423.                         case Message.BeginAsync | Message.OneWay:
  424.                             // pick up call context from the thread
  425.                             m.Properties[Message.CallContextKey] = CallContext.GetLogicalCallContext().Clone();
  426.                             ar = new AsyncResult(m);
  427.                             AgileAsyncWorkerItem workItem = new AgileAsyncWorkerItem(m, ((callType & Message.OneWay) != 0) ? null : ar, d.Target);
  428.                            
  429.                             ThreadPool.QueueUserWorkItem(new WaitCallback(AgileAsyncWorkerItem.ThreadPoolCallBack), workItem);
  430.                            
  431.                             if ((callType & Message.OneWay) != 0) {
  432.                                 ar.SyncProcessMessage(null);
  433.                             }
  434.                             m.PropagateOutParameters(null, ar);
  435.                             break;
  436.                         case (Message.EndAsync | Message.OneWay):
  437.                             return;
  438.                         case Message.EndAsync:
  439.                            
  440.                             // This will also merge back the call context
  441.                             // onto the thread that called EndAsync
  442.                             RealProxy.EndInvokeHelper(m, false);
  443.                             break;
  444.                         default:
  445.                             BCLDebug.Assert(false, "Should never be here. Sync delegate code for agile object ended up in remoting");
  446.                             break;
  447.                     }
  448.                 }
  449.             }
  450.             else {
  451.                 // Static invoke called with incorrect this pointer ...
  452.                 throw new RemotingException(Environment.GetResourceString("Remoting_Default"));
  453.             }
  454.         }
  455.        
  456.         internal ConstructorCallMessage ConstructorMessage {
  457.             get { return _ccm; }
  458.            
  459.             set { _ccm = value; }
  460.         }
  461.        
  462.         //
  463.         // IRemotingTypeInfo interface
  464.         //
  465.        
  466.         // Obtain the fully qualified name of the type that the proxy represents
  467.         public string TypeName {
  468.             get { return GetProxiedType().FullName; }
  469.            
  470.             set {
  471.                 throw new NotSupportedException();
  472.             }
  473.         }
  474.        
  475.        
  476.         // Check whether we can cast the transparent proxy to the given type
  477.         public bool CanCastTo(Type castType, object o)
  478.         {
  479.             bool fCastOK = false;
  480.            
  481.             // The identity should be non-null
  482.             BCLDebug.Assert(null != IdentityObject, "null != IdentityObject");
  483.            
  484.             Message.DebugOut("CheckCast for identity " + IdentityObject.GetType());
  485.            
  486.             if ((castType == s_typeofObject) || (castType == s_typeofMarshalByRefObject)) {
  487.                 return true;
  488.             }
  489.            
  490.             // Get the objref of the proxy
  491.             ObjRef oRef = IdentityObject.ObjectRef;
  492.            
  493.             // If the object ref is non-null then check against the type info
  494.             // stored in the it
  495.             if (null != oRef) {
  496.                 object oTP = GetTransparentProxy();
  497.                
  498.                 // Check that there is a matching type in the server object
  499.                 // hierarchy represented in the objref
  500.                 Message.DebugOut("Calling CanCastTo for type " + castType);
  501.                 IRemotingTypeInfo typeInfo = oRef.TypeInfo;
  502.                 if (null != typeInfo) {
  503.                     fCastOK = typeInfo.CanCastTo(castType, oTP);
  504.                     if (!fCastOK && typeInfo.GetType() == typeof(TypeInfo) && oRef.IsWellKnown()) {
  505.                         fCastOK = CanCastToWK(castType);
  506.                     }
  507.                 }
  508.                 else {
  509.                     if (oRef.IsObjRefLite()) {
  510.                         // we should do a dynamic cast across the network
  511.                         fCastOK = MarshalByRefObject.CanCastToXmlTypeHelper(castType, (MarshalByRefObject)o);
  512.                     }
  513.                 }
  514.             }
  515.             // This is a well known object which does not have a backing ObjRef
  516.             else {
  517.                 fCastOK = CanCastToWK(castType);
  518.             }
  519.             return fCastOK;
  520.         }
  521.        
  522.         // WellKnown proxies we always allow casts to interfaces, and allow
  523.         // casting down a single branch in the type hierarchy (both are on good
  524.         // faith. The calls are failed on server side if a bogus cast is done)
  525.         bool CanCastToWK(Type castType)
  526.         {
  527.             Message.DebugOut("CheckCast for well known objects and type " + castType);
  528.             bool fCastOK = false;
  529.             // Check whether the type to which we want to cast is
  530.             // compatible with the current type
  531.             if (castType.IsClass) {
  532.                 fCastOK = GetProxiedType().IsAssignableFrom(castType);
  533.             }
  534.             else {
  535.                 // NOTE: we are coming here also for x-context proxies
  536.                 // when unmanaged code cannot determine if the cast is not
  537.                 // okay
  538.                 if (!(IdentityObject is ServerIdentity)) {
  539.                     BCLDebug.Assert(IdentityObject.URI != null, "Bad WellKnown ID");
  540.                     // Always allow interface casts to succeed. If the
  541.                     // interface is not supported by the well known object
  542.                     // then we will throw an exception when the interface
  543.                     // is invoked.
  544.                     fCastOK = true;
  545.                 }
  546.             }
  547.            
  548.             return fCastOK;
  549.         }
  550.     }
  551.    
  552.    
  553.     internal class AgileAsyncWorkerItem
  554.     {
  555.         private IMethodCallMessage _message;
  556.         private AsyncResult _ar;
  557.         private object _target;
  558.        
  559.         public AgileAsyncWorkerItem(IMethodCallMessage message, AsyncResult ar, object target)
  560.         {
  561.             _message = new MethodCall(message);
  562.             _ar = ar;
  563.             _target = target;
  564.         }
  565.        
  566.         public static void ThreadPoolCallBack(object o)
  567.         {
  568.             ((AgileAsyncWorkerItem)o).DoAsyncCall();
  569.         }
  570.        
  571.        
  572.         public void DoAsyncCall()
  573.         {
  574.             (new StackBuilderSink(_target)).AsyncProcessMessage(_message, _ar);
  575.         }
  576.     }
  577.    
  578. }

Developer Fusion