//******************************************************************************************************
// LoggerInternal.cs - Gbtc
//
// Copyright © 2016, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/eclipse-1.0.php
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 10/24/2016 - Steven E. Chisholm
// Generated original version of source code.
//
//
//******************************************************************************************************
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using GSF.Collections;
using GSF.Threading;
namespace GSF.Diagnostics
{
///
/// The fundamental functionality of .
///
internal class LoggerInternal
: IDisposable
{
private bool m_disposing;
private readonly object m_syncRoot;
private readonly Dictionary m_typeIndexCache;
private readonly List m_allPublishers;
private readonly List m_subscribers;
private readonly ConcurrentQueue> m_messages;
private readonly ScheduledTask m_routingTask;
private readonly ScheduledTask m_calculateRoutingTable;
public bool RoutingTablesValid { get; private set; }
///
/// Creates a .
///
public LoggerInternal(out LoggerInternal loggerClass)
{
//Needed to set the member variable of Logger.
//This is because ScheduleTask will call Logger before Logger's static constructor is completed.
loggerClass = this;
m_syncRoot = new object();
m_typeIndexCache = new Dictionary();
m_allPublishers = new List();
m_subscribers = new List();
m_messages = new ConcurrentQueue>();
// Since ScheduledTask calls ShutdownHandler, which calls Logger. This initialization method cannot occur
// until after the Logger static class has finished initializing.
m_calculateRoutingTable = new ScheduledTask();
m_calculateRoutingTable.Running += CalculateRoutingTable;
m_calculateRoutingTable.IgnoreShutdownEvent();
m_routingTask = new ScheduledTask(ThreadingMode.DedicatedForeground);
m_routingTask.Running += RoutingTask;
m_routingTask.IgnoreShutdownEvent();
}
///
/// Recalculates the entire routing table on a separate thread.
///
///
///
private void CalculateRoutingTable(object sender, EventArgs e)
{
lock (m_syncRoot)
{
//Some other thread won on the race condition.
if (RoutingTablesValid)
return;
var subscribers = new List(m_subscribers.Count);
m_subscribers.RemoveWhere(x =>
{
LogSubscriberInternal subscriber = (LogSubscriberInternal)x.Target;
if (subscriber != null)
{
subscribers.Add(subscriber);
return false;
}
return true;
});
foreach (var pub in m_allPublishers)
{
CalculateRoutingTableForPublisherSync(subscribers, pub);
}
RoutingTablesValid = true;
}
}
///
/// This method should be called with a lock on m_syncRoot
///
private void CalculateRoutingTableForPublisherSync(List subscribers, LogPublisherInternal publisher)
{
MessageAttributeFilterCollection filterCollection = new MessageAttributeFilterCollection();
foreach (var sub in subscribers)
{
filterCollection.Add(sub.GetSubscription(publisher), sub);
}
publisher.SubscriptionFilterCollection = filterCollection;
}
private void RoutingTask(object sender, EventArgs e)
{
lock (m_syncRoot)
{
if (!RoutingTablesValid)
{
CalculateRoutingTable(null, null);
}
Tuple messageTuple;
while (m_messages.TryDequeue(out messageTuple))
{
var publisher = messageTuple.Item2;
var message = messageTuple.Item1;
foreach (var route in publisher.SubscriptionFilterCollection.Routes)
{
var filter = route.Item1;
var subscriber = route.Item2.Target as LogSubscriberInternal;
if (subscriber == null)
RecalculateRoutingTable();
else if (filter.IsSubscribedTo(message.LogMessageAttributes))
subscriber.RaiseLogMessages(message);
}
}
}
}
///
/// Creates a that can subscribe to log messages.
///
///
public LogSubscriberInternal CreateSubscriber()
{
lock (m_syncRoot)
{
if (m_disposing)
return LogSubscriberInternal.DisposedSubscriber;
var s = new LogSubscriberInternal(RecalculateRoutingTable);
m_subscribers.Add(s.Reference);
return s;
}
}
///
/// Invalidates the current routing table.
///
private void RecalculateRoutingTable()
{
RoutingTablesValid = false;
//Wait some time before recalculating the routing tables. This will be done automatically
//if a message is routed before the table is recalculated.
m_calculateRoutingTable.Start(10);
}
///
/// Handles the routing of messages through the logging system.
///
/// the message to route
/// the publisher that is originating this message.
public void OnNewMessage(LogMessage message, LogPublisherInternal publisher)
{
m_messages.Enqueue(Tuple.Create(message, publisher));
m_routingTask.Start(50); //Allow a 50ms delay for multiple logs to queue if in a burst period.
}
///
/// Creates a type topic on a specified type.
///
/// the type to create the topic from
///
public LogPublisherInternal CreateType(Type type)
{
if ((object)type == null)
throw new ArgumentNullException(nameof(type));
LogPublisherInternal publisher;
lock (m_syncRoot)
{
if (!m_typeIndexCache.TryGetValue(type, out publisher))
{
publisher = new LogPublisherInternal(this, type);
m_typeIndexCache.Add(type, publisher);
if (!m_disposing)
{
m_allPublishers.Add(publisher);
var lst = new List();
foreach (var logSubscriberInternal in m_subscribers)
{
LogSubscriberInternal target = logSubscriberInternal.Target as LogSubscriberInternal;
if (target != null)
{
lst.Add(target);
}
}
CalculateRoutingTableForPublisherSync(lst, publisher);
}
}
}
return publisher;
}
///
/// Gracefully terminate all message routing. Function blocks until all termination is successful.
///
public void Dispose()
{
if (m_disposing)
return;
lock (m_syncRoot)
{
//Ensure that setting disposing is in a synchronized context.
m_disposing = true;
}
//These scheduled tasks block when dispose is called. Therefore do not put these in a lock on the base class.
m_calculateRoutingTable.Dispose();
m_routingTask.Dispose();
lock (m_syncRoot)
{
m_allPublishers.Clear();
foreach (var sub in m_subscribers)
{
(sub.Target as LogSubscriberInternal)?.Dispose();
}
m_subscribers.Clear();
}
}
}
}