From 52f2f0a387cc015c0c14c1f9ef41263cc1f5f036 Mon Sep 17 00:00:00 2001 From: Phillip Allen Lane Date: Wed, 22 Nov 2023 23:51:59 -0600 Subject: [PATCH 01/12] fix test that should not be randomly failing --- DotMP-Tests/ParallelTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DotMP-Tests/ParallelTests.cs b/DotMP-Tests/ParallelTests.cs index e00ed4b3..536bb261 100644 --- a/DotMP-Tests/ParallelTests.cs +++ b/DotMP-Tests/ParallelTests.cs @@ -1091,10 +1091,11 @@ public void Tasking_works() { uint threads = 6; int sleep_duration = 100; - double start = DotMP.Parallel.GetWTime(); int[] tasks_thread_executed = new int[threads]; int total_tasks_executed = 0; + double start = DotMP.Parallel.GetWTime(); + DotMP.Parallel.ParallelRegion(num_threads: threads, action: () => { DotMP.Parallel.Single(0, () => @@ -1119,7 +1120,6 @@ public void Tasking_works() i.Should().Be(2); } elapsed.Should().BeGreaterThan(2.0 * (sleep_duration / 1000.0)); - elapsed.Should().BeLessThan(1.3 * 2.0 * (sleep_duration / 1000.0)); tasks_thread_executed = new int[threads]; int tasks_to_spawn = 100_000; From da6dc20d1e4aa89d5641a672619ea96e5bd6e715 Mon Sep 17 00:00:00 2001 From: Phillip Allen Lane Date: Wed, 22 Nov 2023 23:52:16 -0600 Subject: [PATCH 02/12] remove excess barrier --- DotMP/Parallel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/DotMP/Parallel.cs b/DotMP/Parallel.cs index 64b6d51e..5d06c992 100644 --- a/DotMP/Parallel.cs +++ b/DotMP/Parallel.cs @@ -899,8 +899,6 @@ public static void Taskwait() TaskingContainer tc = new TaskingContainer(); int tasks_remaining; - Barrier(); - do if (tc.GetNextTask(out Action do_action, out ulong uuid, out tasks_remaining)) { do_action(); From 86a8868895f1d5256c19e457c730830063729c13 Mon Sep 17 00:00:00 2001 From: Phillip Allen Lane Date: Fri, 24 Nov 2023 16:33:24 -0600 Subject: [PATCH 03/12] add exception for improper taskwait usage --- DotMP/Exceptions.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/DotMP/Exceptions.cs b/DotMP/Exceptions.cs index ea5862a6..bfa29f71 100644 --- a/DotMP/Exceptions.cs +++ b/DotMP/Exceptions.cs @@ -77,4 +77,16 @@ public class TooManyIterationsException : Exception /// The message to associate with the exception. public TooManyIterationsException(string msg) : base(msg) { } } + + /// + /// Exception thrown if the wrong taskwait overload was used from within a task. + /// + public class ImproperTaskwaitUsageException : Exception + { + /// + /// Constructor with a message. + /// + /// The message to associate with the exception. + public ImproperTaskwaitUsageException(string msg) : base(msg) { } + } } From b77f29116fa95bd893cce989948dea6137a22f4c Mon Sep 17 00:00:00 2001 From: Phillip Allen Lane Date: Fri, 24 Nov 2023 16:33:42 -0600 Subject: [PATCH 04/12] add checks to see if task is completed --- DotMP/DependencyGraph.cs | 10 ++++++++++ DotMP/Tasking.cs | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/DotMP/DependencyGraph.cs b/DotMP/DependencyGraph.cs index 2a9cff84..9d458364 100644 --- a/DotMP/DependencyGraph.cs +++ b/DotMP/DependencyGraph.cs @@ -171,5 +171,15 @@ public void Dispose() { rw_lock.Dispose(); } + + /// + /// Determines if a task has been completed. + /// + /// The ID of the task to check completion. + /// Whether or not the task has been completed. + internal bool TaskIsComplete(T id) + { + return completed.ContainsKey(id); + } } } diff --git a/DotMP/Tasking.cs b/DotMP/Tasking.cs index 81fcd615..66d100e2 100644 --- a/DotMP/Tasking.cs +++ b/DotMP/Tasking.cs @@ -105,6 +105,16 @@ internal void CompleteTask(ulong uuid) { dag.CompleteItem(uuid); } + + /// + /// Determines if a task has been completed. + /// + /// The ID of the task to check completion. + /// Whether or not the task has been completed. + internal bool TaskIsComplete(ulong uuid) + { + return dag.TaskIsComplete(uuid); + } } /// From c165ca955a2667f5eb2da3c70607bc458d4f5d2d Mon Sep 17 00:00:00 2001 From: Phillip Allen Lane Date: Fri, 24 Nov 2023 16:34:16 -0600 Subject: [PATCH 05/12] add new overload for taskwait to be called from within child tasks --- DotMP/Parallel.cs | 53 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/DotMP/Parallel.cs b/DotMP/Parallel.cs index 5d06c992..4dfa6f66 100644 --- a/DotMP/Parallel.cs +++ b/DotMP/Parallel.cs @@ -53,6 +53,10 @@ public static class Parallel /// Current thread num, cached. /// private static ThreadLocal thread_num = new ThreadLocal(() => Convert.ToInt32(Thread.CurrentThread.Name)); + /// + /// The level of task nesting, to determine when to enact barriers and reset the DAG. + /// + private static ThreadLocal task_nesting = new ThreadLocal(() => 0); /// /// Fixes the arguments for a parallel for loop. @@ -891,14 +895,19 @@ public static TaskUUID Task(Action action, params TaskUUID[] depends) /// Is injected into a Thread's work by the Region constructor, but can also be called manually. /// The injection is done to ensure that Parallel.Taskwait() is called before a Parallel.ParallelRegion() terminates, /// guaranteeing all tasks submitted complete. - /// Acts as an implicit Barrier(). + /// Acts as an implicit Barrier() if it is not called from within a task. /// public static void Taskwait() { + if (task_nesting.Value > 0) + throw new ImproperTaskwaitUsageException("Using the default taskwait from within a task deadlocks. Try specifying the task to wait on as an argument."); + ForkedRegion fr = new ForkedRegion(); TaskingContainer tc = new TaskingContainer(); int tasks_remaining; + ++task_nesting.Value; + do if (tc.GetNextTask(out Action do_action, out ulong uuid, out tasks_remaining)) { do_action(); @@ -906,11 +915,47 @@ public static void Taskwait() } while (tasks_remaining > 0); - Barrier(); + if (--task_nesting.Value == 0) + { + Master(() => Console.WriteLine("Encountering barrier, resetting DAG.")); + Barrier(); + tc.ResetDAG(); + Barrier(); + } + } + + /// + /// Wait for a task in the queue to complete. + /// Acts as an implicit Barrier() if it is not called from within a task. + /// + /// The task to wait on. + public static void Taskwait(TaskUUID task) + { + ForkedRegion fr = new ForkedRegion(); + TaskingContainer tc = new TaskingContainer(); + int tasks_remaining; - tc.ResetDAG(); + ++task_nesting.Value; - Barrier(); + do if (tc.GetNextTask(out Action do_action, out ulong uuid, out tasks_remaining)) + { + do_action(); + tc.CompleteTask(uuid); + } + while (!tc.TaskIsComplete(task.GetUUID())); + + if (--task_nesting.Value == 0) + { + Master(() => Console.WriteLine("Encountering barrier.")); + Barrier(); + + if (tasks_remaining == 0) + { + Master(() => Console.WriteLine("Resetting DAG.")); + tc.ResetDAG(); + Barrier(); + } + } } /// From 05e877d8f2888df32207ca18f29af6bb7f75abaa Mon Sep 17 00:00:00 2001 From: Phillip Allen Lane Date: Fri, 24 Nov 2023 16:34:43 -0600 Subject: [PATCH 06/12] not real test, but doesn't deadlock or except --- DotMP-Tests/ParallelTests.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/DotMP-Tests/ParallelTests.cs b/DotMP-Tests/ParallelTests.cs index 536bb261..128fbccb 100644 --- a/DotMP-Tests/ParallelTests.cs +++ b/DotMP-Tests/ParallelTests.cs @@ -1539,6 +1539,28 @@ public void Custom_scheduler_works() }); } + /// + /// Checks that nested taskwait works. + /// + [Fact] + public void Nested_taskwait_works() + { + DotMP.Parallel.ParallelMaster(() => + { + DotMP.Parallel.Task(() => + { + Console.WriteLine("Parent task."); + + var child = DotMP.Parallel.Task(() => + { + Console.WriteLine("Child task."); + }); + + DotMP.Parallel.Taskwait(child); + }); + }); + } + /// /// A sample workload for DotMP.Parallel.ParallelFor(). /// From 610a28f2b090d42e0ab9f690544e5c91727cdad6 Mon Sep 17 00:00:00 2001 From: Phillip Allen Lane Date: Fri, 24 Nov 2023 23:08:12 -0600 Subject: [PATCH 07/12] add code to manually cancel thread in case thread cannot be interrupted --- DotMP/ForkedRegion.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/DotMP/ForkedRegion.cs b/DotMP/ForkedRegion.cs index bbefed5e..fc7c746d 100644 --- a/DotMP/ForkedRegion.cs +++ b/DotMP/ForkedRegion.cs @@ -65,10 +65,12 @@ internal Thread CreateThread(Action omp_fn, int tid, uint num_threads) catch (Exception ex) { this.ex ??= ex; + Parallel.canceled = true; - for (int i = 0; i < num_threads; i++) - if (i != tid) - threads[i].Interrupt(); + if (ex is not ThreadInterruptedException) + for (int i = 0; i < num_threads; i++) + if (i != tid) + threads[i].Interrupt(); } }); } @@ -170,6 +172,7 @@ internal ForkedRegion(uint num_threads, Action omp_fn) internal void StartThreadpool() { in_parallel = true; + Parallel.canceled = false; for (int i = 0; i < reg.num_threads; i++) reg.threads[i].Start(); From ab97626ef2a3fff8e38396266ee298fd7e642c95 Mon Sep 17 00:00:00 2001 From: Phillip Allen Lane Date: Fri, 24 Nov 2023 23:08:32 -0600 Subject: [PATCH 08/12] add new reset dag function --- DotMP/Tasking.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/DotMP/Tasking.cs b/DotMP/Tasking.cs index 66d100e2..aacb525c 100644 --- a/DotMP/Tasking.cs +++ b/DotMP/Tasking.cs @@ -50,6 +50,17 @@ internal void ResetDAG() }); } + /// + /// Resets the DAG to a default state. + /// Allows the garbage collector to collect unused data. + /// Unlike ResetDAG(), this version is not thread-safe! + /// + internal void ResetDAGNotThreadSafe() + { + dag.Dispose(); + dag = new DAG(); + } + /// /// Gets the next task from the queue. /// From baa4cf056322c353ea8d8fd2b8e8abc67e7dc232 Mon Sep 17 00:00:00 2001 From: Phillip Allen Lane Date: Fri, 24 Nov 2023 23:09:17 -0600 Subject: [PATCH 09/12] complete overhaul of taskwait, now can wait on specific tasks and can be called from within a task without deadlocking. throws exception if a deadlock is detected --- DotMP/Parallel.cs | 86 ++++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/DotMP/Parallel.cs b/DotMP/Parallel.cs index 4dfa6f66..2c7fcab9 100644 --- a/DotMP/Parallel.cs +++ b/DotMP/Parallel.cs @@ -57,6 +57,10 @@ public static class Parallel /// The level of task nesting, to determine when to enact barriers and reset the DAG. /// private static ThreadLocal task_nesting = new ThreadLocal(() => 0); + /// + /// Determines if the current threadpool has been marked to terminate. + /// + internal static volatile bool canceled = false; /// /// Fixes the arguments for a parallel for loop. @@ -601,10 +605,22 @@ public static void ParallelRegion(Action action, uint? num_threads = null) num_threads ??= Parallel.num_threads; ForkedRegion freg = new ForkedRegion(num_threads.Value, action); + + if (barrier is not null) barrier.Dispose(); barrier = new Barrier((int)num_threads.Value); + + task_nesting.Dispose(); + task_nesting = new ThreadLocal(() => 0); + + TaskingContainer tc = new TaskingContainer(); + tc.ResetDAGNotThreadSafe(); + freg.StartThreadpool(); + freg.reg.num_threads = 1; + single_thread.Clear(); + barrier.Dispose(); barrier = new Barrier(1); } @@ -889,69 +905,63 @@ public static TaskUUID Task(Action action, params TaskUUID[] depends) TaskingContainer tc = new TaskingContainer(); return tc.EnqueueTask(action, depends); } - + /// - /// Wait for all tasks in the queue to complete. - /// Is injected into a Thread's work by the Region constructor, but can also be called manually. - /// The injection is done to ensure that Parallel.Taskwait() is called before a Parallel.ParallelRegion() terminates, - /// guaranteeing all tasks submitted complete. + /// Wait for selected tasks in the queue to complete, or for the full queue to empty if no tasks are specified. /// Acts as an implicit Barrier() if it is not called from within a task. /// - public static void Taskwait() + /// The tasks to wait on. + /// Thrown if a parameter-less taskwait is called from within a thread, which leads to deadlock. + public static void Taskwait(params TaskUUID[] tasks) { - if (task_nesting.Value > 0) - throw new ImproperTaskwaitUsageException("Using the default taskwait from within a task deadlocks. Try specifying the task to wait on as an argument."); - + Func check; ForkedRegion fr = new ForkedRegion(); TaskingContainer tc = new TaskingContainer(); int tasks_remaining; + int tasks_completed = 0; - ++task_nesting.Value; - - do if (tc.GetNextTask(out Action do_action, out ulong uuid, out tasks_remaining)) + if (tasks.Length == 0) + { + if (task_nesting.Value > 0) + throw new ImproperTaskwaitUsageException("Using the default taskwait from within a task will result in a deadlock. Try specifying the task to wait on as an argument."); + + check = tr => tr > 0; + } + else + { + check = _ => { - do_action(); - tc.CompleteTask(uuid); - } - while (tasks_remaining > 0); + while (tasks_completed < tasks.Length && tc.TaskIsComplete(tasks[tasks_completed].GetUUID())) + ++tasks_completed; - if (--task_nesting.Value == 0) - { - Master(() => Console.WriteLine("Encountering barrier, resetting DAG.")); - Barrier(); - tc.ResetDAG(); - Barrier(); + return tasks_completed != tasks.Length; + }; } - } - - /// - /// Wait for a task in the queue to complete. - /// Acts as an implicit Barrier() if it is not called from within a task. - /// - /// The task to wait on. - public static void Taskwait(TaskUUID task) - { - ForkedRegion fr = new ForkedRegion(); - TaskingContainer tc = new TaskingContainer(); - int tasks_remaining; + + if (task_nesting.Value == 0) + Barrier(); ++task_nesting.Value; - do if (tc.GetNextTask(out Action do_action, out ulong uuid, out tasks_remaining)) + do + { + if (canceled) + throw new ThreadInterruptedException(); + + if (tc.GetNextTask(out Action do_action, out ulong uuid, out tasks_remaining)) { do_action(); tc.CompleteTask(uuid); } - while (!tc.TaskIsComplete(task.GetUUID())); + } + while (check(tasks_remaining)); if (--task_nesting.Value == 0) { - Master(() => Console.WriteLine("Encountering barrier.")); Barrier(); if (tasks_remaining == 0) { - Master(() => Console.WriteLine("Resetting DAG.")); tc.ResetDAG(); Barrier(); } From 6afc24f69811d72a0b57912618c313000cafa6a1 Mon Sep 17 00:00:00 2001 From: Phillip Allen Lane Date: Fri, 24 Nov 2023 23:10:19 -0600 Subject: [PATCH 10/12] add new tests for taskwait within tasks --- DotMP-Tests/ParallelTests.cs | 62 ++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/DotMP-Tests/ParallelTests.cs b/DotMP-Tests/ParallelTests.cs index 128fbccb..72225c83 100644 --- a/DotMP-Tests/ParallelTests.cs +++ b/DotMP-Tests/ParallelTests.cs @@ -18,10 +18,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime; using System.Threading; using DotMP; using FluentAssertions; using Xunit; +using Xunit.Abstractions; namespace DotMPTests { @@ -30,6 +32,17 @@ namespace DotMPTests /// public class ParallelTests { + private readonly ITestOutputHelper output; + + /// + /// Constructor to write output. + /// + /// Output object. + public ParallelTests(ITestOutputHelper output) + { + this.output = output; + } + /// /// Tests to make sure that parallel performance is higher than sequential performance. /// @@ -1545,18 +1558,53 @@ public void Custom_scheduler_works() [Fact] public void Nested_taskwait_works() { - DotMP.Parallel.ParallelMaster(() => + int prog = 0; + + DotMP.Parallel.ParallelRegion(num_threads: 4, action: () => { - DotMP.Parallel.Task(() => + DotMP.Parallel.Master(() => { - Console.WriteLine("Parent task."); - - var child = DotMP.Parallel.Task(() => + DotMP.Parallel.Task(() => { - Console.WriteLine("Child task."); + DotMP.Atomic.Inc(ref prog).Should().Be(5); + + var child1 = DotMP.Parallel.Task(() => + { + Thread.Sleep(1000); + DotMP.Atomic.Inc(ref prog).Should().BeInRange(6, 7); + }); + + var child2 = DotMP.Parallel.Task(() => + { + Thread.Sleep(1000); + DotMP.Atomic.Inc(ref prog).Should().BeInRange(6, 7); + }); + + DotMP.Parallel.Taskwait(child1, child2); + + DotMP.Atomic.Inc(ref prog).Should().Be(8); }); + }); + + DotMP.Atomic.Inc(ref prog).Should().BeLessThanOrEqualTo(4); + + DotMP.Parallel.Taskwait(); + + DotMP.Atomic.Inc(ref prog).Should().BeGreaterThan(8); + }); + } - DotMP.Parallel.Taskwait(child); + /// + /// Ensures that improper usage of taskwait that risks deadlock should throw an exception. + /// + [Fact] + public void Improper_taskwait_should_except() + { + Assert.Throws(() => + { + DotMP.Parallel.ParallelMaster(() => + { + DotMP.Parallel.Task(() => DotMP.Parallel.Taskwait()); }); }); } From b0cb81fb5f049c1302289b4fac63236c487c5041 Mon Sep 17 00:00:00 2001 From: Phillip Allen Lane Date: Fri, 24 Nov 2023 23:11:52 -0600 Subject: [PATCH 11/12] tidy up license header --- DotMP/DependencyGraph.cs | 4 ++-- DotMP/ForkedRegion.cs | 4 ++-- DotMP/Tasking.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DotMP/DependencyGraph.cs b/DotMP/DependencyGraph.cs index 9d458364..47f3a48b 100644 --- a/DotMP/DependencyGraph.cs +++ b/DotMP/DependencyGraph.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. */ diff --git a/DotMP/ForkedRegion.cs b/DotMP/ForkedRegion.cs index fc7c746d..f07f9094 100644 --- a/DotMP/ForkedRegion.cs +++ b/DotMP/ForkedRegion.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. */ diff --git a/DotMP/Tasking.cs b/DotMP/Tasking.cs index aacb525c..db2aa23d 100644 --- a/DotMP/Tasking.cs +++ b/DotMP/Tasking.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. */ From 4203bdd2a6165d48ac5f8e218d8588ee48f9a2a1 Mon Sep 17 00:00:00 2001 From: Phillip Allen Lane Date: Fri, 24 Nov 2023 23:24:51 -0600 Subject: [PATCH 12/12] format with dotnet format --- DotMP/Parallel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DotMP/Parallel.cs b/DotMP/Parallel.cs index 2c7fcab9..a7f4f8c9 100644 --- a/DotMP/Parallel.cs +++ b/DotMP/Parallel.cs @@ -611,7 +611,7 @@ public static void ParallelRegion(Action action, uint? num_threads = null) task_nesting.Dispose(); task_nesting = new ThreadLocal(() => 0); - + TaskingContainer tc = new TaskingContainer(); tc.ResetDAGNotThreadSafe(); @@ -905,7 +905,7 @@ public static TaskUUID Task(Action action, params TaskUUID[] depends) TaskingContainer tc = new TaskingContainer(); return tc.EnqueueTask(action, depends); } - + /// /// Wait for selected tasks in the queue to complete, or for the full queue to empty if no tasks are specified. /// Acts as an implicit Barrier() if it is not called from within a task. @@ -924,7 +924,7 @@ public static void Taskwait(params TaskUUID[] tasks) { if (task_nesting.Value > 0) throw new ImproperTaskwaitUsageException("Using the default taskwait from within a task will result in a deadlock. Try specifying the task to wait on as an argument."); - + check = tr => tr > 0; } else