<commit_before>﻿using BitSharp.Common.ExtensionMethods;
using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BitSharp.Common
{
    public abstract class Worker : IDisposable
    {
        public event Action OnNotifyWork;
        public event Action OnWorkStarted;
        public event Action OnWorkStopped;

        private readonly Logger logger;
        private readonly CancellationTokenSource shutdownToken;

        private readonly Thread workerThread;
        private readonly AutoResetEvent notifyEvent;
        private readonly AutoResetEvent forceNotifyEvent;
        private readonly ManualResetEventSlim idleEvent;

        private SemaphoreSlim semaphore;
        private readonly ManualResetEventSlim startedEvent;
        private bool isDisposed;

        public Worker(string name, bool initialNotify, TimeSpan minIdleTime, TimeSpan maxIdleTime, Logger logger)
        {
            this.logger = logger;
            this.Name = name;
            this.MinIdleTime = minIdleTime;
            this.MaxIdleTime = maxIdleTime;

            this.shutdownToken = new CancellationTokenSource();

            this.workerThread = new Thread(WorkerLoop);
            this.workerThread.Name = "Worker.{0}.WorkerLoop".Format2(name);

            this.notifyEvent = new AutoResetEvent(initialNotify);
            this.forceNotifyEvent = new AutoResetEvent(initialNotify);
            this.idleEvent = new ManualResetEventSlim(false);

            this.semaphore = new SemaphoreSlim(1);
            this.startedEvent = new ManualResetEventSlim(false);
            this.isDisposed = false;
        }

        public string Name { get; protected set; }

        public TimeSpan MinIdleTime { get; set; }

        public TimeSpan MaxIdleTime { get; set; }

        protected CancellationTokenSource ShutdownToken { get { return this.shutdownToken; } }

        public void Start()
        {
            this.semaphore.Do(() =>
            {
                if (!this.startedEvent.IsSet)
                {
                    this.SubStart();

                    this.workerThread.Start();
                    this.startedEvent.Set();
                }
            });
        }

        public void Stop()
        {
            this.semaphore.Do(() =>
            {
                if (this.startedEvent.IsSet)
                {
                    this.SubStop();

                    this.startedEvent.Reset();
                }
            });
        }

        public void Dispose()
        {
            var semaphoreLocal = this.semaphore;
            if (semaphoreLocal != null)
            {
                var wasDisposed = false;
                semaphoreLocal.Do(() =>
                {
                    if (!this.isDisposed)
                    {
                        this.SubDispose();

                        this.shutdownToken.Cancel();
                        this.notifyEvent.Set();
                        this.forceNotifyEvent.Set();

                        if (this.workerThread.IsAlive)
                            this.workerThread.Join(5000);

                        this.shutdownToken.Dispose();
                        this.notifyEvent.Dispose();
                        this.forceNotifyEvent.Dispose();
                        this.idleEvent.Dispose();
                        this.startedEvent.Dispose();

                        this.semaphore = null;
                        this.isDisposed = true;
                        wasDisposed = true;
                    }
                });

                if (wasDisposed)
                    semaphoreLocal.Dispose();
            }
        }

        public void NotifyWork()
        {
            if (this.isDisposed)
                return;

            this.notifyEvent.Set();

            var handler = this.OnNotifyWork;
            if (handler != null)
                handler();
        }

        public void ForceWork()
        {
            if (this.isDisposed)
                return;

            this.forceNotifyEvent.Set();
            this.notifyEvent.Set();

            var handler = this.OnNotifyWork;
            if (handler != null)
                handler();
        }

        public void ForceWorkAndWait()
        {
            if (this.isDisposed)
                return;

            // wait for worker to idle
            this.idleEvent.Wait();

            // reset its idle state
            this.idleEvent.Reset();

            // force an execution
            ForceWork();

            // wait for worker to be idle again
            this.idleEvent.Wait();
        }

        private void WorkerLoop()<commit_msg>don't wait in a worker that isn't started, it'll hang forever<commit_after>﻿using BitSharp.Common.ExtensionMethods;
using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BitSharp.Common
{
    public abstract class Worker : IDisposable
    {
        public event Action OnNotifyWork;
        public event Action OnWorkStarted;
        public event Action OnWorkStopped;

        private readonly Logger logger;
        private readonly CancellationTokenSource shutdownToken;

        private readonly Thread workerThread;
        private readonly AutoResetEvent notifyEvent;
        private readonly AutoResetEvent forceNotifyEvent;
        private readonly ManualResetEventSlim idleEvent;

        private SemaphoreSlim semaphore;
        private readonly ManualResetEventSlim startedEvent;
        private bool isDisposed;

        public Worker(string name, bool initialNotify, TimeSpan minIdleTime, TimeSpan maxIdleTime, Logger logger)
        {
            this.logger = logger;
            this.Name = name;
            this.MinIdleTime = minIdleTime;
            this.MaxIdleTime = maxIdleTime;

            this.shutdownToken = new CancellationTokenSource();

            this.workerThread = new Thread(WorkerLoop);
            this.workerThread.Name = "Worker.{0}.WorkerLoop".Format2(name);

            this.notifyEvent = new AutoResetEvent(initialNotify);
            this.forceNotifyEvent = new AutoResetEvent(initialNotify);
            this.idleEvent = new ManualResetEventSlim(false);

            this.semaphore = new SemaphoreSlim(1);
            this.startedEvent = new ManualResetEventSlim(false);
            this.isDisposed = false;
        }

        public string Name { get; protected set; }

        public TimeSpan MinIdleTime { get; set; }

        public TimeSpan MaxIdleTime { get; set; }

        protected CancellationTokenSource ShutdownToken { get { return this.shutdownToken; } }

        public void Start()
        {
            this.semaphore.Do(() =>
            {
                if (!this.startedEvent.IsSet)
                {
                    this.SubStart();

                    this.workerThread.Start();
                    this.startedEvent.Set();
                }
            });
        }

        public void Stop()
        {
            this.semaphore.Do(() =>
            {
                if (this.startedEvent.IsSet)
                {
                    this.SubStop();

                    this.startedEvent.Reset();
                }
            });
        }

        public void Dispose()
        {
            var semaphoreLocal = this.semaphore;
            if (semaphoreLocal != null)
            {
                var wasDisposed = false;
                semaphoreLocal.Do(() =>
                {
                    if (!this.isDisposed)
                    {
                        this.SubDispose();

                        this.shutdownToken.Cancel();
                        this.notifyEvent.Set();
                        this.forceNotifyEvent.Set();

                        if (this.workerThread.IsAlive)
                            this.workerThread.Join(5000);

                        this.shutdownToken.Dispose();
                        this.notifyEvent.Dispose();
                        this.forceNotifyEvent.Dispose();
                        this.idleEvent.Dispose();
                        this.startedEvent.Dispose();

                        this.semaphore = null;
                        this.isDisposed = true;
                        wasDisposed = true;
                    }
                });

                if (wasDisposed)
                    semaphoreLocal.Dispose();
            }
        }

        public void NotifyWork()
        {
            if (this.isDisposed)
                return;

            this.notifyEvent.Set();

            var handler = this.OnNotifyWork;
            if (handler != null)
                handler();
        }

        public void ForceWork()
        {
            if (this.isDisposed)
                return;

            this.forceNotifyEvent.Set();
            this.notifyEvent.Set();

            var handler = this.OnNotifyWork;
            if (handler != null)
                handler();
        }

        public void ForceWorkAndWait()
        {
            if (this.isDisposed)
                return;
            if (!this.startedEvent.IsSet)
                return;

            // wait for worker to idle
            this.idleEvent.Wait();

            // reset its idle state
            this.idleEvent.Reset();

            // force an execution
            ForceWork();

            // wait for worker to be idle again
            this.idleEvent.Wait();
        }

        private void WorkerLoop()
{'fraction_non_alphanumeric': 0.07956900124326564, 'fraction_numerical': 0.0014504765851636966, 'mean_word_length': 1.348661800486618, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 9, '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': '5686317', 'n_tokens_mistral': 2426, 'n_tokens_neox': 2276, 'n_words': 514}