diff --git a/DotMP-Tests/ParallelTests.cs b/DotMP-Tests/ParallelTests.cs
index 72225c8..4f38d36 100644
--- a/DotMP-Tests/ParallelTests.cs
+++ b/DotMP-Tests/ParallelTests.cs
@@ -1609,6 +1609,18 @@ public void Improper_taskwait_should_except()
});
}
+ ///
+ /// Ensures that overflows in the schedulers properly throw exceptions.
+ ///
+ [Fact]
+ public void Boundary_parallelfor_should_except()
+ {
+ Assert.Throws(() =>
+ {
+ DotMP.Parallel.ParallelFor(0, int.MaxValue, i => { });
+ });
+ }
+
///
/// A sample workload for DotMP.Parallel.ParallelFor().
///
diff --git a/DotMP/DotMP.csproj b/DotMP/DotMP.csproj
index 4a09920..e8cc69f 100644
--- a/DotMP/DotMP.csproj
+++ b/DotMP/DotMP.csproj
@@ -4,7 +4,7 @@
net6.0;net7.0;net8.0
DotMP
DotMP
- 1.6.0-pre2
+ 1.6.0
Phillip Allen Lane,et al.
A library for fork-join parallelism in .NET, with an OpenMP-like API.
https://github.com/computablee/DotMP
diff --git a/DotMP/Exceptions.cs b/DotMP/Exceptions.cs
index bfa29f7..449f97f 100644
--- a/DotMP/Exceptions.cs
+++ b/DotMP/Exceptions.cs
@@ -89,4 +89,16 @@ public class ImproperTaskwaitUsageException : Exception
/// The message to associate with the exception.
public ImproperTaskwaitUsageException(string msg) : base(msg) { }
}
+
+ ///
+ /// Exception thrown if the internal schedulers encounter an overflow.
+ ///
+ public class InternalSchedulerException : Exception
+ {
+ ///
+ /// Constructor with a message.
+ ///
+ /// The message to associate with the exception.
+ public InternalSchedulerException(string msg) : base(msg) { }
+ }
}
diff --git a/DotMP/Schedule.cs b/DotMP/Schedule.cs
index 9033402..48b8d97 100644
--- a/DotMP/Schedule.cs
+++ b/DotMP/Schedule.cs
@@ -204,14 +204,18 @@ private struct IterWrapper
/// The end of the loop, exclusive.
/// The number of threads.
/// The chunk size.
+ /// Thrown if there's an internal scheduler overflow.
public override void LoopInit(int start, int end, uint num_threads, uint chunk_size)
{
- this.chunk_size = chunk_size;
- this.end = end;
- advance_by = (int)(chunk_size * num_threads);
- curr_iters = new IterWrapper[num_threads];
- for (int i = 0; i < num_threads; i++)
- curr_iters[i].curr_iter = start + ((int)chunk_size * i);
+ checked
+ {
+ this.chunk_size = chunk_size;
+ this.end = end;
+ advance_by = (int)(chunk_size * num_threads);
+ curr_iters = new IterWrapper[num_threads];
+ for (int i = 0; i < num_threads; i++)
+ curr_iters[i].curr_iter = start + ((int)chunk_size * i);
+ }
}
///
@@ -220,11 +224,15 @@ public override void LoopInit(int start, int end, uint num_threads, uint chunk_s
/// The thread ID.
/// The start of the chunk, inclusive.
/// The end of the chunk, exclusive.
+ /// Thrown if there's an internal scheduler overflow.
public override void LoopNext(int thread_id, out int start, out int end)
{
- start = curr_iters[thread_id].curr_iter;
- end = Math.Min(start + (int)chunk_size, this.end);
- curr_iters[thread_id].curr_iter += advance_by;
+ checked
+ {
+ start = curr_iters[thread_id].curr_iter;
+ end = Math.Min(start + (int)chunk_size, this.end);
+ curr_iters[thread_id].curr_iter += advance_by;
+ }
}
}
@@ -266,10 +274,14 @@ public override void LoopInit(int start, int end, uint num_threads, uint chunk_s
/// The thread ID.
/// The start of the chunk, inclusive.
/// The end of the chunk, exclusive.
+ /// Thrown if there's an internal scheduler overflow.
public override void LoopNext(int thread_id, out int start, out int end)
{
- start = Interlocked.Add(ref this.start, (int)chunk_size) - (int)chunk_size;
- end = Math.Min(start + (int)chunk_size, this.end);
+ checked
+ {
+ start = Interlocked.Add(ref this.start, (int)chunk_size) - (int)chunk_size;
+ end = Math.Min(start + (int)chunk_size, this.end);
+ }
}
}
@@ -321,19 +333,23 @@ public override void LoopInit(int start, int end, uint num_threads, uint chunk_s
/// The thread ID.
/// The start of the chunk, inclusive.
/// The end of the chunk, exclusive.
+ /// Thrown if there's an internal scheduler overflow.
public override void LoopNext(int thread_id, out int start, out int end)
{
- int chunk_size;
-
- lock (sched_lock)
+ checked
{
- start = this.start;
- chunk_size = (int)Math.Max(this.chunk_size, (this.end - start) / (num_threads * 2));
+ int chunk_size;
- this.start += chunk_size;
- }
+ lock (sched_lock)
+ {
+ start = this.start;
+ chunk_size = (int)Math.Max(this.chunk_size, (this.end - start) / (num_threads * 2));
+
+ this.start += chunk_size;
+ }
- end = Math.Min(start + chunk_size, this.end);
+ end = Math.Min(start + chunk_size, this.end);
+ }
}
}
@@ -423,29 +439,33 @@ private struct Queue
/// The end of the loop, exclusive.
/// The number of threads.
/// The chunk size.
+ /// Thrown if there's an internal scheduler overflow.
public override void LoopInit(int start, int end, uint num_threads, uint chunk_size)
{
- this.queues = new Queue[num_threads];
- this.chunk_size = chunk_size;
- this.threads_with_remaining_work = num_threads;
- this.num_threads = num_threads;
+ checked
+ {
+ this.queues = new Queue[num_threads];
+ this.chunk_size = chunk_size;
+ this.threads_with_remaining_work = num_threads;
+ this.num_threads = num_threads;
- int ctr = start;
- int div = (end - start) / (int)num_threads;
+ int ctr = start;
+ int div = (end - start) / (int)num_threads;
- for (int i = 0; i < num_threads - 1; i++)
- {
- queues[i].start = ctr;
- ctr += div;
- queues[i].end = ctr;
- queues[i].work_remaining = true;
- queues[i].qlock = new object();
- }
+ for (int i = 0; i < num_threads - 1; i++)
+ {
+ queues[i].start = ctr;
+ ctr += div;
+ queues[i].end = ctr;
+ queues[i].work_remaining = true;
+ queues[i].qlock = new object();
+ }
- queues[num_threads - 1].start = ctr;
- queues[num_threads - 1].end = end;
- queues[num_threads - 1].work_remaining = true;
- queues[num_threads - 1].qlock = new object();
+ queues[num_threads - 1].start = ctr;
+ queues[num_threads - 1].end = end;
+ queues[num_threads - 1].work_remaining = true;
+ queues[num_threads - 1].qlock = new object();
+ }
}
///
@@ -454,25 +474,29 @@ public override void LoopInit(int start, int end, uint num_threads, uint chunk_s
/// The thread ID.
/// The start of the chunk, inclusive.
/// The end of the chunk, exclusive.
+ /// Thrown if there's an internal scheduler overflow.
public override void LoopNext(int thread_id, out int start, out int end)
{
- do
+ checked
{
- lock (queues[thread_id].qlock)
+ do
{
- start = queues[thread_id].start;
- end = Math.Min(start + (int)chunk_size, queues[thread_id].end);
-
- if (start < end)
+ lock (queues[thread_id].qlock)
{
- queues[thread_id].start += (int)chunk_size;
- return;
+ start = queues[thread_id].start;
+ end = Math.Min(start + (int)chunk_size, queues[thread_id].end);
+
+ if (start < end)
+ {
+ queues[thread_id].start += (int)chunk_size;
+ return;
+ }
}
- }
- StealHandler(thread_id);
+ StealHandler(thread_id);
+ }
+ while (threads_with_remaining_work > 0);
}
- while (threads_with_remaining_work > 0);
}
///
@@ -499,35 +523,39 @@ private void StealHandler(int thread_id)
///
/// The thread ID.
/// Whether or not the steal was successful.
+ /// Thrown if there's an internal scheduler overflow.
private bool DoSteal(int thread_id)
{
- int rng = Random.Shared.Next((int)num_threads);
- int new_start, new_end;
-
- lock (queues[rng].qlock)
+ checked
{
- if (queues[rng].start < queues[rng].end)
+ int rng = Random.Shared.Next((int)num_threads);
+ int new_start, new_end;
+
+ lock (queues[rng].qlock)
{
- int steal_size = (queues[rng].end - queues[rng].start + 1) / 2;
+ if (queues[rng].start < queues[rng].end)
+ {
+ int steal_size = (queues[rng].end - queues[rng].start + 1) / 2;
- new_start = queues[rng].start;
- new_end = queues[rng].start + steal_size;
+ new_start = queues[rng].start;
+ new_end = queues[rng].start + steal_size;
- queues[rng].start = new_end;
+ queues[rng].start = new_end;
+ }
+ else
+ {
+ return false;
+ }
}
- else
+
+ lock (queues[thread_id].qlock)
{
- return false;
+ queues[thread_id].start = new_start;
+ queues[thread_id].end = new_end;
}
- }
- lock (queues[thread_id].qlock)
- {
- queues[thread_id].start = new_start;
- queues[thread_id].end = new_end;
+ return true;
}
-
- return true;
}
}
}
diff --git a/DotMP/WorkShare.cs b/DotMP/WorkShare.cs
index df7fbc4..6fb12f7 100644
--- a/DotMP/WorkShare.cs
+++ b/DotMP/WorkShare.cs
@@ -5,11 +5,11 @@
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
-
+ *
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
-
+ *
* You should have received a copy of the GNU Lesser General Public License along with this library; if not,
* write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
@@ -17,6 +17,7 @@
using System.Collections.Generic;
using System.Threading;
using System;
+using DotMP.Exceptions;
namespace DotMP
{
@@ -245,6 +246,7 @@ internal void SetLocal(ref T local)
///
/// The type of reductions, if applicable.
/// The function to be executed.
+ /// Thrown if the internal schedulers throw an exception.
internal void PerformLoop(ForAction forAction)
{
int start = this.start;
@@ -258,19 +260,26 @@ internal void PerformLoop(ForAction forAction)
if (forAction.IsReduction)
SetLocal(ref local);
- Parallel.Master(() => scheduler.LoopInit(start, end, num_threads, chunk_size));
- Parallel.Barrier();
+ try
+ {
+ Parallel.Master(() => scheduler.LoopInit(start, end, num_threads, chunk_size));
+ Parallel.Barrier();
- int chunk_start, chunk_end;
- ref int curr_iter = ref working_iters[thread_id];
+ int chunk_start, chunk_end;
+ ref int curr_iter = ref working_iters[thread_id];
- do
+ do
+ {
+ scheduler.LoopNext(thread_id, out chunk_start, out chunk_end);
+ if (chunk_start < chunk_end)
+ forAction.PerformLoop(ref curr_iter, chunk_start, chunk_end, ref local);
+ }
+ while (chunk_start < chunk_end);
+ }
+ catch (OverflowException)
{
- scheduler.LoopNext(thread_id, out chunk_start, out chunk_end);
- if (chunk_start < chunk_end)
- forAction.PerformLoop(ref curr_iter, chunk_start, chunk_end, ref local);
+ throw new InternalSchedulerException(string.Format("An internal overflow exception has occurred within the loop scheduler. This most often happens when the upper bound of the loop is too close to {0}.", int.MaxValue));
}
- while (chunk_start < chunk_end);
if (forAction.IsReduction)
AddReductionValue(local);
diff --git a/Makefile b/Makefile
index 9c54aa5..6565635 100644
--- a/Makefile
+++ b/Makefile
@@ -30,10 +30,10 @@ examples-seq:
$(DN) build -c $(BUILD) examples/Serial/KNN
tests:
- $(DN) build -c $(BUILD) DotMP-Tests
+ $(DN) build -c Debug DotMP-Tests
test:
- $(DN) test -c $(BUILD) -l "console;verbosity=detailed" -p:CollectCoverage=true -p:CoverletOutputFormat=opencover DotMP-Tests
+ $(DN) test -c Debug -l "console;verbosity=detailed" -p:CollectCoverage=true -p:CoverletOutputFormat=opencover DotMP-Tests
build:
$(DN) build -c $(BUILD) -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg DotMP
@@ -42,11 +42,7 @@ docs: ProcessedREADME.md
doxygen
pack: ProcessedREADME.md build
- $(DN) pack -c $(BUILD) DotMP
- cp ./DotMP/bin/Release/net6.0/DotMP.dll ./DotMP/bin/Release/DotMP-NET6.0.dll
- cp ./DotMP/bin/Release/net7.0/DotMP.dll ./DotMP/bin/Release/DotMP-NET7.0.dll
- cp ./DotMP/bin/Release/net6.0/DotMP.pdb ./DotMP/bin/Release/DotMP-NET6.0.pdb
- cp ./DotMP/bin/Release/net7.0/DotMP.pdb ./DotMP/bin/Release/DotMP-NET7.0.pdb
+ $(DN) pack -c $(BUILD) /p:ContinuousIntegrationBuild=true DotMP
clean:
rm -f ProcessedREADME.md