<commit_before>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Lurchsoft.ParallelWorkshop.Ex08DiyReaderWriterLock;
using NUnit.Framework;

namespace Lurchsoft.ParallelWorkshopTests.Ex08DiyReaderWriterLock
{
    public class DiyReaderWriterLockTests
    {
        [Test]
        public void Lock_ShouldProtectThreadUnsafeCollectionAgainstUnsafeModification()
        {
            var state = new State(new DiyReaderWriterLock());
            var tasks = PrepareTasks(4, 50000, state, PerformReads).Concat(PrepareTasks(2, 10000, state, PerformWrites)).ToArray();

            Task.WaitAll(tasks); // will throw AggregateException if any task throws an exception
        }

        [Test]
        public void Lock_ShouldAllowMultipleSimultaneousReaders()
        {
            var state = new State(new DiyReaderWriterLock());
            var tasks = PrepareTasks(4, 5000, state, PerformReads).Concat(PrepareTasks(1, 1000, state, PerformWrites)).ToArray();
            
            Task.WaitAll(tasks);
            Assert.That(state.MaxSimultaneousReaders, Is.GreaterThan(1));
        }

        private IEnumerable<Task> PrepareTasks(int numTasks, int taskSize, State state, Action<int, State> action)
        {
            return Enumerable.Range(0, numTasks).Select(i => Task.Factory.StartNew(() => action(taskSize, state)));
        }

        private static void PerformWrites(int numWrites, State state)
        {
            for (int i = 0; i < numWrites; ++i)
            {
                state.Add(GetUniqueString(), GetUniqueString());
            }
        }

        private static void PerformReads(int numReads, State state)
        {
            for (int i = 0; i < numReads; ++i)
            {
                // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
                state.AllValues.ToList();
            }
        }

        private static string GetUniqueString()
        {
            return Guid.NewGuid().ToString("D");
        }

        private class State
        {
            private readonly Dictionary<string, string> values = new Dictionary<string, string>();
            private readonly IReaderWriterLock readerWriterLock;
            private int curSimultaneousReaders, maxSimultaneousReaders;

            public State(IReaderWriterLock readerWriterLock)
            {
                this.readerWriterLock = readerWriterLock;
            }

            public int MaxSimultaneousReaders
            {
                get { return maxSimultaneousReaders; }
            }

            public IEnumerable<string> AllValues
            {
                get
                {
                    int readers = Interlocked.Increment(ref curSimultaneousReaders);
                    Interlocked.CompareExchange(ref maxSimultaneousReaders, readers, readers - 1);
                    readerWriterLock.EnterReadLock();
                    try
                    {
                        return values.Values.ToList();
                    }
                    finally
                    {
                        readerWriterLock.ExitReadLock();
                        Interlocked.Decrement(ref curSimultaneousReaders);
                    }
                }
            }

            public void Add(string key, string value)
            {
                readerWriterLock.EnterWriteLock();
                try
                {
                    values[key] = value;
                }
                finally
                {
                    readerWriterLock.ExitWriteLock();
                }
            }
        }
    }
}<commit_msg>Simplify by using a List, not a Dictionary<commit_after>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Lurchsoft.ParallelWorkshop.Ex08DiyReaderWriterLock;
using NUnit.Framework;

namespace Lurchsoft.ParallelWorkshopTests.Ex08DiyReaderWriterLock
{
    public class DiyReaderWriterLockTests
    {
        [Test]
        public void Lock_ShouldProtectThreadUnsafeCollectionAgainstUnsafeModification()
        {
            var list = new ReaderWriterLockedList(new DiyReaderWriterLock());
            Task[] tasks = PrepareTasks(4, 20000, list, PerformReads).Concat(PrepareTasks(2, 6000, list, PerformWrites)).ToArray();

            Task.WaitAll(tasks); // will throw AggregateException if any task throws an exception
        }

        [Test]
        public void Lock_ShouldAllowMultipleSimultaneousReaders()
        {
            var list = new ReaderWriterLockedList(new DiyReaderWriterLock());
            Task[] tasks = PrepareTasks(4, 5000, list, PerformReads).Concat(PrepareTasks(1, 1000, list, PerformWrites)).ToArray();
            
            Task.WaitAll(tasks);
            Assert.That(list.MaxSimultaneousReaders, Is.GreaterThan(1));
        }

        private IEnumerable<Task> PrepareTasks(int numTasks, int taskSize, ReaderWriterLockedList state, Action<int, ReaderWriterLockedList> action)
        {
            return Enumerable.Range(0, numTasks).Select(i => Task.Factory.StartNew(() => action(taskSize, state)));
        }

        private static void PerformWrites(int numWrites, ReaderWriterLockedList state)
        {
            for (int i = 0; i < numWrites; ++i)
            {
                state.Add(Guid.NewGuid().ToString("D"));
            }
        }

        private static void PerformReads(int numReads, ReaderWriterLockedList state)
        {
            for (int i = 0; i < numReads; ++i)
            {
                state.GetAllValues();
            }
        }

        /// <summary>This wraps a <see cref="List{T}"/>, exposing one read and one write operation, protected by a reader-writer lock.</summary>
        private class ReaderWriterLockedList
        {
            private readonly List<string> values = new List<string>();

            private readonly IReaderWriterLock readerWriterLock;
            private int curSimultaneousReaders, maxSimultaneousReaders;

            public ReaderWriterLockedList(IReaderWriterLock readerWriterLock)
            {
                this.readerWriterLock = readerWriterLock;
            }

            public int MaxSimultaneousReaders
            {
                get { return maxSimultaneousReaders; }
            }

            public IEnumerable<string> GetAllValues()
            {
                int readers = Interlocked.Increment(ref curSimultaneousReaders);
                Interlocked.CompareExchange(ref maxSimultaneousReaders, readers, readers - 1);
                readerWriterLock.EnterReadLock();
                try
                {
                    return new List<string>(values); // snapshot
                }
                finally
                {
                    readerWriterLock.ExitReadLock();
                    Interlocked.Decrement(ref curSimultaneousReaders);
                }
            }

            public void Add(string value)
            {
                readerWriterLock.EnterWriteLock();
                try
                {
                    values.Add(value);
                }
                finally
                {
                    readerWriterLock.ExitWriteLock();
                }
            }
        }
    }
}
{'fraction_non_alphanumeric': 0.07904436860068259, 'fraction_numerical': 0.00832764505119454, 'mean_word_length': 1.734602463605823, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 21, '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': '2771354', 'n_tokens_mistral': 1911, 'n_tokens_neox': 1796, 'n_words': 387}