Guest User

PipeClient.cs with deadlock fix v2

a guest
Dec 15th, 2012
74
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. using System;
  2. using System.IO;
  3. using System.Runtime.InteropServices;
  4. using System.Threading;
  5. using Microsoft.Win32.SafeHandles;
  6.  
  7. namespace wyDay.Controls
  8. {
  9.     /// <summary>Allow pipe communication between a server and a client.</summary>
  10.     public class PipeClient : IDisposable
  11.     {
  12.         private object _lock = new object();
  13.  
  14.         [DllImport("kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
  15.         static extern SafeFileHandle CreateFile(
  16.             string pipeName,
  17.             uint dwDesiredAccess,
  18.             uint dwShareMode,
  19.             IntPtr lpSecurityAttributes,
  20.             uint dwCreationDisposition,
  21.             uint dwFlagsAndAttributes,
  22.             IntPtr hTemplate);
  23.  
  24.         /// <summary>Handles messages received from a server pipe</summary>
  25.         /// <param name="message">The byte message received</param>
  26.         public delegate void MessageReceivedHandler(byte[] message);
  27.  
  28.         /// <summary>Event is called whenever a message is received from the server pipe</summary>
  29.         public event MessageReceivedHandler MessageReceived;
  30.  
  31.         /// <summary>Handles server disconnected messages</summary>
  32.         public delegate void ServerDisconnectedHandler();
  33.  
  34.         /// <summary>Event is called when the server pipe is severed.</summary>
  35.         public event ServerDisconnectedHandler ServerDisconnected;
  36.  
  37.         const int BUFFER_SIZE = 4096;
  38.  
  39.         FileStream stream;
  40.         SafeFileHandle handle;
  41.         Thread readThread;
  42.  
  43.         /// <summary>Gets if this client connected to a server pipe.</summary>
  44.         public bool Connected { get; private set; }
  45.  
  46.         /// <summary>The pipe this client is connected to.</summary>
  47.         public string PipeName { get; private set; }
  48.  
  49.         #region Dispose
  50.  
  51.         /// <summary>Indicates whether this instance is disposed.</summary>
  52.         bool isDisposed;
  53.  
  54.         /// <summary>
  55.         /// Finalizes an instance of the <see cref="PipeClient"/> class.
  56.         /// Releases unmanaged resources and performs other cleanup operations before the
  57.         /// <see cref="PipeClient"/> is reclaimed by garbage collection.
  58.         /// </summary>
  59.         ~PipeClient()
  60.         {
  61.             Dispose(false);
  62.         }
  63.  
  64.         /// <summary>Releases unmanaged and - optionally - managed resources.</summary>
  65.         /// <param name="isManaged">Result: <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  66.         protected virtual void Dispose(bool isManaged)
  67.         {
  68.             if (isManaged)
  69.             {
  70.                 lock (_lock)
  71.                 {
  72.                     if (!isDisposed)
  73.                     {
  74.  
  75.                         // dispose managed resources
  76.                         Disconnect();
  77.  
  78.  
  79.                         // free unmanaged resources
  80.                         // Set large fields to null.
  81.  
  82.                         // Instance is disposed
  83.                         isDisposed = true;
  84.                     }
  85.                 }
  86.             }
  87.             // Instance is disposed
  88.             isDisposed = true;
  89.         }
  90.  
  91.         /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
  92.         public void Dispose()
  93.         {
  94.             Dispose(true);
  95.             GC.SuppressFinalize(this);
  96.         }
  97.  
  98.         #endregion
  99.  
  100.         /// <summary>Connects to the server with a pipename.</summary>
  101.         /// <param name="pipename">The name of the pipe to connect to.</param>
  102.         public void Connect(string pipename)
  103.         {
  104.             lock (_lock)
  105.             {
  106.                 if (isDisposed) return;
  107.                 if (Connected)
  108.                     throw new Exception("Already connected to pipe server.");
  109.  
  110.                 PipeName = pipename;
  111.  
  112.                 handle =
  113.                     CreateFile(
  114.                         PipeName,
  115.                         0xC0000000, // GENERIC_READ | GENERIC_WRITE = 0x80000000 | 0x40000000
  116.                         0,
  117.                         IntPtr.Zero,
  118.                         3, // OPEN_EXISTING
  119.                         0x40000000, // FILE_FLAG_OVERLAPPED
  120.                         IntPtr.Zero);
  121.  
  122.                 // could not create handle - server probably not running
  123.                 if (handle.IsInvalid)
  124.                 {
  125.                     handle = null;
  126.                     return;
  127.                 }
  128.  
  129.                 Connected = true;
  130.  
  131.                 // start listening for messages
  132.                 readThread = new Thread(Read) { IsBackground = true };
  133.                 readThread.Start();
  134.             }
  135.         }
  136.  
  137.         /// <summary>Disconnects from the server.</summary>
  138.         public void Disconnect()
  139.         {
  140.             lock (_lock)
  141.             {
  142.                 if (isDisposed) return;
  143.  
  144.                 if (!Connected)
  145.                     return;
  146.  
  147.                 // we're no longer connected to the server
  148.                 Connected = false;
  149.                 PipeName = null;
  150.  
  151.                 // clean up resources
  152.                 if (stream != null)
  153.                     stream.Dispose();
  154.  
  155.                 // If Connected == true then handle is non-null.
  156.                 // Thus, just dispose it.
  157.                 handle.Dispose();
  158.  
  159.                 stream = null;
  160.                 handle = null;
  161.             }
  162.         }
  163.  
  164.         void Read()
  165.         {
  166.             lock (_lock)
  167.             {
  168.                 if (isDisposed) return;
  169.  
  170.                 stream = new FileStream(handle, FileAccess.ReadWrite, BUFFER_SIZE, true);
  171.  
  172.             }
  173.  
  174.             byte[] readBuffer = new byte[BUFFER_SIZE];
  175.  
  176.             while (true)
  177.             {
  178.                 int bytesRead = 0;
  179.  
  180.                 using (MemoryStream ms = new MemoryStream())
  181.                 {
  182.                     try
  183.                     {
  184.                         // read the total stream length
  185.                         int totalSize = stream.Read(readBuffer, 0, 4);
  186.  
  187.                         // client has disconnected
  188.                         if (totalSize == 0)
  189.                             break;
  190.  
  191.                         totalSize = BitConverter.ToInt32(readBuffer, 0);
  192.  
  193.                         do
  194.                         {
  195.                             int numBytes = stream.Read(readBuffer, 0, Math.Min(totalSize - bytesRead, BUFFER_SIZE));
  196.  
  197.                             ms.Write(readBuffer, 0, numBytes);
  198.  
  199.                             bytesRead += numBytes;
  200.  
  201.                         } while (bytesRead < totalSize);
  202.                     }
  203.                     catch
  204.                     {
  205.                         //read error has occurred
  206.                         break;
  207.                     }
  208.  
  209.                     //client has disconnected
  210.                     if (bytesRead == 0)
  211.                         break;
  212.  
  213.                     //get the event to execute while locked, but execute it outside the lock to prevent deadlock
  214.                     MessageReceivedHandler eventToExecute;
  215.                     lock (_lock)
  216.                     {
  217.                         if (isDisposed) return;
  218.                         eventToExecute = MessageReceived;
  219.                     }
  220.                     //fire message received event
  221.                     if (eventToExecute != null)
  222.                         eventToExecute(ms.ToArray());
  223.  
  224.                 }
  225.             }
  226.  
  227.             bool isLocked = false;
  228.             try
  229.             {
  230.                 Monitor.Enter(_lock);
  231.                 isLocked = true;
  232.                 if (isDisposed) return;
  233.  
  234.                 // if connected, then the disconnection was
  235.                 // caused by a server terminating, otherwise it was from
  236.                 // a call to Disconnect()
  237.                 if (Connected)
  238.                 {
  239.                     //clean up resource
  240.                     stream.Dispose();
  241.                     handle.Dispose();
  242.  
  243.                     stream = null;
  244.                     handle = null;
  245.  
  246.                     // we're no longer connected to the server
  247.                     Connected = false;
  248.                     PipeName = null;
  249.  
  250.                     //get the event to execute while locked, but execute it outside the lock to prevent deadlock
  251.                     var eventToExecute = ServerDisconnected;
  252.                     Monitor.Exit(_lock);
  253.                     isLocked = false;
  254.  
  255.                     if (eventToExecute != null)
  256.                         eventToExecute();
  257.                 }
  258.             }
  259.             finally
  260.             {
  261.                 if (isLocked)
  262.                 {
  263.                     Monitor.Exit(_lock);
  264.                 }
  265.             }
  266.         }
  267.  
  268.         /// <summary>Sends a message to the server.</summary>
  269.         /// <param name="message">The message to send.</param>
  270.         /// <returns>True if the message is sent successfully - false otherwise.</returns>
  271.         public bool SendMessage(byte[] message)
  272.         {
  273.             lock (_lock)
  274.             {
  275.                 if (isDisposed) return false;
  276.  
  277.                 if (stream == null) return false;
  278.  
  279.                 try
  280.                 {
  281.                     // write the entire stream length
  282.                     stream.Write(BitConverter.GetBytes(message.Length), 0, 4);
  283.  
  284.                     stream.Write(message, 0, message.Length);
  285.                     stream.Flush();
  286.                     return true;
  287.                 }
  288.                 catch
  289.                 {
  290.                     return false;
  291.                 }
  292.             }
  293.         }
  294.     }
  295. }
Advertisement
Add Comment
Please, Sign In to add comment