private IMetricSeriesAggregator GetOrCreateAggregator(MetricAggregationCycleKind aggregationCycleKind, ref WeakReference <IMetricSeriesAggregator> aggregatorWeakRef) { while (true) { // Local cache for the reference in case of concurrnet updates: WeakReference <IMetricSeriesAggregator> currentAggregatorWeakRef = aggregatorWeakRef; // Try to dereference the weak reference: IMetricSeriesAggregator aggregatorHardRef = UnwrapAggregator(currentAggregatorWeakRef); if (aggregatorHardRef != null) { return(aggregatorHardRef); } // END OF FAST PATH. Could not dereference aggregator. Will attempt to create it... // So aggretator is NULL. For non-default cycle kinds, see if the there is even a cycle hooked up: if (aggregationCycleKind != CycleKind.Default) { IMetricSeriesFilter dataSeriesFilter; if (false == this.aggregationManager.IsCycleActive(aggregationCycleKind, out dataSeriesFilter)) { return(null); } // Ok, a cycle is, indeed, acgive up. See if the cycle's filter is interested in this series: // Respect the filter. Note: Filter may be user code. If user code is broken, assume we accept the series. IMetricValueFilter valuesFilter; if (false == Util.FilterWillConsume(dataSeriesFilter, this, out valuesFilter)) { return(null); } } // Ok, they want to consume us. Create new aggregator and a weak reference to it: IMetricSeriesAggregator newAggregator = this.GetNewOrRecycledAggregatorInstance(aggregationCycleKind); WeakReference <IMetricSeriesAggregator> newAggregatorWeakRef = new WeakReference <IMetricSeriesAggregator>(newAggregator, trackResurrection: false); // Store the weak reference to the aggregator. However, there is a race on doing it, so check for it: WeakReference <IMetricSeriesAggregator> prevAggregatorWeakRef = Interlocked.CompareExchange(ref aggregatorWeakRef, newAggregatorWeakRef, currentAggregatorWeakRef); if (prevAggregatorWeakRef == currentAggregatorWeakRef) { // We won the race and stored the aggregator. Now tell the manager about it and go on using it. // Note that the status of IsCycleActive and the dataSeriesFilter may have changed concurrently. // So it is essential that we do this after the above interlocked assignment of aggregator. // It ensures that only objects are added to the aggregator collection that are also referenced by the data series. // In addition, AddAggregator(..) always uses the current value of the aggregator-collection in a thread-safe manner. // Becasue the aggregator collection reference is always updated before telling the aggregators to cycle, // we know that any aggregator added will be eventually cycled. // If adding succeeds, AddAggregator(..) will have set the correct filter. bool added = this.aggregationManager.AddAggregator(newAggregator, aggregationCycleKind); // If the manager does not accept the addition, it means that the aggregationCycleKind is disabled or that the filter is not interested in this data series. // Either way we need to give up. if (added) { return(newAggregator); } else { // We could have accepted some values into this aggregator in the short time it was set in this series. We will loose those values. Interlocked.CompareExchange(ref aggregatorWeakRef, null, newAggregatorWeakRef); return(null); } } // We lost the race and a different aggregator was used. Loop again. Doing so will attempt to dereference the latest aggregator reference. } }
internal MetricSeries( MetricAggregationManager aggregationManager, MetricIdentifier metricIdentifier, IEnumerable <KeyValuePair <string, string> > dimensionNamesAndValues, IMetricSeriesConfiguration configuration) { // Validate and store aggregationManager: Util.ValidateNotNull(aggregationManager, nameof(aggregationManager)); this.aggregationManager = aggregationManager; // Validate and store metricIdentifier: Util.ValidateNotNull(metricIdentifier, nameof(metricIdentifier)); this.MetricIdentifier = metricIdentifier; // Copy dimensionNamesAndValues, validate values (keys are implicitly validated as they need to match the keys in the identifier): var dimNameVals = new Dictionary <string, string>(); if (dimensionNamesAndValues != null) { int dimIndex = 0; foreach (KeyValuePair <string, string> dimNameVal in dimensionNamesAndValues) { if (dimNameVal.Value == null) { throw new ArgumentNullException(Invariant($"The value for dimension '{dimNameVal.Key}' number is null.")); } if (String.IsNullOrWhiteSpace(dimNameVal.Value)) { throw new ArgumentNullException(Invariant($"The value for dimension '{dimNameVal.Key}' is empty or white-space.")); } dimNameVals[dimNameVal.Key] = dimNameVal.Value; dimIndex++; } } // Validate that metricIdentifier and dimensionNamesAndValues contain consistent dimension names: if (metricIdentifier.DimensionsCount != dimNameVals.Count) { throw new ArgumentException(Invariant($"The specified {nameof(metricIdentifier)} contains {metricIdentifier.DimensionsCount} dimensions,") + Invariant($" however the specified {nameof(dimensionNamesAndValues)} contains {dimNameVals.Count} name-value pairs with unique names.")); } foreach (string dimName in metricIdentifier.GetDimensionNames()) { if (false == dimNameVals.ContainsKey(dimName)) { throw new ArgumentException(Invariant($"The specified {nameof(metricIdentifier)} contains a dimension named \"{dimName}\",") + Invariant($" however the specified {nameof(dimensionNamesAndValues)} does not contain an entry for that name.")); } } // Store copied dimensionNamesAndValues: this.dimensionNamesAndValues = dimNameVals; // Validate and store configuration: Util.ValidateNotNull(configuration, nameof(configuration)); this.configuration = configuration; this.requiresPersistentAggregator = configuration.RequiresPersistentAggregation; // Init other instance vars: this.aggregatorPersistent = null; this.aggregatorDefault = null; this.aggregatorQuickPulse = null; this.aggregatorCustom = null; }