<commit_before>using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using JetBrains.Annotations;

namespace Lambda2Js
{
    /// <summary>
    /// Provides metadata about the objects that are going to be converted to JavaScript in some way.
    /// </summary>
    public class AttributeJavascriptMetadataProvider : JavascriptMetadataProvider
    {
        private IJavascriptMemberMetadata GetMemberMetadataNoCache([NotNull] MemberInfo memberInfo)
        {
            if (memberInfo == null)
                throw new ArgumentNullException(nameof(memberInfo));

            var attr0 = memberInfo
                .GetCustomAttributes(typeof(JavascriptMemberAttribute), true)
                .OfType<IJavascriptMemberMetadata>()
                .SingleOrDefault();

            if (attr0 != null)
                return attr0;

            var jsonAttr = memberInfo
                .GetCustomAttributes(true)
                .Where(a => a.GetType().Name == "JsonPropertyAttribute")
                .Select(ConvertJsonAttribute)
                .SingleOrDefault();

            if (jsonAttr != null)
                return jsonAttr;

            return new JavascriptMemberAttribute
            {
                MemberName = memberInfo.Name
            };
        }

        private readonly Dictionary<MemberInfo, IJavascriptMemberMetadata> cache
            = new Dictionary<MemberInfo, IJavascriptMemberMetadata>();

        private IJavascriptMemberMetadata GetMemberMetadataWithCache([NotNull] MemberInfo memberInfo)
        {
            // ReSharper disable InconsistentlySynchronizedField
            if (!cache.ContainsKey(memberInfo))
                lock (cache)
                    if (!cache.ContainsKey(memberInfo))
                    {
                        var meta = this.GetMemberMetadataNoCache(memberInfo);
                        Thread.MemoryBarrier();
                        cache[memberInfo] = meta;
                        return meta;
                    }

            return cache[memberInfo];
            // ReSharper restore InconsistentlySynchronizedField
        }

        /// <summary>
        /// Gets metadata about a property that is going to be used in JavaScript code.
        /// </summary>
        /// <param name="memberInfo"></param>
        /// <returns></returns>
        public override IJavascriptMemberMetadata GetMemberMetadata([NotNull] MemberInfo memberInfo)
        {
            return this.GetMemberMetadata(memberInfo, this.UseCache);
        }

        /// <summary>
        /// Gets or sets a value indicating whether to use cache by default.
        /// Applyes to `GetMemberMetadata` overload without `useCache` parameter.
        /// </summary>
        public bool UseCache { get; set; }

        /// <summary>
        /// Gets metadata about a property that is going to be used in JavaScript code.
        /// </summary>
        /// <param name="memberInfo"></param>
        /// <returns></returns>
        public IJavascriptMemberMetadata GetMemberMetadata([NotNull] MemberInfo memberInfo, bool useCache)
        {
            if (useCache)
                return this.GetMemberMetadataWithCache(memberInfo);

            return this.GetMemberMetadataNoCache(memberInfo);
        }

        private IJavascriptMemberMetadata ConvertJsonAttribute(object attr)
        {
            var type = attr.GetType();
            var accessor = GetAccessors(type);

            return new JavascriptMemberAttribute
            {
                MemberName = accessor.PropertyNameGetter?.Invoke(attr),
            };
        }

        class Accessors
        {
            public Func<object, string> PropertyNameGetter { get; set; }
        }

        private readonly Dictionary<Type, Accessors> accessors = new Dictionary<Type, Accessors>();

        private Accessors GetAccessors(Type type)
        {
            // ReSharper disable InconsistentlySynchronizedField
            if (!accessors.ContainsKey(type))
                lock (accessors)
                    if (!accessors.ContainsKey(type))
                    {
                        var accessor = new Accessors
                        {
                            PropertyNameGetter = type.GetProperty("PropertyName")?.MakeGetterDelegate<string>()
                        };
                        Thread.MemoryBarrier();
                        accessors[type] = accessor;
                        return accessor;
                    }

            return accessors[type];
            // ReSharper restore InconsistentlySynchronizedField
        }
    }
}<commit_msg>Fixing race condition in caches and moving to use double checked locking<commit_after>using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using JetBrains.Annotations;

namespace Lambda2Js
{
    /// <summary>
    /// Provides metadata about the objects that are going to be converted to JavaScript in some way.
    /// </summary>
    public class AttributeJavascriptMetadataProvider : JavascriptMetadataProvider
    {
        private IJavascriptMemberMetadata GetMemberMetadataNoCache([NotNull] MemberInfo memberInfo)
        {
            if (memberInfo == null)
                throw new ArgumentNullException(nameof(memberInfo));

            var attr0 = memberInfo
                .GetCustomAttributes(typeof(JavascriptMemberAttribute), true)
                .OfType<IJavascriptMemberMetadata>()
                .SingleOrDefault();

            if (attr0 != null)
                return attr0;

            var jsonAttr = memberInfo
                .GetCustomAttributes(true)
                .Where(a => a.GetType().Name == "JsonPropertyAttribute")
                .Select(ConvertJsonAttribute)
                .SingleOrDefault();

            if (jsonAttr != null)
                return jsonAttr;

            return new JavascriptMemberAttribute
            {
                MemberName = memberInfo.Name
            };
        }

        private Dictionary<MemberInfo, IJavascriptMemberMetadata> cache
            = new Dictionary<MemberInfo, IJavascriptMemberMetadata>();

        private IJavascriptMemberMetadata GetMemberMetadataWithCache([NotNull] MemberInfo memberInfo)
        {
            IJavascriptMemberMetadata value;
            if (cache.TryGetValue(memberInfo, out value))
                return value;

            lock (this)
            {
                if (cache.TryGetValue(memberInfo, out value))
                    return value;

                var meta = GetMemberMetadataNoCache(memberInfo);
                // have to create a new instance here, because readers don't have any
                // syncronization with writers.
                cache = new Dictionary<MemberInfo, IJavascriptMemberMetadata>(cache)
                    {
                        [memberInfo] = meta
                    };
                return meta;
            }
        }

        /// <summary>
        /// Gets metadata about a property that is going to be used in JavaScript code.
        /// </summary>
        /// <param name="memberInfo"></param>
        /// <returns></returns>
        public override IJavascriptMemberMetadata GetMemberMetadata([NotNull] MemberInfo memberInfo)
        {
            return this.GetMemberMetadata(memberInfo, this.UseCache);
        }

        /// <summary>
        /// Gets or sets a value indicating whether to use cache by default.
        /// Applyes to `GetMemberMetadata` overload without `useCache` parameter.
        /// </summary>
        public bool UseCache { get; set; }

        /// <summary>
        /// Gets metadata about a property that is going to be used in JavaScript code.
        /// </summary>
        /// <param name="memberInfo"></param>
        /// <returns></returns>
        public IJavascriptMemberMetadata GetMemberMetadata([NotNull] MemberInfo memberInfo, bool useCache)
        {
            if (useCache)
                return this.GetMemberMetadataWithCache(memberInfo);

            return this.GetMemberMetadataNoCache(memberInfo);
        }

        private IJavascriptMemberMetadata ConvertJsonAttribute(object attr)
        {
            var type = attr.GetType();
            var accessor = GetAccessors(type);

            return new JavascriptMemberAttribute
            {
                MemberName = accessor.PropertyNameGetter?.Invoke(attr),
            };
        }

        class Accessors
        {
            public Func<object, string> PropertyNameGetter { get; set; }
        }

        private Dictionary<Type, Accessors> accessors = new Dictionary<Type, Accessors>();

        private Accessors GetAccessors(Type type)
        {
            Accessors value;
            if (accessors.TryGetValue(type, out value))
                return value;
            lock (this)
            {
                if (accessors.TryGetValue(type, out value))
                    return value;
                var accessor = new Accessors
                {
                    PropertyNameGetter = type.GetTypeInfo().GetDeclaredProperty("PropertyName")?.MakeGetterDelegate<string>()
                };
                // have to create a new instance here, because readers don't have any
                // syncronization with writers.
                accessors = new Dictionary<Type, Accessors>(accessors)
                    {
                        [type]=accessor
                    };
                return accessor;
            }
        }
    }
}
{'fraction_non_alphanumeric': 0.0801670146137787, 'fraction_numerical': 0.0008350730688935282, 'mean_word_length': 1.6958356781091728, 'pattern_counts': {'":': 0, '<': 51, '<?xml version=': 0, '>': 53, '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': '4662129', 'n_tokens_mistral': 2259, 'n_tokens_neox': 2101, 'n_words': 566}