How WCF Shoots Himself in the Foot with TraceSource

It is not often possible to write something interesting about the problems associated with parallel programming. This time it was "lucky". Due to the implementation of the standard TraceEvent method, an error occurred with blocking of several threads. I would like to warn about the existing nuance and tell about an interesting case from the support of our users. What does support have to do with it? You will learn this from the article. Enjoy reading.





Background

PVS-Studio CLMonitor.exe, . "" PVS-Studio C C++ . , PVS-Studio. : gcc, clang, cl, ..





Windows , 3 :





  1. 'CLMonitor.exe monitor';





  2. ;





  3. 'CLMonitor.exe analyze'.





'', , . – , . , . . 'CLMonitor.exe analyze' '', : ", , ". , – . , , .





, - . , , , . timeout'. – . . ... . , , .





. Windows CLMonitor.exe. Windows.





CLMonitor.exe

, . , .





, , , . , . .





, , , . . , C++ — PVS-Studio_Cmd. , , – Visual Studio. , , , – . , :





  • ;





  • ;





  • .





PVS-Studio_Cmd (*.vcxproj). "" MSBuild Visual Studio. NMake , . , NMake .vcxproj. . . , Unreal Engine *Unreal Build Tool *– , " ". .





, PVS-Studio , , CLMonitor.exe. . . , .





WCF (Windows Communication Foundation). , .





*ServiceHost * , . :





static ErrorLevels PerformMonitoring(....) 
{
  using (ServiceHost host = new ServiceHost(
                       typeof(CLMonitoringContract),   
                       new Uri[]{new Uri(PipeCredentials.PipeRoot)})) 
  {
    ....
    host.AddServiceEndpoint(typeof(ICLMonitoringContract), 
                            pipe, 
                            PipeCredentials.PipeName);
    host.Open();     
    ....
  }
}

      
      



: *CLMonitoringContract * ICLMonitoringContract.





*ICLMonitoringContract *– . *CLMonitoringContract *– . :





[ServiceContract(SessionMode = SessionMode.Required, 
                 CallbackContract = typeof(ICLMonitoringContractCallback))]
interface ICLMonitoringContract
{
  [OperationContract]
  void StopMonitoring(string dumpPath = null);
} 

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class CLMonitoringContract : ICLMonitoringContract
{
  public void StopMonitoring(string dumpPath = null)
  {
    ....
    CLMonitoringServer.CompilerMonitor.StopMonitoring(dumpPath);
  } 
}

      
      



, . . :





public void FinishMonitor()
{
  CLMonitoringContractCallback allback = new CLMonitoringContractCallback();
  var pipeFactory = new DuplexChannelFactory<ICLMonitoringContract>(
           allback, 
           pipe, 
           new EndpointAddress(....));

  ICLMonitoringContract pipeProxy = pipeFactory.CreateChannel();
  ((IContextChannel)pipeProxy).OperationTimeout = new TimeSpan(24, 0, 0);
  ((IContextChannel)pipeProxy).Faulted += CLMonitoringServer_Faulted;

  pipeProxy.StopMonitoring(dumpPath);
}

      
      



StopMonitoring, . .





, , , CLMonitor.exe.





. , . , – , . 10 :





** . ** 10 ? , , - 24 , , . , . .





( ) 5 , , .





. , . , :) .





''

, , :





. , :





public void FinishMonitor()
{
  ....
  ICLMonitoringContract pipeProxy = pipeFactory.CreateChannel();
  ((IContextChannel)pipeProxy).OperationTimeout = new TimeSpan(24, 0, 0);
  ((IContextChannel)pipeProxy).Faulted += CLMonitoringServer_Faulted;

  pipeProxy.StopMonitoring(dumpPath);            // <=
  ....
}

      
      



, , . , . , 5 . , , .





''

:





-, TraceEvent'? , , 50. . , . , , , - .. , , . , , , , .





, , , , ''. , . . ? , , . , - - . ReferenceSource TraceEvent.





TraceEvent lock:





, - TraceEvent, TraceInternal.critSec. , . , . , DiagnosticsConfiguration.Initialize:





NegotiateStream.AuthenticateAsServer, -:





- WCF. , . , DiagnosticsConfiguration.Initialize . ... - :





, , . , critSec, :





, , .





. TraceEvent GitHub. , Microsoft:





"Also one of the locks, TraceInternal.critSec, is only present if the TraceListener asks for it. Generally speaking such 'global' locks are not a good idea for a high performance logging system (indeed we don't recommend TraceSource for high performance logging at all, it is really there only for compatibility reasons)".





, Microsoft , IPC , , , ...





, :





  1. WCF.





  2. . 10 -.





  3. TraceEvent - Initialize.





  4. , .





  5. , TraceEvent, - lock . lock.





  6. Initialize lock.





, . , Initialize. - , TraceEvent, . TraceEvent' , '' . , , TraceEvent. !





, * * . - , . , , WCF – , - .





.





. , , , - . CrazyLogging, :





private void CrazyLogging()
{
  for (var i = 0; i < 30; i++)
  {
    var j = i;
    new Thread(new ThreadStart(() =>
    {
      while (!Program.isStopMonitor)
        Logger.TraceEvent(TraceEventType.Error, 0, j.ToString());
    })).Start();
  }
}

      
      



Trace, . , :





public void Trace()
{
  ListenersInitialization();
  CrazyLogging();
  ....
}

      
      



. ( Visual Studio 2019), 5 :





! (TestTraceSource.exe analyze), .





, , . Visual Studio . – , DiagnosticsConfiguration.Initialize. .





? , TraceSource – , , . , , . Event' TraceSource.TraceEvent.





"" . , Console.WriteLine. , - , , . - . enableLogger.





,

, .





, .exe trace. , analyze.





**: ** CrazyLogging . , . Visual Studio .





:





using System.Linq;

namespace TestTraceSource
{
  class Program
  {
    public static bool isStopMonitor = false;

    static void Main(string[] args)
    {
      if (!args.Any())
        return;

      if (args[0] == "trace")
      {
        Server server = new Server();
        server.Trace();
      }
      if (args[0] == "analyze")
      {
        Client client = new Client();
        client.FinishMonitor();
      }
    }  
  }
}

      
      



:





using System;
using System.Diagnostics;
using System.ServiceModel;
using System.Threading;

namespace TestTraceSource
{
  class Server
  {
    private static TraceSource Logger;

    public void Trace()
    {
      ListenersInitialization();
      CrazyLogging();
      using (ServiceHost host = new ServiceHost(
                          typeof(TestTraceContract), 
                          new Uri[]{new Uri(PipeCredentials.PipeRoot)}))
      {
        host.AddServiceEndpoint(typeof(IContract), 
                                new NetNamedPipeBinding(), 
                                PipeCredentials.PipeName);
        host.Open();

        while (!Program.isStopMonitor)
        {
          // We catch all processes, process them, and so on
        }

        host.Close();
      }

      Console.WriteLine("Complited.");
    }

    private void ListenersInitialization()
    {
      Logger = new TraceSource("PVS-Studio CLMonitoring");
      Logger.Switch.Level = SourceLevels.Verbose;
      Logger.Listeners.Add(new ConsoleTraceListener());

      String EventSourceName = "PVS-Studio CL Monitoring";

      EventLog log = new EventLog();
      log.Source = EventSourceName;
      Logger.Listeners.Add(new EventLogTraceListener(log));
    }

    private void CrazyLogging()
    {
      for (var i = 0; i < 30; i++)
      {
        var j = i;
        new Thread(new ThreadStart(() =>
        {
          var start = DateTime.Now;
          while (!Program.isStopMonitor)
            Logger.TraceEvent(TraceEventType.Error, 0, j.ToString());
        })).Start();
      }
    } 
  }
}

      
      



:





using System;
using System.ServiceModel;

namespace TestTraceSource
{
  class Client
  {
    public void FinishMonitor()
    {
      TestTraceContractCallback allback = new TestTraceContractCallback();
      var pipeFactory = new DuplexChannelFactory<IContract>(
                                allback,
                                new NetNamedPipeBinding(),
                                new EndpointAddress(PipeCredentials.PipeRoot 
                                                  + PipeCredentials.PipeName));
      IContract pipeProxy = pipeFactory.CreateChannel();
      pipeProxy.StopServer();

      Console.WriteLine("Complited.");    
    }
  }
}

      
      



:





using System;
using System.ServiceModel;

namespace TestTraceSource
{
  class PipeCredentials
  {
    public const String PipeName = "PipeCLMonitoring";
    public const String PipeRoot = "net.pipe://localhost/";
    public const long MaxMessageSize = 500 * 1024 * 1024; //bytes
  }

  class TestTraceContractCallback : IContractCallback
  {
    public void JobComplete()
    {
      Console.WriteLine("Job Completed.");
    }
  }

  [ServiceContract(SessionMode = SessionMode.Required, 
                   CallbackContract = typeof(IContractCallback))]
  interface IContract
  {
    [OperationContract]
    void StopServer();
  }

  interface IContractCallback
  {
    [OperationContract(IsOneWay = true)]
    void JobComplete();
  }

  [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
  class TestTraceContract : IContract
  {
    public void StopServer()
    {
      Program.isStopMonitor = true;
    }
  }
}

      
      



TraceSource.TraceEvent. , . . , TraceSource. - , .





. Twitter.





, : Nikolay Mironov. How WCF Shoots Itself in the Foot With TraceSource.








All Articles