tags:

views:

102

answers:

2

Update: with help from Henk, determined that public Dispose() is being called, which in turn calls private Dispose(true). This is my first implementation of IDisposable interface, so not sure if it is correct. I don't call Dispose explicitly anywhere. It seems that WCF architecture is calling it on exit from each OperationContract member.

Took unmanaged cleanup code out of Dispose for now, and multiple calls are able to access the static data. It seems that Dispose() is called on all locally allocated objects on return from calls, even if there is a reference to object in static storage. Not sure in .net world how to get around this so that IDisposable interface will get callled correctly. I'm guessing that these objects will get garbage collected at some point also.

Here is call stack on return from 1st call when Dispose is being called:

BossISeriesCwbxService.dll!BossISeriesCwbxServices.DataContracts.ISeriesSystem.Dispose(bool bDisposing = true) Line 119 C#
BossISeriesCwbxService.dll!BossISeriesCwbxServices.DataContracts.ISeriesSystem.Dispose() Line 107 + 0xd bytes C#
System.ServiceModel.dll!System.ServiceModel.Dispatcher.MessageRpc.DisposeParametersCore() + 0x56 bytes System.ServiceModel.dll!System.ServiceModel.Dispatcher.MessageRpc.DisposeParameters() + 0xf bytes System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessageCleanup(ref System.ServiceModel.Dispatcher.MessageRpc rpc = {System.ServiceModel.Dispatcher.MessageRpc}) + 0x135 bytes System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(ref System.ServiceModel.Dispatcher.MessageRpc rpc = {System.ServiceModel.Dispatcher.MessageRpc}) + 0x1bf bytes System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(ref System.ServiceModel.Dispatcher.MessageRpc rpc) + 0x80 bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(ref System.ServiceModel.Dispatcher.MessageRpc rpc) + 0x36 bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(ref System.ServiceModel.Dispatcher.MessageRpc rpc) + 0x43 bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(ref System.ServiceModel.Dispatcher.MessageRpc rpc) + 0xd7 bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.MessageRpc.Process(bool isOperationContextSet = false) + 0x9b bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.Dispatch(ref System.ServiceModel.Dispatcher.MessageRpc rpc, bool isOperationContextSet) + 0x2d bytes

System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(System.ServiceModel.Channels.RequestContext request = {System.ServiceModel.Security.SecuritySessionServerSettings.SecuritySessionRequestContext}, bool cleanThread, System.ServiceModel.OperationContext currentOperationContext) + 0x20c bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(System.ServiceModel.Channels.RequestContext request, System.ServiceModel.OperationContext currentOperationContext) + 0xdf bytes

System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(System.IAsyncResult result) + 0x43 bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.OnContinueAsyncReceive(object state) + 0x45 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke2() + 0x46 bytes System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.OnSecurityContextCallback(object o) + 0x28 bytes
mscorlib.dll!System.Security.SecurityContext.Run(System.Security.SecurityContext securityContext, System.Threading.ContextCallback callback, object state) + 0x55 bytes

System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke() + 0x4d bytes

System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ProcessCallbacks() + 0x180 bytes System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.CompletionCallback(object state) + 0x7a bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ScheduledOverlapped.IOCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* nativeOverlapped) + 0xf bytes
SMDiagnostics.dll!System.ServiceModel.Diagnostics.Utility.IOCompletionThunk.UnhandledExceptionFrame(uint error, uint bytesRead, System.Threading.NativeOverlapped* nativeOverlapped) + 0x3d bytes
mscorlib.dll!System.Threading._IOCompletionCallback.PerformIOCompletionCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* pOVERLAP) + 0x54 bytes

I read some posts on caching static data in a WCF service implementation class, and was having a problem with the GC calling dispose on the objects in a static Dictionary. I am referencing some activex objects from IBM iSeries Access, so I implemented IDisposable interface to clean up connection to iSeries. My problem is the GC is Disposing of objects in the Static members of the Service class. Not sure all the code was required, but here it is anyway. The problem is that on return from each OperationContract method, the GC is calling Dispose on the ISeriesSystem or Queue object that was added to the associated Dictionary, but the ISeriesSystem Dictionary is static, so I thought that it held a reference to the object, so GC wouldn't be done until it is removed from Dictionary.

Service Interface:

[ServiceContract(Namespace="BossISeriesCwbxServices")]
public interface IDataQueueService
{
     [OperationContract]
     ISeriesSystem SystemInitialize(string sISeriesName);

     [OperationContract(Name="FinalizeSystemByName")]
     void SystemFinalize(string sISeriesName);

     [OperationContract]
     void SystemFinalize(ISeriesSystem oISeriesSystem);

     [OperationContract]
     Queue QueueInitialize(string sQueueName, string sLibrary, string sISeriesName);

     [OperationContract(Name="FinalizeQueueByName")]
     void QueueFinalize(string sQueueName, string sLibrary, string sISeriesName);

     [OperationContract]
     void QueueFinalize(Queue oDataQueue);

     [OperationContract (Name="QueueWriteByName")]
     void QueueWrite(string sQueueName, string sLibrary, string sISeriesName, string sMessage);

     [OperationContract]
     void QueueWrite(Queue oDataQueue, string sMessage);

     [OperationContract (Name="QueueReadByName")]
     string QueueRead(string sQueueName, string sLibrary, string sISeriesName);

     [OperationContract]
     string QueueRead(Queue oDataQueue);    
}

Service Implementation:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)] 
public class DataQueueService : IDataQueueService
{
    private static Dictionary<string, ISeriesSystem> mdictISeriesSystems = new Dictionary<string, ISeriesSystem>();
    public static IDictionary<string, ISeriesSystem> ISeriesDict
    {
       get { return mdictISeriesSystems; }
    } 

  public ISeriesSystem SystemInitialize(string sISeriesName)
  {
     ISeriesSystem oISeriesSystem = AddSystem(sISeriesName);
     return oISeriesSystem;
  }

  public void SystemFinalize(string sISeriesName)
  {
  }

  public void SystemFinalize(ISeriesSystem oISeriesSystem)
  {
     SystemFinalize(oISeriesSystem.Name);
  }


  public Queue QueueInitialize(string sQueueName, string sLibrary, string sISeriesName)
  {
     ISeriesSystem oISeriesSystem = null;
     Queue oDataQueue = null;

     try
     {
        oISeriesSystem = AddSystem(sISeriesName);
        oDataQueue = oISeriesSystem.AddQueue(sQueueName, sLibrary);
     }
     catch (Exception ex)
     {
        // ToDo: Log ex to WCF service log and remove from Console.
        Console.WriteLine(ex.ToString());
        oDataQueue = null;
     }

     return oDataQueue;
  }

  public Queue QueueInitialize(string sQueueName, string sLibrary, ISeriesSystem oISeriesSystem)
  {
     return QueueInitialize(sQueueName, sLibrary, oISeriesSystem.Name);
  }

  public void QueueFinalize(string sQueueName, string sLibrary, string sISeriesName)
  {
     string sISeriesKey = sISeriesName.Trim();
     string sDataQueueKey = sLibrary.Trim() + sQueueName.Trim();

     ISeriesSystem oISeriesSystem = null;
     Queue oDataQueue = null;


     if (DataQueueService.ISeriesDict.TryGetValue(sISeriesKey, out oISeriesSystem))
     {
        if (oISeriesSystem.DataQueueDict.TryGetValue(sDataQueueKey, out oDataQueue))
        {
           oDataQueue.Dispose();
           oDataQueue = null;
           oISeriesSystem.DataQueueDict.Remove(sDataQueueKey);
        }

        if (oISeriesSystem.DataQueueDict.Count == 0)
        {
           oISeriesSystem.Dispose();
           oISeriesSystem = null;
        }
     }
  }

  public void QueueFinalize(Queue oDataQueue)
  {
     QueueFinalize(oDataQueue.Name, oDataQueue.Library, oDataQueue.ISeriesName);
  }

  public void QueueWrite(string sQueueName, string sLibrary, string sISeriesName, string sMessage)
  {
     string sISeriesKey = sISeriesName.Trim();
     string sDataQueueKey = sLibrary.Trim() + sQueueName.Trim();

     ISeriesSystem oISeriesSystem = null;
     Queue oDataQueue = null;


     if (DataQueueService.ISeriesDict.TryGetValue(sISeriesKey, out oISeriesSystem))
     {
        if (oISeriesSystem.DataQueueDict.TryGetValue(sDataQueueKey, out oDataQueue))
        {
           oDataQueue.Write(sMessage);
        }
     }
  }

  public void QueueWrite(Queue oDataQueue, string sMessage)
  {
     QueueWrite(oDataQueue.Name, oDataQueue.Library, oDataQueue.ISeriesName, sMessage);
  }

  public string QueueRead(string sQueueName, string sLibrary, string sISeriesName)
  {
     string sISeriesKey = sISeriesName.Trim();
     string sDataQueueKey = sLibrary.Trim() + sQueueName.Trim();

     ISeriesSystem oISeriesSystem = null;
     Queue oDataQueue = null;


     if (DataQueueService.ISeriesDict.TryGetValue(sISeriesKey, out oISeriesSystem))
     {
        if (oISeriesSystem.DataQueueDict.TryGetValue(sDataQueueKey, out oDataQueue))
        {
           return oDataQueue.Read();
        }
     }

     return "";
  }

  public string QueueRead(Queue oDataQueue)
  {
     return QueueRead(oDataQueue.Name, oDataQueue.Library, oDataQueue.ISeriesName);

  }

  ISeriesSystem AddSystem(string sISeriesName)
  {
     ISeriesSystem oISeriesSystem = null;
     string sISeriesKey = sISeriesName.Trim();

     if (!DataQueueService.ISeriesDict.TryGetValue(sISeriesKey, out oISeriesSystem))
     {
        oISeriesSystem = new ISeriesSystem(sISeriesName);
        DataQueueService.ISeriesDict[sISeriesKey] = oISeriesSystem;
     }

     return oISeriesSystem;
  }

ISeriesSystem DataContract:

using System;
using System.Collections.Generic;
using System.Text;

using System.ServiceModel;
using System.Runtime.Serialization;

using cwbx;

namespace BossISeriesCwbxServices.DataContracts
{
   public class ISeriesSystem : IDisposable
   {

      private string msName;
      [DataMember]
      public string Name
      {
         get { return msName; }
         set { msName = value; }
      }

      private Dictionary<string, Queue> mdictDataQueues = new Dictionary<string, Queue>();
      public IDictionary<string, Queue> DataQueueDict
      {
         get { return mdictDataQueues; }
      }

      private cwbx.AS400System mcwbxISeriesSystem = new AS400System();
      private cwbx.AS400System CwbxISeriesSystem
      {
         get { return mcwbxISeriesSystem; }
         set { mcwbxISeriesSystem = value; }
      }


      private bool bDisposed = false;


      public ISeriesSystem()
      {

      }

      public ISeriesSystem(string sISeriesName)
      {
         try
         {
            // Set DataContract properties.
            this.Name = sISeriesName;

            // Connect to iSeries, Logon and connect to iSeries services that may be used.
            this.CwbxISeriesSystem.Define(sISeriesName);
            this.CwbxISeriesSystem.UserID = "APP1DAK";
            this.CwbxISeriesSystem.Password = "DONNA99";
            this.CwbxISeriesSystem.Signon();
            this.CwbxISeriesSystem.Connect(cwbcoServiceEnum.cwbcoServiceDataQueues);
            this.CwbxISeriesSystem.Connect(cwbcoServiceEnum.cwbcoServiceSecurity);
            this.CwbxISeriesSystem.Connect(cwbcoServiceEnum.cwbcoServiceRemoteCmd);
         }
         catch (Exception ex)
         {
            // ToDo: Log ex to WCF service log and remove from Console.
            Console.WriteLine(ex.ToString());
            foreach (cwbx.Error cwbxError in this.CwbxISeriesSystem.Errors)
            {
               Console.WriteLine(cwbxError.Text);
               Console.WriteLine(cwbxError.ToString());
            }
         }
      }

      ~ISeriesSystem()
      {
         Dispose(false);
      }

      public void Dispose()
      {
         Dispose(true);
         GC.SuppressFinalize(this);
      }

      private void Dispose(bool bDisposing)
      {
         // Only Dispose of the object 1 time.
         if (!this.bDisposed)
         {
            // If disposing equals true, Dispose() was called by GC, so dispose all managed resources.
            if (bDisposing)
            {
               // Dispose managed resources, calling object Dispose method for objects
               // that implement IDisposable interface.
            }

            try
            {
               if (this.CwbxISeriesSystem.IsConnected(cwbcoServiceEnum.cwbcoServiceAny) == 1)
               {
                  this.CwbxISeriesSystem.Disconnect(cwbcoServiceEnum.cwbcoServiceAll);
               }
            }
            catch (Exception ex)
            {
               // ToDo: Log ex to WCF service log and remove from Console.
               Console.WriteLine(ex.ToString());
               foreach (cwbx.Error cwbxError in this.CwbxISeriesSystem.Errors)
               {
                  Console.WriteLine(cwbxError.Text);
                  Console.WriteLine(cwbxError.ToString());
               }
            }

            // Mark disposing as being done.
            bDisposed = true;
         }
      }

      public Queue AddQueue(string sQueueName, string sLibrary)
      {
         Queue oDataQueue = null;
         string sDataQueueKey = sLibrary.Trim() + sQueueName.Trim();

         if (!this.DataQueueDict.TryGetValue(sDataQueueKey, out oDataQueue))
         {
            oDataQueue = new Queue(sQueueName, sLibrary, this.CwbxISeriesSystem);
            this.DataQueueDict[sDataQueueKey] = oDataQueue;
         }

         return oDataQueue;
      }
   }
}

Queue DataContract:

using System;
using System.Collections.Generic;
using System.Text;

using System.ServiceModel;
using System.Runtime.Serialization;

using cwbx;

namespace BossISeriesCwbxServices.DataContracts
{
   [DataContract]
   public class Queue : IDisposable
   {
      private string msName;
      [DataMember]
      public string Name
      {
         get { return msName; }
         set { msName = value; }
      }

      private string msLibrary;
      [DataMember]
      public string Library
      {
         get { return msLibrary; }
         set { msLibrary = value; }
      }

      private string msISeriesName;
      [DataMember]
      public string ISeriesName
      {
         get { return msISeriesName; }
         set { msISeriesName = value; }
      }

      private short miWaitTime = 10;
      [DataMember]
      public short WaitTime
      {
         get { return miWaitTime; }
         set { miWaitTime = value; }
      }

      private short miNumberOfAttempts = 1;
      [DataMember]
      public short NumberOfAttempts
      {
         get { return miNumberOfAttempts; }
         set { miNumberOfAttempts = value; }
      }

      private short miMaxQueueIndex = 1;
      public short MaxQueueIndex
      {
         get { return miMaxQueueIndex; }
         set { miMaxQueueIndex = value; }
      }

      private short miCurrentQueueIndex = 1;
      public short CurrentQueueIndex
      {
         get { return miCurrentQueueIndex; }
         set { miCurrentQueueIndex = value; }
      }



      private cwbx.DataQueue mcwbxDataQueue = new cwbx.DataQueue();
      private cwbx.DataQueue CwbxDataQueue
      {
         get { return mcwbxDataQueue; }
         set { mcwbxDataQueue = value; }
      }

      private bool bDisposed = false;


      public Queue()
      {
      }

      public Queue(string sQueueName, string sLibrary, cwbx.AS400System cwbxISeriesSystem)
      {
         this.Name = sQueueName;
         this.Library = sLibrary;
         this.ISeriesName = cwbxISeriesSystem.SystemName;

         this.CwbxDataQueue.QueueName = sQueueName;
         this.CwbxDataQueue.LibraryName = sLibrary;
         this.CwbxDataQueue.system = cwbxISeriesSystem;
      }

      ~Queue()
      {
         Dispose(false);
      }

      public void Dispose()
      {
         Dispose(true);
         GC.SuppressFinalize(this);
      }

      private void Dispose(bool bDisposing)
      {
         // Only Dispose of the object 1 time.
         if (!this.bDisposed)
         {
            // If disposing equals true, Dispose() was called by GC, so dispose all managed resources.
            if (bDisposing)
            {
               // Dispose managed resources, calling object Dispose method for objects
               // that implement IDisposable interface.
            }

            // Call the appropriate methods to clean up unmanaged resources here.
            try
            {
               this.CwbxDataQueue = null;
            }
            catch (Exception ex)
            {
               // ToDo: Log ex to WCF service log and remove from Console.
               Console.WriteLine(ex.ToString());
               foreach (cwbx.Error cwbxError in this.CwbxDataQueue.Errors)
               {
                  Console.WriteLine(cwbxError.Text);
                  Console.WriteLine(cwbxError.ToString());
               }
            }
            // Mark disposing as being done.
            bDisposed = true;
         }
      }


      public void Write(string sMessage)
      {
         try
         {
            cwbx.StringConverter cwbxStringConverter = new cwbx.StringConverter();
            Object oBytes = cwbxStringConverter.ToBytes(sMessage);
            this.CwbxDataQueue.Write(oBytes, false);
         }
         catch (Exception ex)
         {
            // ToDo: Log ex to WCF service log and remove from Console.
            Console.WriteLine(ex.ToString());
            foreach (cwbx.Error cwbxError in this.CwbxDataQueue.Errors)
            {
               Console.WriteLine(cwbxError.Text);
               Console.WriteLine(cwbxError.ToString());
            }
         }
      }

      public string Read()
      {
         try
         {
            Object oObject = null;
            return (new cwbx.StringConverter()).FromBytes(this.CwbxDataQueue.Read(this.WaitTime * this.NumberOfAttempts, out oObject));
         }
         catch (Exception ex)
         {
            Console.WriteLine(ex.ToString());
            foreach (cwbx.Error cwbxError in this.CwbxDataQueue.Errors)
            {
               Console.WriteLine(cwbxError.Text);
               Console.WriteLine(cwbxError.ToString());
            }

            return "";
         }
      }
   }
}

Client Code:

ISeriesSystem oISeriesSystem = null;
Queue oDataQueue = null;

oISeriesSystem = DQService.SystemInitialize("A2029D2.AS400.US.UPS.COM");
oDataQueue = DQService.QueueInitialize("SMTLST020", "IB5EXE", oISeriesSystem.Name);
oISeriesSystem.DataQueueDict.Add(oDataQueue.Library + oDataQueue.Name, oDataQueue);
ISeriesSystemDict.Add(oISeriesSystem.Name, oISeriesSystem);

DQService.QueueWrite(oDataQueue, "Testing cwbx.DataQueue WCF service");
string sMessage = DQService.QueueRead(oDataQueue);

Exe Hosted Service:

Uri baseAddress = new Uri("http://localhost:8080/BossISeriesCwbxServices");

//Instantiate new ServiceHost 
moServiceHost = new ServiceHost(typeof(BossISeriesCwbxServices.DataQueueService), baseAddress);

// Add Endpoint
moServiceHost.AddServiceEndpoint(typeof(BossISeriesCwbxServices.IDataQueueService), new WSHttpBinding(), "IDataQueueService");
// Enable metadata exchange.
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
moServiceHost.Description.Behaviors.Add(smb);

//Open moServiceHost
moServiceHost.Open();
Console.WriteLine("The IDataQueueService is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
A: 

Can you put the following code :

[ServiceContract(SessionMode = SessionMode.Required)]

on your service contract just to make sure the transport session is being created? If it isn't this should throw an error.

WSHttpBinding does not create a transport session without security and reliable session.

EDIT :

Other than that, since you are creating a Single Instance service, why would you need static members? There is only going to be one instance alive, so, any instance members of that instance will live with it and will be kind of static. Have you thought about that or there is a specific reason for using static members?

decyclone
same behavior with SessionMode.Required. I'm not tied to Http binding. Actually will be using NetTcp if this goes into produciton.
Don Kelley
+1  A: 

Does it happen regularly or every now and then? Does it happen under development or only (after some time) on the production server?

When you host this under IIS, the server might decide to 'recycle' your app. Basic advice: don't use static in server apps. It's not reliable and not scalable.


Edit
OK, I've read a little more (but not all).

The problem is that on return from each OperationContract method, the GC is calling Dispose on the ISeriesSystem or Queue object

You should verify that in great detail. Is it really the GC that calls your Finalizer(s) (aka destructors)? You should use logging or debugging to verify that the overload Dispose(false) is being called. If Dispose(true) is called (and I see a lot of code involved in doing that) you should stacktrace to the actual cause.

Henk Holterman
Is a proof of concept service, not in production. Happens on every call that adds to the Static Dictionary.
Don Kelley
Dispose(true) is called
Don Kelley
stack trace: (won't let me paste entire stack)> BossISeriesCwbxService.dll!BossISeriesCwbxServices.DataContracts.ISeriesSystem.Dispose() Line 107 C# System.ServiceModel.dll!System.ServiceModel.Dispatcher.MessageRpc.DisposeParametersCore() + 0x56 bytes System.ServiceModel.dll!System.ServiceModel.Dispatcher.MessageRpc.DisposeParameters() + 0xf bytes
Don Kelley
Is WCF calling Dispose() on return from OperationContract? I want to cleanup iSeries connections in Dispose, because there will be many of them in production. The actual static dictionary seems to be still resident in memeory, on subsequent calls to ServiceContract. call stack starts here: mscorlib.dll!System.Threading._IOCompletionCallback.PerformIOCompletionCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* pOVERLAP) + 0x54 bytes
Don Kelley
Don, please add the stacktrace to the question and format properly. Also write down that Dispose(true) is being called.
Henk Holterman
Will do. Sorry, this is my first post, not sure of what the rules are.
Don Kelley
Maybe I've chosen a bad architecture. What I'm trying to do, is a proof of concept for hosting iSeries DataQueue transactions in a WCF service. DataQueue reads/writes, require a iSeries system connection and a DataQueue definition. The system connection is relatively expensive for the throughput needed (1500 clients - 500,000 transactions per day per iSeries, 8 iSeries) so I didn't think that recreating the connection on a PerCall basis would be a good idea. Is there a better way to cache the objects needed, than using static data structures?
Don Kelley
For now, I removed the code from Dispose that cleans up unmanaged code, and the managed objects that contain the unmanaged child objects stay around from call to call, and the unmanaged iSeries objects are useable. Seems odd.
Don Kelley