<commit_before>﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using StackExchange.Profiling;

namespace StackExchange.Opserver.Data
{
    public class Cache<T> : Cache where T : class
    {
        /// <summary>
        /// Returns if this cache has data - THIS WILL NOT TRIGGER A FETCH
        /// </summary>
        public override bool ContainsData => DataBacker != null;
        public override object GetData() { return DataBacker; }
        public override Type Type => typeof (T);
        private readonly SemaphoreSlim _pollSemaphoreSlim = new SemaphoreSlim(1);

        public override string InventoryDescription
        {
            get
            {
                var tmp = DataBacker;
                return tmp == null ? null : ((tmp as IList)?.Count.Pluralize("item") ?? "1 Item");
            }
        }

        private T DataBacker { get; set; }
        public T Data
        {
            get
            {
                if (_needsPoll)
                {
                    PollAsync().Wait(10000);
                }
                return DataBacker;
            }
            internal set { DataBacker = value; }
        }
        public Func<Cache<T>, Task> UpdateCache { get; set; }

        /// <summary>
        /// If profiling for cache polls is active, this contains a MiniProfiler of the current or last poll
        /// </summary>
        public MiniProfiler Profiler { get; set; }

        public override async Task<int> PollAsync(bool force = false)
        {
            Interlocked.Increment(ref PollingEngine._activePolls);
            int result;
            if (CacheKey.HasValue())
            {
                result = await CachePollAsync(force).ConfigureAwait(false);
            }
            else
            {
                if (force) _needsPoll = true;
                result = await UpdateAsync().ConfigureAwait(false);
            }
            Interlocked.Decrement(ref PollingEngine._activePolls);
            return result;
        }

        private async Task<int> UpdateAsync()
        {
            PollStatus = "UpdateAsync";
            if (!_needsPoll && !IsStale) return 0;

            PollStatus = "Awaiting Semaphore";
            await _pollSemaphoreSlim.WaitAsync().ConfigureAwait(false);
            if (!_needsPoll && !IsStale) return 0;
            if (_isPolling) return 0;
            CurrentPollDuration = Stopwatch.StartNew();
            _isPolling = true;
            bool errored = false;
            try
            {
                Interlocked.Increment(ref _pollsTotal);
                PollStatus = "UpdateCache";
                await UpdateCache(this).ConfigureAwait(false);
                PollStatus = "UpdateCache Complete";
                _needsPoll = false;
                if (DataBacker != null)
                    Interlocked.Increment(ref _pollsSuccessful);
                return DataBacker != null ? 1 : 0;
            }
            catch (Exception e)
            {
                var errorMessage = e.Message;<commit_msg>Always release the semaphores on races<commit_after>﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using StackExchange.Profiling;

namespace StackExchange.Opserver.Data
{
    public class Cache<T> : Cache where T : class
    {
        /// <summary>
        /// Returns if this cache has data - THIS WILL NOT TRIGGER A FETCH
        /// </summary>
        public override bool ContainsData => DataBacker != null;
        public override object GetData() { return DataBacker; }
        public override Type Type => typeof (T);
        private readonly SemaphoreSlim _pollSemaphoreSlim = new SemaphoreSlim(1);

        public override string InventoryDescription
        {
            get
            {
                var tmp = DataBacker;
                return tmp == null ? null : ((tmp as IList)?.Count.Pluralize("item") ?? "1 Item");
            }
        }

        private T DataBacker { get; set; }
        public T Data
        {
            get
            {
                if (_needsPoll)
                {
                    PollAsync().Wait(10000);
                }
                return DataBacker;
            }
            internal set { DataBacker = value; }
        }
        public Func<Cache<T>, Task> UpdateCache { get; set; }

        /// <summary>
        /// If profiling for cache polls is active, this contains a MiniProfiler of the current or last poll
        /// </summary>
        public MiniProfiler Profiler { get; set; }

        public override async Task<int> PollAsync(bool force = false)
        {
            Interlocked.Increment(ref PollingEngine._activePolls);
            int result;
            if (CacheKey.HasValue())
            {
                result = await CachePollAsync(force).ConfigureAwait(false);
            }
            else
            {
                if (force) _needsPoll = true;
                result = await UpdateAsync().ConfigureAwait(false);
            }
            Interlocked.Decrement(ref PollingEngine._activePolls);
            return result;
        }

        private async Task<int> UpdateAsync()
        {
            PollStatus = "UpdateAsync";
            if (!_needsPoll && !IsStale) return 0;

            PollStatus = "Awaiting Semaphore";
            await _pollSemaphoreSlim.WaitAsync().ConfigureAwait(false);
            bool errored = false;
            try
            {
                if (!_needsPoll && !IsStale) return 0;
                if (_isPolling) return 0;
                CurrentPollDuration = Stopwatch.StartNew();
                _isPolling = true;
                Interlocked.Increment(ref _pollsTotal);
                PollStatus = "UpdateCache";
                await UpdateCache(this).ConfigureAwait(false);
                PollStatus = "UpdateCache Complete";
                _needsPoll = false;
                if (DataBacker != null)
                    Interlocked.Increment(ref _pollsSuccessful);
                return DataBacker != null ? 1 : 0;
            }
            catch (Exception e)
            {
                var errorMessage = e.Message;
{'fraction_non_alphanumeric': 0.08566515554166015, 'fraction_numerical': 0.0037517586368610288, 'mean_word_length': 1.586095392077607, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1850356', 'n_tokens_mistral': 1700, 'n_tokens_neox': 1620, 'n_words': 436}