< Summary

Information
Class: Ice.Internal.ThreadPoolMessage
Assembly: Ice
File(s): /_/csharp/src/Ice/Internal/ThreadPool.cs
Tag: 125_37167941578
Line coverage
100%
Covered lines: 20
Uncovered lines: 0
Coverable lines: 20
Total lines: 770
Line coverage: 100%
Branch coverage
100%
Covered branches: 6
Total branches: 6
Branch coverage: 100%
Method coverage
100%
Covered methods: 5
Fully covered methods: 5
Total methods: 5
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
startIOScope()100%11100%
finishIOScope()100%22100%
ioCompleted()100%22100%
Dispose()100%22100%

File(s)

/_/csharp/src/Ice/Internal/ThreadPool.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using System.Diagnostics;
 4
 5namespace Ice.Internal;
 6
 7public delegate void ThreadPoolWorkItem(ThreadPoolCurrent current);
 8
 9public delegate void AsyncCallback(object state);
 10
 11internal class ThreadPoolMessage : IDisposable
 12{
 113    public ThreadPoolMessage(ThreadPoolCurrent current, object mutex)
 14    {
 115        _current = current;
 116        _mutex = mutex;
 117        _finish = false;
 118        _finishWithIO = false;
 119    }
 20
 21    public bool startIOScope()
 22    {
 23        // This must be called with the handler locked.
 124        _finishWithIO = _current.startMessage();
 125        return _finishWithIO;
 26    }
 27
 28    public void finishIOScope()
 29    {
 130        if (_finishWithIO)
 31        {
 32            // This must be called with the handler locked.
 133            _current.finishMessage();
 34        }
 135    }
 36
 37    public void ioCompleted()
 38    {
 39        //
 40        // Call finishMessage once IO is completed only if serialization is not enabled.
 41        // Otherwise, finishMessage will be called when the event handler is done with
 42        // the message (it will be called from Dispose below).
 43        //
 44        Debug.Assert(_finishWithIO);
 145        if (_current.ioCompleted())
 46        {
 147            _finishWithIO = false;
 148            _finish = true;
 49        }
 150    }
 51
 52    public void Dispose()
 53    {
 154        if (_finish)
 55        {
 56            //
 57            // A ThreadPoolMessage instance must be created outside the synchronization of the event handler. We
 58            // need to lock the event handler here to call finishMessage.
 59            //
 160            lock (_mutex)
 61            {
 162                _current.finishMessage();
 163            }
 64        }
 165    }
 66
 67    private readonly ThreadPoolCurrent _current;
 68    private readonly object _mutex;
 69    private bool _finish;
 70    private bool _finishWithIO;
 71}
 72
 73public class ThreadPoolCurrent
 74{
 75    internal ThreadPoolCurrent(ThreadPool threadPool, ThreadPool.WorkerThread thread)
 76    {
 77        _threadPool = threadPool;
 78        _thread = thread;
 79    }
 80
 81    public int operation;
 82
 83    public bool ioCompleted() => _threadPool.ioCompleted(this);
 84
 85    public bool startMessage() => _threadPool.startMessage(this);
 86
 87    public void finishMessage() => _threadPool.finishMessage(this);
 88
 89    internal readonly ThreadPool _threadPool;
 90    internal readonly ThreadPool.WorkerThread _thread;
 91    internal bool _ioCompleted;
 92    internal EventHandler _handler;
 93}
 94
 95public sealed class ThreadPool : System.Threading.Tasks.TaskScheduler
 96{
 97    public ThreadPool(Instance instance, string prefix, int timeout)
 98    {
 99        Ice.Properties properties = instance.initializationData().properties;
 100
 101        _instance = instance;
 102        _executor = instance.initializationData().executor;
 103        _destroyed = false;
 104        _prefix = prefix;
 105        _threadIndex = 0;
 106        _inUse = 0;
 107        _serialize = properties.getPropertyAsInt(_prefix + ".Serialize") > 0;
 108        _serverIdleTime = timeout <= 0 ? Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(timeout);
 109
 110        string programName = properties.getIceProperty("Ice.ProgramName");
 111        if (programName.Length > 0)
 112        {
 113            _threadPrefix = programName + "-" + _prefix;
 114        }
 115        else
 116        {
 117            _threadPrefix = _prefix;
 118        }
 119
 120        int size = properties.getPropertyAsIntWithDefault(_prefix + ".Size", 1);
 121        if (size < 1)
 122        {
 123            string s = _prefix + ".Size < 1; Size adjusted to 1";
 124            _instance.initializationData().logger.warning(s);
 125            size = 1;
 126        }
 127
 128        int sizeMax = properties.getPropertyAsIntWithDefault(_prefix + ".SizeMax", size);
 129        if (sizeMax < size)
 130        {
 131            string s = _prefix + ".SizeMax < " + _prefix + ".Size; SizeMax adjusted to Size (" + size + ")";
 132            _instance.initializationData().logger.warning(s);
 133            sizeMax = size;
 134        }
 135
 136        int sizeWarn = properties.getPropertyAsInt(_prefix + ".SizeWarn");
 137        if (sizeWarn != 0 && sizeWarn < size)
 138        {
 139            string s = _prefix + ".SizeWarn < " + _prefix + ".Size; adjusted SizeWarn to Size (" + size + ")";
 140            _instance.initializationData().logger.warning(s);
 141            sizeWarn = size;
 142        }
 143        else if (sizeWarn > sizeMax)
 144        {
 145            string s = _prefix + ".SizeWarn > " + _prefix + ".SizeMax; adjusted SizeWarn to SizeMax ("
 146                + sizeMax + ")";
 147            _instance.initializationData().logger.warning(s);
 148            sizeWarn = sizeMax;
 149        }
 150
 151        int threadIdleTime = properties.getPropertyAsIntWithDefault(_prefix + ".ThreadIdleTime", 60);
 152        if (threadIdleTime < 0)
 153        {
 154            string s = _prefix + ".ThreadIdleTime < 0; ThreadIdleTime adjusted to 0";
 155            _instance.initializationData().logger.warning(s);
 156            threadIdleTime = 0;
 157        }
 158
 159        _size = size;
 160        _sizeMax = sizeMax;
 161        _sizeWarn = sizeWarn;
 162        _threadIdleTime = threadIdleTime <= 0 ? Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(threadIdleTime);
 163
 164        int stackSize = properties.getPropertyAsInt(_prefix + ".StackSize");
 165        if (stackSize < 0)
 166        {
 167            string s = _prefix + ".StackSize < 0; Size adjusted to OS default";
 168            _instance.initializationData().logger.warning(s);
 169            stackSize = 0;
 170        }
 171        _stackSize = stackSize;
 172
 173        _priority = properties.getProperty(_prefix + ".ThreadPriority").Length > 0 ?
 174            Util.getThreadPriorityProperty(properties, _prefix) :
 175            Util.getThreadPriorityProperty(properties, "Ice");
 176
 177        if (_instance.traceLevels().threadPool >= 1)
 178        {
 179            string s = "creating " + _prefix + ": Size = " + _size + ", SizeMax = " + _sizeMax + ", SizeWarn = " +
 180                       _sizeWarn;
 181            _instance.initializationData().logger.trace(_instance.traceLevels().threadPoolCat, s);
 182        }
 183
 184        _workItems = new Queue<ThreadPoolWorkItem>();
 185
 186        try
 187        {
 188            _threads = new List<WorkerThread>();
 189            for (int i = 0; i < _size; ++i)
 190            {
 191                var thread = new WorkerThread(this, _threadPrefix + "-" + _threadIndex++);
 192                thread.start(_priority);
 193                _threads.Add(thread);
 194            }
 195        }
 196        catch (System.Exception ex)
 197        {
 198            string s = "cannot create thread for `" + _prefix + "':\n" + ex;
 199            _instance.initializationData().logger.error(s);
 200
 201            destroy();
 202            joinWithAllThreads();
 203            throw;
 204        }
 205    }
 206
 207    public void destroy()
 208    {
 209        lock (_mutex)
 210        {
 211            if (_destroyed)
 212            {
 213                return;
 214            }
 215            _destroyed = true;
 216            Monitor.PulseAll(_mutex);
 217        }
 218    }
 219
 220    public void updateObservers()
 221    {
 222        lock (_mutex)
 223        {
 224            foreach (WorkerThread t in _threads)
 225            {
 226                t.updateObserver();
 227            }
 228        }
 229    }
 230
 231    public void initialize(EventHandler handler)
 232    {
 233        handler._ready = 0;
 234        handler._pending = 0;
 235        handler._started = 0;
 236        handler._finish = false;
 237        handler._hasMoreData = false;
 238        handler._registered = 0;
 239    }
 240
 241    public void register(EventHandler handler, int op) => update(handler, SocketOperation.None, op);
 242
 243    public void update(EventHandler handler, int remove, int add)
 244    {
 245        lock (_mutex)
 246        {
 247            Debug.Assert(!_destroyed);
 248
 249            // Don't remove what needs to be added
 250            remove &= ~add;
 251
 252            // Don't remove/add if already un-registered or registered
 253            remove &= handler._registered;
 254            add &= ~handler._registered;
 255            if (remove == add)
 256            {
 257                return;
 258            }
 259
 260            handler._registered &= ~remove;
 261            handler._registered |= add;
 262
 263            if ((add & SocketOperation.Read) != 0 && (handler._pending & SocketOperation.Read) == 0)
 264            {
 265                handler._pending |= SocketOperation.Read;
 266                queueReadyForIOHandler(handler, SocketOperation.Read);
 267            }
 268            else if ((add & SocketOperation.Write) != 0 && (handler._pending & SocketOperation.Write) == 0)
 269            {
 270                handler._pending |= SocketOperation.Write;
 271                queueReadyForIOHandler(handler, SocketOperation.Write);
 272            }
 273        }
 274    }
 275
 276    public void unregister(EventHandler handler, int op) => update(handler, op, SocketOperation.None);
 277
 278    public void finish(EventHandler handler)
 279    {
 280        lock (_mutex)
 281        {
 282            Debug.Assert(!_destroyed);
 283
 284            handler._registered = SocketOperation.None;
 285
 286            //
 287            // If there are no pending asynchronous operations, we can call finish on the handler now.
 288            //
 289            if (handler._pending == 0)
 290            {
 291                _workItems.Enqueue(current =>
 292                    {
 293                        current.operation = SocketOperation.None;
 294                        current._handler = handler;
 295                        handler.finished(current);
 296                    });
 297                Monitor.Pulse(_mutex);
 298            }
 299            else
 300            {
 301                handler._finish = true;
 302            }
 303        }
 304    }
 305
 306    public void executeFromThisThread(System.Action call, Ice.Connection connection)
 307    {
 308        if (_executor is not null)
 309        {
 310            try
 311            {
 312                _executor(call, connection);
 313            }
 314            catch (System.Exception ex)
 315            {
 316                if (_instance.initializationData().properties.getIcePropertyAsInt("Ice.Warn.Executor") > 0)
 317                {
 318                    _instance.initializationData().logger.warning($"executor exception:\n{ex}");
 319                }
 320            }
 321        }
 322        else
 323        {
 324            call();
 325        }
 326    }
 327
 328    public void execute(Action workItem, Ice.Connection connection)
 329    {
 330        lock (_mutex)
 331        {
 332            if (_destroyed)
 333            {
 334                throw new Ice.CommunicatorDestroyedException();
 335            }
 336            _workItems.Enqueue(current =>
 337                {
 338                    current.ioCompleted();
 339                    executeFromThisThread(workItem, connection);
 340                });
 341            Monitor.Pulse(_mutex);
 342        }
 343    }
 344
 345    public void joinWithAllThreads()
 346    {
 347        //
 348        // _threads is immutable after destroy() has been called, therefore no synchronization is needed.
 349        // (Synchronization wouldn't be possible here anyway, because otherwise the other threads would never
 350        // terminate.)
 351        //
 352        Debug.Assert(_destroyed);
 353        foreach (WorkerThread thread in _threads)
 354        {
 355            thread.join();
 356        }
 357    }
 358
 359    // A worker thread can exit only when both conditions hold: the thread idle time is finite (so an idle worker
 360    // can time out), and the pool can have more than one worker (so a worker is eligible to be reaped without
 361    // dropping the pool below its floor of 1). Used by ConnectionI.startAsync to decide whether async I/O initiated
 362    // on an Ice worker thread needs to be hopped onto a .NET ThreadPool thread to survive the worker's exit.
 363    public bool canShrink => _threadIdleTime != Timeout.InfiniteTimeSpan && _sizeMax > 1;
 364
 365    protected sealed override void QueueTask(
 366        System.Threading.Tasks.Task task) => execute(
 367            () => TryExecuteTask(task),
 368            null);
 369
 370    protected sealed override bool TryExecuteTaskInline(System.Threading.Tasks.Task task, bool taskWasPreviouslyQueued)
 371    {
 372        if (!taskWasPreviouslyQueued)
 373        {
 374            executeFromThisThread(() => TryExecuteTask(task), null);
 375            return true;
 376        }
 377        return false;
 378    }
 379
 380    protected sealed override bool TryDequeue(System.Threading.Tasks.Task task) => false;
 381
 382    protected sealed override IEnumerable<System.Threading.Tasks.Task> GetScheduledTasks() =>
 383        Array.Empty<System.Threading.Tasks.Task>();
 384
 385    private void queueReadyForIOHandler(EventHandler handler, int operation)
 386    {
 387        lock (_mutex)
 388        {
 389            Debug.Assert(!_destroyed);
 390            _workItems.Enqueue(current =>
 391                {
 392                    current._handler = handler;
 393                    current.operation = operation;
 394                    try
 395                    {
 396                        current._handler.message(current);
 397                    }
 398                    catch (System.Exception ex)
 399                    {
 400                        string s = "exception in `" + _prefix + "':\n" + ex + "\nevent handler: " +
 401                            current._handler.ToString();
 402                        _instance.initializationData().logger.error(s);
 403                    }
 404                });
 405            Monitor.Pulse(_mutex);
 406        }
 407    }
 408
 409    private void run(WorkerThread thread)
 410    {
 411        var current = new ThreadPoolCurrent(this, thread);
 412        while (true)
 413        {
 414            ThreadPoolWorkItem workItem = null;
 415            lock (_mutex)
 416            {
 417                while (_workItems.Count == 0)
 418                {
 419                    if (_destroyed)
 420                    {
 421                        return;
 422                    }
 423
 424                    if (_threadIdleTime != Timeout.InfiniteTimeSpan)
 425                    {
 426                        if (!Monitor.Wait(_mutex, _threadIdleTime) && _workItems.Count == 0) // If timeout
 427                        {
 428                            if (_destroyed)
 429                            {
 430                                return;
 431                            }
 432                            else if (_inUse < _threads.Count - 1)
 433                            {
 434                                if (_instance.traceLevels().threadPool >= 1)
 435                                {
 436                                    string s = "shrinking " + _prefix + ": Size=" + (_threads.Count - 1);
 437                                    _instance.initializationData().logger.trace(
 438                                        _instance.traceLevels().threadPoolCat, s);
 439                                }
 440
 441                                _threads.Remove(thread);
 442                                _workItems.Enqueue(c =>
 443                                        // No call to ioCompleted, this shouldn't block (and we don't want to cause
 444                                        // a new thread to be started).
 445                                        thread.join());
 446                                Monitor.Pulse(_mutex);
 447                                return;
 448                            }
 449                            else if (_inUse > 0)
 450                            {
 451                                //
 452                                // If this is the last idle thread but there are still other threads
 453                                // busy dispatching, we go back waiting with _threadIdleTime. We only
 454                                // wait with _serverIdleTime when there's only one thread left.
 455                                //
 456                                continue;
 457                            }
 458
 459                            Debug.Assert(_threads.Count == 1);
 460                            if (!Monitor.Wait(_mutex, _serverIdleTime) && !_destroyed)
 461                            {
 462                                _workItems.Enqueue(c =>
 463                                    {
 464                                        c.ioCompleted();
 465                                        try
 466                                        {
 467                                            _instance.objectAdapterFactory().shutdown();
 468                                        }
 469                                        catch (Ice.CommunicatorDestroyedException)
 470                                        {
 471                                        }
 472                                    });
 473                            }
 474                        }
 475                    }
 476                    else
 477                    {
 478                        Monitor.Wait(_mutex);
 479                    }
 480                }
 481
 482                Debug.Assert(_workItems.Count > 0);
 483                workItem = _workItems.Dequeue();
 484
 485                current._thread.setState(Ice.Instrumentation.ThreadState.ThreadStateInUseForIO);
 486                current._ioCompleted = false;
 487            }
 488
 489            try
 490            {
 491                workItem(current);
 492            }
 493            catch (System.Exception ex)
 494            {
 495                string s = "exception in `" + _prefix + "' while calling on work item:\n" + ex + '\n';
 496                _instance.initializationData().logger.error(s);
 497            }
 498
 499            lock (_mutex)
 500            {
 501                if (_sizeMax > 1 && current._ioCompleted)
 502                {
 503                    Debug.Assert(_inUse > 0);
 504                    --_inUse;
 505                }
 506                thread.setState(Ice.Instrumentation.ThreadState.ThreadStateIdle);
 507            }
 508        }
 509    }
 510
 511    public bool ioCompleted(ThreadPoolCurrent current)
 512    {
 513        lock (_mutex)
 514        {
 515            current._ioCompleted = true; // Set the IO completed flag to specify that ioCompleted() has been called.
 516
 517            current._thread.setState(Ice.Instrumentation.ThreadState.ThreadStateInUseForUser);
 518
 519            if (_sizeMax > 1)
 520            {
 521                Debug.Assert(_inUse >= 0);
 522                ++_inUse;
 523
 524                if (_sizeMax > 1 && _inUse == _sizeWarn)
 525                {
 526                    string s = "thread pool `" + _prefix + "' is running low on threads\n"
 527                        + "Size=" + _size + ", " + "SizeMax=" + _sizeMax + ", " + "SizeWarn=" + _sizeWarn;
 528                    _instance.initializationData().logger.warning(s);
 529                }
 530
 531                if (!_destroyed && _inUse < _sizeMax && _inUse == _threads.Count)
 532                {
 533                    if (_instance.traceLevels().threadPool >= 1)
 534                    {
 535                        string s = "growing " + _prefix + ": Size = " + (_threads.Count + 1);
 536                        _instance.initializationData().logger.trace(_instance.traceLevels().threadPoolCat, s);
 537                    }
 538
 539                    try
 540                    {
 541                        var t = new WorkerThread(this, _threadPrefix + "-" + _threadIndex++);
 542                        t.start(_priority);
 543                        _threads.Add(t);
 544                    }
 545                    catch (System.Exception ex)
 546                    {
 547                        string s = "cannot create thread for `" + _prefix + "':\n" + ex;
 548                        _instance.initializationData().logger.error(s);
 549                    }
 550                }
 551            }
 552        }
 553        return _sizeMax > 1 && _serialize;
 554    }
 555
 556    public bool startMessage(ThreadPoolCurrent current)
 557    {
 558        Debug.Assert((current._handler._pending & current.operation) != 0);
 559
 560        if ((current._handler._started & current.operation) != 0)
 561        {
 562            Debug.Assert((current._handler._ready & current.operation) == 0);
 563            current._handler._ready |= current.operation;
 564            current._handler._started &= ~current.operation;
 565            if (!current._handler.finishAsync(current.operation)) // Returns false if the handler is finished.
 566            {
 567                current._handler._pending &= ~current.operation;
 568                if (current._handler._pending == 0 && current._handler._finish)
 569                {
 570                    finish(current._handler);
 571                }
 572                return false;
 573            }
 574        }
 575        else if ((current._handler._ready & current.operation) == 0 &&
 576                (current._handler._registered & current.operation) != 0)
 577        {
 578            Debug.Assert((current._handler._started & current.operation) == 0);
 579            if (!current._handler.startAsync(current.operation, getCallback(current.operation)))
 580            {
 581                current._handler._pending &= ~current.operation;
 582                if (current._handler._pending == 0 && current._handler._finish)
 583                {
 584                    finish(current._handler);
 585                }
 586                return false;
 587            }
 588            else
 589            {
 590                current._handler._started |= current.operation;
 591                return false;
 592            }
 593        }
 594
 595        if ((current._handler._registered & current.operation) != 0)
 596        {
 597            Debug.Assert((current._handler._ready & current.operation) != 0);
 598            current._handler._ready &= ~current.operation;
 599            return true;
 600        }
 601        else
 602        {
 603            current._handler._pending &= ~current.operation;
 604            if (current._handler._pending == 0 && current._handler._finish)
 605            {
 606                finish(current._handler);
 607            }
 608            return false;
 609        }
 610    }
 611
 612    public void finishMessage(ThreadPoolCurrent current)
 613    {
 614        if ((current._handler._registered & current.operation) != 0)
 615        {
 616            Debug.Assert((current._handler._ready & current.operation) == 0);
 617            if (!current._handler.startAsync(current.operation, getCallback(current.operation)))
 618            {
 619                current._handler._pending &= ~current.operation;
 620            }
 621            else
 622            {
 623                Debug.Assert((current._handler._pending & current.operation) != 0);
 624                current._handler._started |= current.operation;
 625            }
 626        }
 627        else
 628        {
 629            current._handler._pending &= ~current.operation;
 630        }
 631
 632        if (current._handler._pending == 0 && current._handler._finish)
 633        {
 634            // There are no more pending async operations, it's time to call finish.
 635            finish(current._handler);
 636        }
 637    }
 638
 639    private AsyncCallback getCallback(int operation)
 640    {
 641        Debug.Assert(operation == SocketOperation.Read || operation == SocketOperation.Write);
 642        return state => queueReadyForIOHandler((EventHandler)state, operation);
 643    }
 644
 645    private readonly Instance _instance;
 646    private readonly System.Action<System.Action, Ice.Connection> _executor;
 647    private bool _destroyed;
 648    private readonly string _prefix;
 649    private readonly string _threadPrefix;
 650
 651    private readonly object _mutex = new object();
 652
 653    internal sealed class WorkerThread
 654    {
 655        private readonly ThreadPool _threadPool;
 656        private Ice.Instrumentation.ThreadObserver _observer;
 657        private Ice.Instrumentation.ThreadState _state;
 658
 659        internal WorkerThread(ThreadPool threadPool, string name)
 660        {
 661            _threadPool = threadPool;
 662            _name = name;
 663            _state = Ice.Instrumentation.ThreadState.ThreadStateIdle;
 664            updateObserver();
 665        }
 666
 667        public void updateObserver()
 668        {
 669            // Must be called with the thread pool mutex locked
 670            Ice.Instrumentation.CommunicatorObserver obsv = _threadPool._instance.initializationData().observer;
 671            if (obsv is not null)
 672            {
 673                _observer = obsv.getThreadObserver(_threadPool._prefix, _name, _state, _observer);
 674                _observer?.attach();
 675            }
 676        }
 677
 678        public void setState(Ice.Instrumentation.ThreadState s)
 679        {
 680            // Must be called with the thread pool mutex locked
 681            if (_observer is not null)
 682            {
 683                if (_state != s)
 684                {
 685                    _observer.stateChanged(_state, s);
 686                }
 687            }
 688            _state = s;
 689        }
 690
 691        public void join() => _thread.Join();
 692
 693        public void start(ThreadPriority priority)
 694        {
 695            if (_threadPool._stackSize == 0)
 696            {
 697                _thread = new Thread(new ThreadStart(Run));
 698            }
 699            else
 700            {
 701                _thread = new Thread(new ThreadStart(Run), _threadPool._stackSize);
 702            }
 703            _thread.IsBackground = true;
 704            _thread.Name = _name;
 705            _thread.Priority = priority;
 706            _thread.Start();
 707        }
 708
 709        public void Run()
 710        {
 711            if (_threadPool._instance.initializationData().threadStart is not null)
 712            {
 713                try
 714                {
 715                    _threadPool._instance.initializationData().threadStart();
 716                }
 717                catch (System.Exception ex)
 718                {
 719                    string s = "thread hook start() method raised an unexpected exception in `";
 720                    s += _threadPool._prefix + "' thread " + _thread.Name + ":\n" + ex;
 721                    _threadPool._instance.initializationData().logger.error(s);
 722                }
 723            }
 724
 725            try
 726            {
 727                _threadPool.run(this);
 728            }
 729            catch (System.Exception ex)
 730            {
 731                string s = "exception in `" + _threadPool._prefix + "' thread " + _thread.Name + ":\n" + ex;
 732                _threadPool._instance.initializationData().logger.error(s);
 733            }
 734
 735            _observer?.detach();
 736
 737            if (_threadPool._instance.initializationData().threadStop is not null)
 738            {
 739                try
 740                {
 741                    _threadPool._instance.initializationData().threadStop();
 742                }
 743                catch (System.Exception ex)
 744                {
 745                    string s = "thread hook stop() method raised an unexpected exception in `";
 746                    s += _threadPool._prefix + "' thread " + _thread.Name + ":\n" + ex;
 747                    _threadPool._instance.initializationData().logger.error(s);
 748                }
 749            }
 750        }
 751
 752        private readonly string _name;
 753        private Thread _thread;
 754    }
 755
 756    private readonly int _size; // Number of threads that are pre-created.
 757    private readonly int _sizeMax; // Maximum number of threads.
 758    private readonly int _sizeWarn; // If _inUse reaches _sizeWarn, a "low on threads" warning will be printed.
 759    private readonly bool _serialize; // True if requests need to be serialized over the connection.
 760    private readonly ThreadPriority _priority;
 761    private readonly TimeSpan _serverIdleTime;
 762    private readonly TimeSpan _threadIdleTime;
 763    private readonly int _stackSize;
 764
 765    private readonly List<WorkerThread> _threads; // All threads, running or not.
 766    private int _threadIndex; // For assigning thread names.
 767    private int _inUse; // Number of threads that are currently in use.
 768
 769    private readonly Queue<ThreadPoolWorkItem> _workItems;
 770}