This is an automated email from the ASF dual-hosted git repository.

paulirwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git


The following commit(s) were added to refs/heads/master by this push:
     new b1d309fc6 Add unit tests for LimitedConcurrencyLevelTaskScheduler, 
#1110 (#1119)
b1d309fc6 is described below

commit b1d309fc657b2ae7bb20fa3e0ab5300351b52f17
Author: Paul Irwin <[email protected]>
AuthorDate: Wed Jan 22 13:27:04 2025 -0700

    Add unit tests for LimitedConcurrencyLevelTaskScheduler, #1110 (#1119)
    
    * Add unit tests for LimitedConcurrencyLevelTaskScheduler, #1110
    
    * Move TaskState into Extensions class and make private, #1110
---
 Directory.Build.targets                            |   1 -
 .../Support/Threading/JSR166TestCase.cs            | 344 +++++++++++++++------
 .../TestLimitedConcurrencyLevelTaskScheduler.cs    | 237 ++++++++++++++
 .../LimitedConcurrencyLevelTaskScheduler.cs        |  98 +++---
 4 files changed, 535 insertions(+), 145 deletions(-)

diff --git a/Directory.Build.targets b/Directory.Build.targets
index d08b29eb3..85fdcc8f0 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -112,7 +112,6 @@
     
<DefineConstants>$(DefineConstants);FEATURE_ASSEMBLY_GETCALLINGASSEMBLY</DefineConstants>
     
<DefineConstants>$(DefineConstants);FEATURE_FILESTREAM_LOCK</DefineConstants>
     
<DefineConstants>$(DefineConstants);FEATURE_TEXTWRITER_CLOSE</DefineConstants>
-    
<DefineConstants>$(DefineConstants);FEATURE_THREADPOOL_UNSAFEQUEUEWORKITEM</DefineConstants>
     
<DefineConstants>$(DefineConstants);FEATURE_TYPE_GETMETHOD__BINDINGFLAGS_PARAMS</DefineConstants>
 
   </PropertyGroup>
diff --git a/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs 
b/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs
index 990b2cd35..e892d8854 100644
--- a/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs
+++ b/src/Lucene.Net.Tests/Support/Threading/JSR166TestCase.cs
@@ -1,5 +1,15 @@
-using Lucene.Net.Util;
+// From Apache Harmony tests:
+// 
https://github.com/apache/harmony/blob/trunk/classlib/modules/concurrent/src/test/java/JSR166TestCase.java
+using Lucene.Net.Util;
 using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using ThreadInterruptedException = System.Threading.ThreadInterruptedException;
+
+#nullable enable
 
 namespace Lucene.Net.Support.Threading
 {
@@ -20,82 +30,90 @@ namespace Lucene.Net.Support.Threading
      * limitations under the License.
      */
 
-    /**
-     * Base class for JSR166 Junit TCK tests.  Defines some constants,
-     * utility methods and classes, as well as a simple framework for
-     * helping to make sure that assertions failing in generated threads
-     * cause the associated test that generated them to itself fail (which
-     * JUnit does not otherwise arrange).  The rules for creating such
-     * tests are:
-     *
-     * <ol>
-     *
-     * <li> All assertions in code running in generated threads must use
-     * the forms {@link #threadFail}, {@link #threadAssertTrue}, {@link
-     * #threadAssertEquals}, or {@link #threadAssertNull}, (not
-     * <tt>fail</tt>, <tt>assertTrue</tt>, etc.) It is OK (but not
-     * particularly recommended) for other code to use these forms too.
-     * Only the most typically used JUnit assertion methods are defined
-     * this way, but enough to live with.</li>
-     *
-     * <li> If you override {@link #setUp} or {@link #tearDown}, make sure
-     * to invoke <tt>super.setUp</tt> and <tt>super.tearDown</tt> within
-     * them. These methods are used to clear and check for thread
-     * assertion failures.</li>
-     *
-     * <li>All delays and timeouts must use one of the constants <tt>
-     * SHORT_DELAY_MS</tt>, <tt> SMALL_DELAY_MS</tt>, <tt> 
MEDIUM_DELAY_MS</tt>,
-     * <tt> LONG_DELAY_MS</tt>. The idea here is that a SHORT is always
-     * discriminable from zero time, and always allows enough time for the
-     * small amounts of computation (creating a thread, calling a few
-     * methods, etc) needed to reach a timeout point. Similarly, a SMALL
-     * is always discriminable as larger than SHORT and smaller than
-     * MEDIUM.  And so on. These constants are set to conservative values,
-     * but even so, if there is ever any doubt, they can all be increased
-     * in one spot to rerun tests on slower platforms.</li>
-     *
-     * <li> All threads generated must be joined inside each test case
-     * method (or <tt>fail</tt> to do so) before returning from the
-     * method. The <tt> joinPool</tt> method can be used to do this when
-     * using Executors.</li>
-     *
-     * </ol>
-     *
-     * <p> <b>Other notes</b>
-     * <ul>
-     *
-     * <li> Usually, there is one testcase method per JSR166 method
-     * covering "normal" operation, and then as many exception-testing
-     * methods as there are exceptions the method can throw. Sometimes
-     * there are multiple tests per JSR166 method when the different
-     * "normal" behaviors differ significantly. And sometimes testcases
-     * cover multiple methods when they cannot be tested in
-     * isolation.</li>
-     *
-     * <li> The documentation style for testcases is to provide as javadoc
-     * a simple sentence or two describing the property that the testcase
-     * method purports to test. The javadocs do not say anything about how
-     * the property is tested. To find out, read the code.</li>
-     *
-     * <li> These tests are "conformance tests", and do not attempt to
-     * test throughput, latency, scalability or other performance factors
-     * (see the separate "jtreg" tests for a set intended to check these
-     * for the most central aspects of functionality.) So, most tests use
-     * the smallest sensible numbers of threads, collection sizes, etc
-     * needed to check basic conformance.</li>
-     *
-     * <li>The test classes currently do not declare inclusion in
-     * any particular package to simplify things for people integrating
-     * them in TCK test suites.</li>
-     *
-     * <li> As a convenience, the <tt>main</tt> of this class (JSR166TestCase)
-     * runs all JSR166 unit tests.</li>
-     *
-     * </ul>
-     */
+    /// <summary>
+    /// LUCENENET NOTE: This class has been adapted from the Apache Harmony
+    /// tests. The original javadoc is included below, and adapted where 
necessary.
+    /// <para />
+    ///
+    /// Base class for JSR166 Junit TCK tests.  Defines some constants,
+    /// utility methods and classes, as well as a simple framework for
+    /// helping to make sure that assertions failing in generated threads
+    /// cause the associated test that generated them to itself fail (which
+    /// JUnit does not otherwise arrange).  The rules for creating such
+    /// tests are:
+    ///
+    /// <list type="bullets">
+    ///
+    /// <item> All assertions in code running in generated threads must use
+    /// the forms <see cref="threadFail"/>, <see cref="threadAssertTrue"/>,
+    /// <see cref="threadAssertEquals(long,long)"/>, <see 
cref="threadAssertEquals(object,object)"/>
+    /// or <see cref="threadAssertNull"/>, (not
+    /// <c>fail</c>, <c>assertTrue</c>, etc.) It is OK (but not
+    /// particularly recommended) for other code to use these forms too.
+    /// Only the most typically used JUnit assertion methods are defined
+    /// this way, but enough to live with.</item>
+    ///
+    /// <item> If you override <see cref="SetUp"/> or <see cref="TearDown"/>, 
make sure
+    /// to invoke <c>base.SetUp</c> and <c>base.TearDown</c> within
+    /// them. These methods are used to clear and check for thread
+    /// assertion failures.</item>
+    ///
+    /// <item>All delays and timeouts must use one of the constants
+    /// <see cref="SHORT_DELAY_MS"/>, <see cref="SMALL_DELAY_MS"/>, <see 
cref="MEDIUM_DELAY_MS"/>,
+    /// <see cref="LONG_DELAY_MS"/>. The idea here is that a SHORT is always
+    /// discriminable from zero time, and always allows enough time for the
+    /// small amounts of computation (creating a thread, calling a few
+    /// methods, etc) needed to reach a timeout point. Similarly, a SMALL
+    /// is always discriminable as larger than SHORT and smaller than
+    /// MEDIUM.  And so on. These constants are set to conservative values,
+    /// but even so, if there is ever any doubt, they can all be increased
+    /// in one spot to rerun tests on slower platforms.</item>
+    ///
+    /// <item> All threads generated must be joined inside each test case
+    /// method (or <c>fail</c> to do so) before returning from the
+    /// method. The <see cref="joinPool"/> method can be used to do this when
+    /// using Executors.</item>
+    ///
+    /// </list>
+    ///
+    /// <para />
+    /// <b>Other notes</b>
+    /// <list type="bullet">
+    ///
+    /// <item> Usually, there is one testcase method per JSR166 method
+    /// covering "normal" operation, and then as many exception-testing
+    /// methods as there are exceptions the method can throw. Sometimes
+    /// there are multiple tests per JSR166 method when the different
+    /// "normal" behaviors differ significantly. And sometimes testcases
+    /// cover multiple methods when they cannot be tested in
+    /// isolation.</item>
+    ///
+    /// <item> The documentation style for testcases is to provide as javadoc
+    /// a simple sentence or two describing the property that the testcase
+    /// method purports to test. The javadocs do not say anything about how
+    /// the property is tested. To find out, read the code.</item>
+    ///
+    /// <item> These tests are "conformance tests", and do not attempt to
+    /// test throughput, latency, scalability or other performance factors
+    /// (see the separate "jtreg" tests for a set intended to check these
+    /// for the most central aspects of functionality.) So, most tests use
+    /// the smallest sensible numbers of threads, collection sizes, etc
+    /// needed to check basic conformance.</item>
+    ///
+    /// <item>The test classes currently do not declare inclusion in
+    /// any particular package to simplify things for people integrating
+    /// them in TCK test suites.</item>
+    ///
+    /// <!-- LUCENENET: not implemented
+    /// <item> As a convenience, the <c>main</c> of this class (JSR166TestCase)
+    /// runs all JSR166 unit tests.</item>
+    /// -->
+    ///
+    /// </list>
+    /// </summary>
     public class JSR166TestCase : LuceneTestCase
     {
-        ///**
+        // /**
         // * Runs all JSR166 unit tests using junit.textui.TestRunner
         // */
         //public static void main(String[] args)
@@ -255,7 +273,7 @@ public void threadAssertFalse(bool b)
          * If argument not null, set status to indicate current testcase
          * should fail
          */
-        public void threadAssertNull(object x)
+        public void threadAssertNull(object? x)
         {
             if (x != null)
             {
@@ -281,7 +299,7 @@ public void threadAssertEquals(long x, long y)
          * If arguments not equal, set status to indicate current testcase
          * should fail
          */
-        public void threadAssertEquals(object x, object y)
+        public void threadAssertEquals(object? x, object? y)
         {
             if (x != y && (x == null || !x.equals(y)))
             {
@@ -326,25 +344,25 @@ public void threadUnexpectedException(Exception ex)
             fail("Unexpected exception: " + ex);
         }
 
-        ///**
-        // * Wait out termination of a thread pool or fail doing so
-        // */
-        //public void joinPool(ExecutorService exec)
-        //{
-        //    try
-        //    {
-        //        exec.shutdown();
-        //        assertTrue(exec.awaitTermination(LONG_DELAY_MS, 
TimeUnit.MILLISECONDS));
-        //    }
-        //    catch (SecurityException ok)
-        //    {
-        //        // Allowed in case test doesn't have privs
-        //    }
-        //    catch (InterruptedException ie)
-        //    {
-        //        fail("Unexpected exception");
-        //    }
-        //}
+        /**
+         * Wait out termination of a thread pool or fail doing so
+         */
+        public void joinPool(TaskScheduler exec)
+        {
+            try
+            {
+                exec.Shutdown();
+                
assertTrue(exec.AwaitTermination(TimeSpan.FromMilliseconds(LONG_DELAY_MS)));
+            }
+            // catch (SecurityException ok) // LUCENENET - not needed
+            // {
+            //     // Allowed in case test doesn't have privs
+            // }
+            catch (ThreadInterruptedException /*ie*/)
+            {
+                fail("Unexpected exception");
+            }
+        }
 
 
         /**
@@ -363,7 +381,141 @@ public void unexpectedException()
             fail("Unexpected exception");
         }
 
+        internal void ShortRunnable()
+        {
+            try
+            {
+                Thread.Sleep(SHORT_DELAY_MS);
+            }
+            catch (Exception e)
+            {
+                threadUnexpectedException(e);
+            }
+        }
+
+        internal void MediumRunnable()
+        {
+            try
+            {
+                Thread.Sleep(MEDIUM_DELAY_MS);
+            }
+            catch (Exception e)
+            {
+                threadUnexpectedException(e);
+            }
+        }
 
         // LUCENENET TODO: Complete port
     }
+
+    /// <summary>
+    /// LUCENENET specific - fake support for an API that feels like 
ThreadPoolExecutor.
+    /// </summary>
+    internal static class JSR166TestCaseExtensions
+    {
+        /// <summary>
+        /// LUCENENET specific - state to keep track of tasks.
+        /// <see cref="LimitedConcurrencyLevelTaskScheduler"/> removes tasks 
from the list when they complete,
+        /// so this class is needed to keep track of them.
+        /// </summary>
+        private class TaskState
+        {
+            private readonly TaskFactory _factory;
+            private readonly List<Task> _tasks = new();
+
+            public TaskState(TaskScheduler scheduler)
+            {
+                _factory = new TaskFactory(scheduler);
+            }
+
+            public void NewTask(Action action)
+            {
+                var task = _factory.StartNew(action);
+                _tasks.Add(task);
+            }
+
+            public int ActiveCount => _tasks.Count(t => t.Status == 
TaskStatus.Running);
+
+            public int CompletedCount => _tasks.Count(t => t.IsCompleted);
+
+            public int TaskCount => _tasks.Count;
+
+            public bool AllCompleted => _tasks.All(t => t.IsCompleted);
+
+            public bool JoinAll(TimeSpan timeout) => 
Task.WhenAll(_tasks).Wait(timeout);
+        }
+
+        private static readonly ConditionalWeakTable<TaskScheduler, TaskState> 
_taskFactories = new();
+
+        public static void Execute(this TaskScheduler scheduler, Action action)
+        {
+            if (!_taskFactories.TryGetValue(scheduler, out TaskState? state))
+            {
+                state = new TaskState(scheduler);
+                _taskFactories.Add(scheduler, state);
+            }
+
+            state.NewTask(action);
+        }
+
+        public static bool AwaitTermination(this TaskScheduler scheduler, 
TimeSpan timeout)
+        {
+            if (_taskFactories.TryGetValue(scheduler, out TaskState? state))
+            {
+                return state.JoinAll(timeout);
+            }
+
+            return true;
+        }
+
+        public static int GetActiveCount(this TaskScheduler scheduler)
+        {
+            if (_taskFactories.TryGetValue(scheduler, out TaskState? state))
+            {
+                // Approximate the number of running threads, which shouldn't 
exceed the concurrency level
+                return Math.Min(scheduler.MaximumConcurrencyLevel, 
state.ActiveCount);
+            }
+
+            return 0;
+        }
+
+        public static int GetCompletedTaskCount(this TaskScheduler scheduler)
+        {
+            if (_taskFactories.TryGetValue(scheduler, out TaskState? state))
+            {
+                return state.CompletedCount;
+            }
+
+            return 0;
+        }
+
+        public static int GetTaskCount(this TaskScheduler scheduler)
+        {
+            if (_taskFactories.TryGetValue(scheduler, out TaskState? state))
+            {
+                return state.TaskCount;
+            }
+
+            return 0;
+        }
+
+        public static void Shutdown(this TaskScheduler scheduler)
+        {
+            if (scheduler is LimitedConcurrencyLevelTaskScheduler lcl)
+            {
+                lcl.Shutdown();
+            }
+        }
+
+        public static bool IsTerminated(this TaskScheduler scheduler)
+        {
+            if (scheduler is LimitedConcurrencyLevelTaskScheduler lcl
+                && _taskFactories.TryGetValue(scheduler, out TaskState? state))
+            {
+                return lcl.IsShutdown && state.AllCompleted;
+            }
+
+            return false; // can't be shut down, so can't be terminated
+        }
+    }
 }
diff --git 
a/src/Lucene.Net.Tests/Support/Threading/TestLimitedConcurrencyLevelTaskScheduler.cs
 
b/src/Lucene.Net.Tests/Support/Threading/TestLimitedConcurrencyLevelTaskScheduler.cs
new file mode 100644
index 000000000..2952ce259
--- /dev/null
+++ 
b/src/Lucene.Net.Tests/Support/Threading/TestLimitedConcurrencyLevelTaskScheduler.cs
@@ -0,0 +1,237 @@
+// Based on tests from Apache Harmony:
+// 
https://github.com/apache/harmony/blob/02970cb7227a335edd2c8457ebdde0195a735733/classlib/modules/concurrent/src/test/java/ThreadPoolExecutorTest.java
+
+using NUnit.Framework;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+#nullable enable
+
+namespace Lucene.Net.Support.Threading
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+    /// <summary>
+    /// Tests for <see cref="LimitedConcurrencyLevelTaskScheduler"/>.
+    /// Adapted from Apache Harmony test suite <c>ThreadPoolExecutorTest</c>.
+    /// </summary>
+    [TestFixture]
+    public class TestLimitedConcurrencyLevelTaskScheduler : JSR166TestCase
+    {
+        /// <summary>
+        /// execute successfully executes a runnable
+        /// </summary>
+        /// <remarks>
+        /// LUCENENET Note: Execute is provided in <see 
cref="JSR166TestCaseExtensions"/>
+        /// to emulate the behavior of the Java method; it is not in the public
+        /// API for <see cref="LimitedConcurrencyLevelTaskScheduler"/>. This
+        /// just helps ensure the class is working as expected.
+        /// </remarks>
+        [Test]
+        public void TestExecute()
+        {
+            TaskScheduler p1 = new LimitedConcurrencyLevelTaskScheduler(1);
+
+            try
+            {
+                p1.Execute(() =>
+                {
+                    try
+                    {
+                        Thread.Sleep(SHORT_DELAY_MS);
+                    }
+                    catch (ThreadInterruptedException /*e*/)
+                    {
+                        threadUnexpectedException();
+                    }
+                });
+                Thread.Sleep(SMALL_DELAY_MS);
+            }
+            catch (ThreadInterruptedException /*e*/)
+            {
+                unexpectedException();
+            }
+
+            joinPool(p1);
+        }
+
+        /// <summary>
+        /// getActiveCount increases but doesn't overestimate, when a
+        /// thread becomes active
+        /// </summary>
+        /// <remarks>
+        /// LUCENENET Note: GetActiveCount is provided in <see 
cref="JSR166TestCaseExtensions"/>
+        /// to emulate the behavior of the Java method; it is not in the public
+        /// API for <see cref="LimitedConcurrencyLevelTaskScheduler"/>. This
+        /// just helps ensure the class is working as expected.
+        /// </remarks>
+        [Test]
+        public void TestGetActiveCount()
+        {
+            TaskScheduler p2 = new LimitedConcurrencyLevelTaskScheduler(2);
+            assertEquals(0, p2.GetActiveCount());
+            p2.Execute(MediumRunnable);
+
+            try
+            {
+                Thread.Sleep(SHORT_DELAY_MS);
+            }
+            catch (Exception /*e*/)
+            {
+                unexpectedException();
+            }
+
+            assertEquals(1, p2.GetActiveCount());
+            joinPool(p2);
+        }
+
+        // LUCENENET NOTE: testPrestartCoreThread and 
testPrestartAllCoreThreads omitted; they are not relevant
+
+        /// <summary>
+        /// getCompletedTaskCount increases, but doesn't overestimate,
+        /// when tasks complete
+        /// </summary>
+        /// <remarks>
+        /// LUCENENET Note: GetCompletedTaskCount is provided in <see 
cref="JSR166TestCaseExtensions"/>
+        /// to emulate the behavior of the Java method; it is not in the public
+        /// API for <see cref="LimitedConcurrencyLevelTaskScheduler"/>. This
+        /// just helps ensure the class is working as expected.
+        /// </remarks>
+        [Test]
+        public void TestGetCompletedTaskCount()
+        {
+            TaskScheduler p2 = new LimitedConcurrencyLevelTaskScheduler(2);
+            assertEquals(0, p2.GetCompletedTaskCount());
+            p2.Execute(ShortRunnable);
+
+            try
+            {
+                Thread.Sleep(SMALL_DELAY_MS);
+            }
+            catch (Exception /*e*/)
+            {
+                unexpectedException();
+            }
+
+            assertEquals(1, p2.GetCompletedTaskCount());
+            // LUCENENET NOTE: not catching SecurityException because that's 
not relevant here
+            p2.Shutdown();
+            joinPool(p2);
+        }
+
+        /// <summary>
+        /// Tests <see 
cref="LimitedConcurrencyLevelTaskScheduler.MaximumConcurrencyLevel"/>
+        /// returns size given in constructor if not otherwise set
+        /// </summary>
+        /// <remarks>
+        /// LUCENENET Note: this is equivalent to the 
<c>testGetCorePoolSize</c> or
+        /// <c>testGetMaximumPoolSize</c> methods in the Harmony tests, but we 
don't
+        /// have the same concepts or distinction, so just testing to make
+        /// sure that the maximum concurrency level is set correctly.
+        /// </remarks>
+        [Test]
+        public void TestMaximumConcurrencyLevel()
+        {
+            TaskScheduler p1 = new LimitedConcurrencyLevelTaskScheduler(1);
+            assertEquals(1, p1.MaximumConcurrencyLevel);
+            joinPool(p1);
+        }
+
+        // LUCENENET NOTE: testGetKeepAliveTime, testGetThreadFactory, 
testSetThreadFactory,
+        // testSetThreadFactoryNull, testGetRejectedExecutionHandler, 
testSetRejectedExecutionHandler,
+        // testSetRejectedExecutionHandlerNull, testGetLargestPoolSize, and 
testGetPoolSize omitted; they are not relevant
+
+        /// <summary>
+        /// getTaskCount increases, but doesn't overestimate, when tasks 
submitted
+        /// </summary>
+        /// <remarks>
+        /// LUCENENET Note: GetTaskCount is provided in <see 
cref="JSR166TestCaseExtensions"/>
+        /// to emulate the behavior of the Java method; it is not in the public
+        /// API for <see cref="LimitedConcurrencyLevelTaskScheduler"/>. This
+        /// just helps ensure the class is working as expected.
+        /// </remarks>
+        [Test]
+        public void TestGetTaskCount()
+        {
+            TaskScheduler p1 = new LimitedConcurrencyLevelTaskScheduler(1);
+
+            try
+            {
+                assertEquals(0, p1.GetTaskCount());
+                p1.Execute(MediumRunnable);
+                Thread.Sleep(SHORT_DELAY_MS);
+                assertEquals(1, p1.GetTaskCount());
+            }
+            catch (Exception /*e*/)
+            {
+                unexpectedException();
+            }
+
+            joinPool(p1);
+        }
+
+        /// <summary>
+        /// <see cref="LimitedConcurrencyLevelTaskScheduler.IsShutdown"/> is 
false before shutdown, true after
+        /// </summary>
+        [Test]
+        public void TestIsShutdown()
+        {
+            var p1 = new LimitedConcurrencyLevelTaskScheduler(1);
+            assertFalse(p1.IsShutdown);
+            p1.Shutdown(); // LUCENENET NOTE: not catching SecurityException 
because that's not relevant here
+            assertTrue(p1.IsShutdown);
+            joinPool(p1);
+        }
+
+        /// <summary>
+        /// isTerminated is false before termination, true after
+        /// </summary>
+        /// <remarks>
+        /// LUCENENET Note: IsTerminated is provided in <see 
cref="JSR166TestCaseExtensions"/>
+        /// to emulate the behavior of the Java method; it is not in the public
+        /// API for <see cref="LimitedConcurrencyLevelTaskScheduler"/>. This
+        /// just helps ensure the class is working as expected.
+        /// </remarks>
+        [Test]
+        public void TestIsTerminated()
+        {
+            TaskScheduler p1 = new LimitedConcurrencyLevelTaskScheduler(1);
+            assertFalse(p1.IsTerminated());
+
+            try
+            {
+                p1.Execute(MediumRunnable);
+            }
+            finally
+            {
+                p1.Shutdown(); // LUCENENET NOTE: not catching 
SecurityException because that's not relevant here
+            }
+
+            try
+            {
+                
assertTrue(p1.AwaitTermination(TimeSpan.FromMilliseconds(LONG_DELAY_MS)));
+                assertTrue(p1.IsTerminated());
+            }
+            catch (Exception /*e*/)
+            {
+                unexpectedException();
+            }
+        }
+
+        // LUCENENET NOTE: remainder of methods omitted, could be added as 
needed.
+    }
+}
diff --git 
a/src/Lucene.Net/Support/Threading/LimitedConcurrencyLevelTaskScheduler.cs 
b/src/Lucene.Net/Support/Threading/LimitedConcurrencyLevelTaskScheduler.cs
index 0717e957f..07b236cea 100644
--- a/src/Lucene.Net/Support/Threading/LimitedConcurrencyLevelTaskScheduler.cs
+++ b/src/Lucene.Net/Support/Threading/LimitedConcurrencyLevelTaskScheduler.cs
@@ -1,50 +1,50 @@
 /*
 MICROSOFT LIMITED PUBLIC LICENSE version 1.1
-This license governs use of code marked as "sample" or "example" available on 
this web site 
-without a license agreement, as provided under the section above titled 
-"NOTICE SPECIFIC TO SOFTWARE AVAILABLE ON THIS WEB SITE." If you use such 
-code (the "software"), you accept this license. If you do not accept the 
+This license governs use of code marked as "sample" or "example" available on 
this web site
+without a license agreement, as provided under the section above titled
+"NOTICE SPECIFIC TO SOFTWARE AVAILABLE ON THIS WEB SITE." If you use such
+code (the "software"), you accept this license. If you do not accept the
 license, do not use the software.
 
 1. Definitions
-The terms "reproduce," "reproduction," "derivative works," and "distribution" 
have the 
+The terms "reproduce," "reproduction," "derivative works," and "distribution" 
have the
 same meaning here as under U.S. copyright law.
 A "contribution" is the original software, or any additions or changes to the 
software.
 A "contributor" is any person that distributes its contribution under this 
license.
 "Licensed patents" are a contributor’s patent claims that read directly on its 
contribution.
 
 2. Grant of Rights
-(A) Copyright Grant - Subject to the terms of this license, including the 
license conditions 
-and limitations in section 3, each contributor grants you a non-exclusive, 
worldwide, 
-royalty-free copyright license to reproduce its contribution, prepare 
derivative works 
+(A) Copyright Grant - Subject to the terms of this license, including the 
license conditions
+and limitations in section 3, each contributor grants you a non-exclusive, 
worldwide,
+royalty-free copyright license to reproduce its contribution, prepare 
derivative works
 of its contribution, and distribute its contribution or any derivative works 
that you create.
-(B) Patent Grant - Subject to the terms of this license, including the license 
conditions 
-and limitations in section 3, each contributor grants you a non-exclusive, 
worldwide, 
-royalty-free license under its licensed patents to make, have made, use, sell, 
-offer for sale, import, and/or otherwise dispose of its contribution in the 
+(B) Patent Grant - Subject to the terms of this license, including the license 
conditions
+and limitations in section 3, each contributor grants you a non-exclusive, 
worldwide,
+royalty-free license under its licensed patents to make, have made, use, sell,
+offer for sale, import, and/or otherwise dispose of its contribution in the
 software or derivative works of the contribution in the software.
 
 3. Conditions and Limitations
-(A) No Trademark License- This license does not grant you rights to use any 
contributors’ 
+(A) No Trademark License- This license does not grant you rights to use any 
contributors’
 name, logo, or trademarks.
-(B) If you bring a patent claim against any contributor over patents that you 
claim are 
-infringed by the software, your patent license from such contributor to the 
software 
+(B) If you bring a patent claim against any contributor over patents that you 
claim are
+infringed by the software, your patent license from such contributor to the 
software
 ends automatically.
-(C) If you distribute any portion of the software, you must retain all 
copyright, patent, 
+(C) If you distribute any portion of the software, you must retain all 
copyright, patent,
 trademark, and attribution notices that are present in the software.
-(D) If you distribute any portion of the software in source code form, you may 
do so only 
-under this license by including a complete copy of this license with your 
distribution. 
-If you distribute any portion of the software in compiled or object code form, 
you may 
+(D) If you distribute any portion of the software in source code form, you may 
do so only
+under this license by including a complete copy of this license with your 
distribution.
+If you distribute any portion of the software in compiled or object code form, 
you may
 only do so under a license that complies with this license.
-(E) The software is licensed "as-is." You bear the risk of using it. The 
contributors 
-give no express warranties, guarantees or conditions. You may have additional 
consumer 
-rights under your local laws which this license cannot change. To the extent 
permitted 
-under your local laws, the contributors exclude the implied warranties of 
merchantability, 
+(E) The software is licensed "as-is." You bear the risk of using it. The 
contributors
+give no express warranties, guarantees or conditions. You may have additional 
consumer
+rights under your local laws which this license cannot change. To the extent 
permitted
+under your local laws, the contributors exclude the implied warranties of 
merchantability,
 fitness for a particular purpose and non-infringement.
-(F) Platform Limitation - The licenses granted in sections 2(A) and 2(B) 
extend only 
-to the software or derivative works that you create that run directly on a 
Microsoft 
-Windows operating system product, Microsoft run-time technology (such as the 
.NET 
-Framework or Silverlight), or Microsoft application platform (such as 
Microsoft 
+(F) Platform Limitation - The licenses granted in sections 2(A) and 2(B) 
extend only
+to the software or derivative works that you create that run directly on a 
Microsoft
+Windows operating system product, Microsoft run-time technology (such as the 
.NET
+Framework or Silverlight), or Microsoft application platform (such as Microsoft
 Office or Microsoft Dynamics).
 */
 
@@ -57,9 +57,9 @@ ends automatically.
 namespace Lucene.Net.Support.Threading
 {
     /// <summary>
-    /// Provides a task scheduler that ensures a maximum concurrency level 
while 
+    /// Provides a task scheduler that ensures a maximum concurrency level 
while
     /// running on top of the thread pool.
-    /// 
+    ///
     /// Source: 
https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler(v=vs.110).aspx
     /// </summary>
     internal class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
@@ -70,29 +70,29 @@ internal class LimitedConcurrencyLevelTaskScheduler : 
TaskScheduler
         [ThreadStatic]
         private static bool _currentThreadIsProcessingItems;
 
-        // The list of tasks to be executed 
+        // The list of tasks to be executed
         private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); // 
protected by lock(_tasks)
 
-        // The maximum concurrency level allowed by this scheduler. 
+        // The maximum concurrency level allowed by this scheduler.
         private readonly int _maxDegreeOfParallelism;
 
-        // Indicates whether the scheduler is currently processing work items. 
+        // Indicates whether the scheduler is currently processing work items.
         private int _delegatesQueuedOrRunning = 0;
 
-        // Creates a new instance with the specified degree of parallelism. 
+        // Creates a new instance with the specified degree of parallelism.
         public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
         {
             if (maxDegreeOfParallelism < 1) throw new 
ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism));
             _maxDegreeOfParallelism = maxDegreeOfParallelism;
         }
 
-        // Queues a task to the scheduler. 
+        // Queues a task to the scheduler.
         protected sealed override void QueueTask(Task task)
         {
             // Don't queue any more work.
             if (shutDown) return;
 
-            // Add the task to the list of tasks to be processed.  If there 
aren't enough 
+            // Add the task to the list of tasks to be processed.  If there 
aren't enough
             // delegates currently queued or running to process tasks, 
schedule another.
             UninterruptableMonitor.Enter(_tasks);
             try
@@ -110,15 +110,10 @@ protected sealed override void QueueTask(Task task)
             }
         }
 
-        // Inform the ThreadPool that there's work to be executed for this 
scheduler. 
+        // Inform the ThreadPool that there's work to be executed for this 
scheduler.
         private void NotifyThreadPoolOfPendingWork()
         {
-#if FEATURE_THREADPOOL_UNSAFEQUEUEWORKITEM
-            ThreadPool.UnsafeQueueUserWorkItem(
-#else
-            ThreadPool.QueueUserWorkItem(
-#endif
-            _ =>
+            ThreadPool.UnsafeQueueUserWorkItem(_ =>
             {
                 // Note that the current thread is now processing work items.
                 // This is necessary to enable inlining of tasks into this 
thread.
@@ -158,7 +153,7 @@ private void NotifyThreadPoolOfPendingWork()
             }, null);
         }
 
-        // Attempts to execute the specified task on the current thread. 
+        // Attempts to execute the specified task on the current thread.
         protected sealed override bool TryExecuteTaskInline(Task task, bool 
taskWasPreviouslyQueued)
         {
             // If this thread isn't already processing a task, we don't 
support inlining
@@ -166,7 +161,7 @@ protected sealed override bool TryExecuteTaskInline(Task 
task, bool taskWasPrevi
 
             // If the task was previously queued, remove it from the queue
             if (taskWasPreviouslyQueued)
-                // Try to run the task. 
+                // Try to run the task.
                 if (TryDequeue(task))
                     return base.TryExecuteTask(task);
                 else
@@ -175,7 +170,7 @@ protected sealed override bool TryExecuteTaskInline(Task 
task, bool taskWasPrevi
                 return base.TryExecuteTask(task);
         }
 
-        // Attempt to remove a previously scheduled task from the scheduler. 
+        // Attempt to remove a previously scheduled task from the scheduler.
         protected sealed override bool TryDequeue(Task task)
         {
             UninterruptableMonitor.Enter(_tasks);
@@ -189,10 +184,10 @@ protected sealed override bool TryDequeue(Task task)
             }
         }
 
-        // Gets the maximum concurrency level supported by this scheduler. 
+        // Gets the maximum concurrency level supported by this scheduler.
         public sealed override int MaximumConcurrencyLevel => 
_maxDegreeOfParallelism;
 
-        // Gets an enumerable of the tasks currently scheduled on this 
scheduler. 
+        // Gets an enumerable of the tasks currently scheduled on this 
scheduler.
         protected sealed override IEnumerable<Task> GetScheduledTasks()
         {
             bool lockTaken = false;
@@ -208,10 +203,17 @@ protected sealed override IEnumerable<Task> 
GetScheduledTasks()
             }
         }
 
-        // Stops this TaskScheduler from queuing new tasks.
+        /// <summary>
+        /// Stops this TaskScheduler from queuing new tasks.
+        /// </summary>
         public void Shutdown()
         {
             shutDown.Value = true;
         }
+
+        /// <summary>
+        /// Gets a value indicating whether this TaskScheduler has been shut 
down.
+        /// </summary>
+        public bool IsShutdown => shutDown;
     }
 }


Reply via email to