From db4fb0c6c9dedfd7802fae4a3bbff2723ce93593 Mon Sep 17 00:00:00 2001 From: Brian Pepin Date: Fri, 27 Mar 2020 16:25:07 -0700 Subject: [PATCH] Fix corner case error handling (#505) This change fixes a corner case where we could orphan a call if an async call was started after a queue was terminated. --- .../libHttpClient.140.UWP.C.vcxproj | 1 + .../libHttpClient.140.UWP.C.vcxproj.filters | 3 + .../libHttpClient.140.Win32.C.vcxproj | 1 + .../libHttpClient.140.Win32.C.vcxproj.filters | 31 ++-- .../libHttpClient.140.XDK.C.vcxproj | 1 + .../libHttpClient.140.XDK.C.vcxproj.filters | 3 + .../libHttpClient.141.Android.C.vcxproj | 2 +- ...ibHttpClient.141.Android.C.vcxproj.filters | 3 + .../libHttpClient.141.UWP.C.vcxproj | 1 + .../libHttpClient.141.UWP.C.vcxproj.filters | 3 + .../libHttpClient.141.Win32.C.vcxproj | 1 + .../libHttpClient.141.Win32.C.vcxproj.filters | 31 ++-- .../libHttpClient.141.XDK.C.vcxproj | 1 + .../libHttpClient.141.XDK.C.vcxproj.filters | 3 + .../libHttpClient.142.Android.C.vcxproj | 4 +- ...ibHttpClient.142.Android.C.vcxproj.filters | 31 ++-- .../libHttpClient.142.UWP.C.vcxproj | 3 +- .../libHttpClient.142.UWP.C.vcxproj.filters | 31 ++-- .../libHttpClient.142.Win32.C.vcxproj | 1 + .../libHttpClient.142.Win32.C.vcxproj.filters | 31 ++-- .../libHttpClient.142.XDK.C.vcxproj | 2 +- .../libHttpClient.142.XDK.C.vcxproj.filters | 28 ++-- .../libHttpClient.UnitTest.141.TAEF.vcxproj | 1 + ...tpClient.UnitTest.141.TAEF.vcxproj.filters | 3 + .../libHttpClient.UnitTest.141.TE.vcxproj | 1 + ...HttpClient.UnitTest.141.TE.vcxproj.filters | 3 + .../libHttpClient.UnitTest.142.TAEF.vcxproj | 3 +- ...tpClient.UnitTest.142.TAEF.vcxproj.filters | 3 + .../libHttpClient.UnitTest.142.TE.vcxproj | 1 + ...HttpClient.UnitTest.142.TE.vcxproj.filters | 3 + Include/XAsyncProvider.h | 30 ---- Source/Common/ResultMacros.h | 2 + Source/Common/pch_common.h | 1 + Source/Task/AsyncLib.cpp | 144 +++++++++++++----- Source/Task/TaskQueue.cpp | 140 ++++++++++++++++- Source/Task/TaskQueueImpl.h | 12 ++ Source/Task/TaskQueueP.h | 11 +- Source/Task/XAsyncProviderPriv.h | 39 +++++ Source/Task/XTaskQueuePriv.h | 21 ++- Tests/UnitTests/Tests/AsyncBlockTests.cpp | 102 ++++++++++--- Tests/UnitTests/Tests/TaskQueueTests.cpp | 29 ++++ Utilities/CMake/CMakeLists.txt | 1 + 42 files changed, 584 insertions(+), 182 deletions(-) create mode 100644 Source/Task/XAsyncProviderPriv.h diff --git a/Build/libHttpClient.140.UWP.C/libHttpClient.140.UWP.C.vcxproj b/Build/libHttpClient.140.UWP.C/libHttpClient.140.UWP.C.vcxproj index 91264c81..276d349a 100644 --- a/Build/libHttpClient.140.UWP.C/libHttpClient.140.UWP.C.vcxproj +++ b/Build/libHttpClient.140.UWP.C/libHttpClient.140.UWP.C.vcxproj @@ -158,6 +158,7 @@ + diff --git a/Build/libHttpClient.140.UWP.C/libHttpClient.140.UWP.C.vcxproj.filters b/Build/libHttpClient.140.UWP.C/libHttpClient.140.UWP.C.vcxproj.filters index 819f2e59..0adb352f 100644 --- a/Build/libHttpClient.140.UWP.C/libHttpClient.140.UWP.C.vcxproj.filters +++ b/Build/libHttpClient.140.UWP.C/libHttpClient.140.UWP.C.vcxproj.filters @@ -159,6 +159,9 @@ C++ Source\Task + + C++ Source\Task + C++ Source\Task diff --git a/Build/libHttpClient.140.Win32.C/libHttpClient.140.Win32.C.vcxproj b/Build/libHttpClient.140.Win32.C/libHttpClient.140.Win32.C.vcxproj index 365965b8..300ba072 100644 --- a/Build/libHttpClient.140.Win32.C/libHttpClient.140.Win32.C.vcxproj +++ b/Build/libHttpClient.140.Win32.C/libHttpClient.140.Win32.C.vcxproj @@ -141,6 +141,7 @@ + diff --git a/Build/libHttpClient.140.Win32.C/libHttpClient.140.Win32.C.vcxproj.filters b/Build/libHttpClient.140.Win32.C/libHttpClient.140.Win32.C.vcxproj.filters index c0fab39d..a547ce5b 100644 --- a/Build/libHttpClient.140.Win32.C/libHttpClient.140.Win32.C.vcxproj.filters +++ b/Build/libHttpClient.140.Win32.C/libHttpClient.140.Win32.C.vcxproj.filters @@ -138,6 +138,9 @@ C++ Source\Task + + C++ Source\Task + C++ Source\Task @@ -183,46 +186,46 @@ - {B348936D-D09B-3ACE-A476-12C7C5E48781} + {AB68AAD7-BF0D-34C3-BFBA-FB7594810503} - {6D290126-3831-3A04-903B-F2ADD104D81C} + {8B9EEA6A-3165-3A83-8CEB-648270B8C445} - {E705B256-E422-3FF4-95FA-7E24B8A5474F} + {28B59420-8446-3F26-9AB1-72123FF92816} - {B5E18BF5-6C51-323C-96A0-81422D9D6069} + {A616C757-EB09-3284-8BA4-4C0B391C1672} - {6DAD9E7F-080C-3264-920E-77D0AFFE8CC9} + {7607E023-D56D-300A-AAE7-35C1337C6EF3} - {01466804-77B8-33C2-AAEE-469619C39927} + {6095F35A-0F36-3DA9-8D1A-8F2DA598B6E0} - {FD710BAD-78A2-39F4-9CB7-90B1627CC1D5} + {658327C4-885D-3CF1-9061-DA642B97898A} - {CE716A57-F0D2-3112-9432-4955DE919940} + {89A6373D-D2E3-3B5B-B6CF-4AB7CDFB13B8} - {ECB59FD8-67C4-354A-90B2-DFC893C0F2E5} + {57AC3982-96A9-3B0B-A7DF-8DF69AD19ECC} - {09ED63FC-76E4-368A-B7E4-40A1D70ABD31} + {075BEBE6-800C-3FB2-84E2-6443C472E199} - {82CDC498-C15D-38CB-ABAF-432B4881A142} + {D0B227E9-0B82-3AF7-82EC-9581DA9FD1A3} - {09CB3AE4-1639-3274-BB94-99AAC498EA50} + {0974FCD6-8409-3FA3-A118-A58ED0FD041C} - {802954F0-B5AC-3B13-A5F2-01E8A7888AAE} + {350A88A7-908E-346C-8EEC-8C500141D764} - {42CA3718-AE3E-3CEB-A859-F23D0D7A9918} + {2F64C9BE-5116-3E71-971F-592C0D76A89D} diff --git a/Build/libHttpClient.140.XDK.C/libHttpClient.140.XDK.C.vcxproj b/Build/libHttpClient.140.XDK.C/libHttpClient.140.XDK.C.vcxproj index 33061886..43e47918 100644 --- a/Build/libHttpClient.140.XDK.C/libHttpClient.140.XDK.C.vcxproj +++ b/Build/libHttpClient.140.XDK.C/libHttpClient.140.XDK.C.vcxproj @@ -130,6 +130,7 @@ + diff --git a/Build/libHttpClient.140.XDK.C/libHttpClient.140.XDK.C.vcxproj.filters b/Build/libHttpClient.140.XDK.C/libHttpClient.140.XDK.C.vcxproj.filters index 4a982d7a..8e7b34ef 100644 --- a/Build/libHttpClient.140.XDK.C/libHttpClient.140.XDK.C.vcxproj.filters +++ b/Build/libHttpClient.140.XDK.C/libHttpClient.140.XDK.C.vcxproj.filters @@ -159,6 +159,9 @@ C++ Source\Task + + C++ Source\Task + C++ Source\Task diff --git a/Build/libHttpClient.141.Android.C/libHttpClient.141.Android.C.vcxproj b/Build/libHttpClient.141.Android.C/libHttpClient.141.Android.C.vcxproj index ca732015..49c72ccf 100644 --- a/Build/libHttpClient.141.Android.C/libHttpClient.141.Android.C.vcxproj +++ b/Build/libHttpClient.141.Android.C/libHttpClient.141.Android.C.vcxproj @@ -46,7 +46,6 @@ 3.0 Android true - 25.0.3 @@ -126,6 +125,7 @@ + diff --git a/Build/libHttpClient.141.Android.C/libHttpClient.141.Android.C.vcxproj.filters b/Build/libHttpClient.141.Android.C/libHttpClient.141.Android.C.vcxproj.filters index 6ad22e6a..f64b4559 100644 --- a/Build/libHttpClient.141.Android.C/libHttpClient.141.Android.C.vcxproj.filters +++ b/Build/libHttpClient.141.Android.C/libHttpClient.141.Android.C.vcxproj.filters @@ -147,6 +147,9 @@ C++ Source\Task + + C++ Source\Task + C++ Source\Task diff --git a/Build/libHttpClient.141.UWP.C/libHttpClient.141.UWP.C.vcxproj b/Build/libHttpClient.141.UWP.C/libHttpClient.141.UWP.C.vcxproj index 2fd4b9a3..753d0606 100644 --- a/Build/libHttpClient.141.UWP.C/libHttpClient.141.UWP.C.vcxproj +++ b/Build/libHttpClient.141.UWP.C/libHttpClient.141.UWP.C.vcxproj @@ -169,6 +169,7 @@ + diff --git a/Build/libHttpClient.141.UWP.C/libHttpClient.141.UWP.C.vcxproj.filters b/Build/libHttpClient.141.UWP.C/libHttpClient.141.UWP.C.vcxproj.filters index 819f2e59..0adb352f 100644 --- a/Build/libHttpClient.141.UWP.C/libHttpClient.141.UWP.C.vcxproj.filters +++ b/Build/libHttpClient.141.UWP.C/libHttpClient.141.UWP.C.vcxproj.filters @@ -159,6 +159,9 @@ C++ Source\Task + + C++ Source\Task + C++ Source\Task diff --git a/Build/libHttpClient.141.Win32.C/libHttpClient.141.Win32.C.vcxproj b/Build/libHttpClient.141.Win32.C/libHttpClient.141.Win32.C.vcxproj index aa2b1e5e..c2411e3e 100644 --- a/Build/libHttpClient.141.Win32.C/libHttpClient.141.Win32.C.vcxproj +++ b/Build/libHttpClient.141.Win32.C/libHttpClient.141.Win32.C.vcxproj @@ -150,6 +150,7 @@ + diff --git a/Build/libHttpClient.141.Win32.C/libHttpClient.141.Win32.C.vcxproj.filters b/Build/libHttpClient.141.Win32.C/libHttpClient.141.Win32.C.vcxproj.filters index c0fab39d..a547ce5b 100644 --- a/Build/libHttpClient.141.Win32.C/libHttpClient.141.Win32.C.vcxproj.filters +++ b/Build/libHttpClient.141.Win32.C/libHttpClient.141.Win32.C.vcxproj.filters @@ -138,6 +138,9 @@ C++ Source\Task + + C++ Source\Task + C++ Source\Task @@ -183,46 +186,46 @@ - {B348936D-D09B-3ACE-A476-12C7C5E48781} + {AB68AAD7-BF0D-34C3-BFBA-FB7594810503} - {6D290126-3831-3A04-903B-F2ADD104D81C} + {8B9EEA6A-3165-3A83-8CEB-648270B8C445} - {E705B256-E422-3FF4-95FA-7E24B8A5474F} + {28B59420-8446-3F26-9AB1-72123FF92816} - {B5E18BF5-6C51-323C-96A0-81422D9D6069} + {A616C757-EB09-3284-8BA4-4C0B391C1672} - {6DAD9E7F-080C-3264-920E-77D0AFFE8CC9} + {7607E023-D56D-300A-AAE7-35C1337C6EF3} - {01466804-77B8-33C2-AAEE-469619C39927} + {6095F35A-0F36-3DA9-8D1A-8F2DA598B6E0} - {FD710BAD-78A2-39F4-9CB7-90B1627CC1D5} + {658327C4-885D-3CF1-9061-DA642B97898A} - {CE716A57-F0D2-3112-9432-4955DE919940} + {89A6373D-D2E3-3B5B-B6CF-4AB7CDFB13B8} - {ECB59FD8-67C4-354A-90B2-DFC893C0F2E5} + {57AC3982-96A9-3B0B-A7DF-8DF69AD19ECC} - {09ED63FC-76E4-368A-B7E4-40A1D70ABD31} + {075BEBE6-800C-3FB2-84E2-6443C472E199} - {82CDC498-C15D-38CB-ABAF-432B4881A142} + {D0B227E9-0B82-3AF7-82EC-9581DA9FD1A3} - {09CB3AE4-1639-3274-BB94-99AAC498EA50} + {0974FCD6-8409-3FA3-A118-A58ED0FD041C} - {802954F0-B5AC-3B13-A5F2-01E8A7888AAE} + {350A88A7-908E-346C-8EEC-8C500141D764} - {42CA3718-AE3E-3CEB-A859-F23D0D7A9918} + {2F64C9BE-5116-3E71-971F-592C0D76A89D} diff --git a/Build/libHttpClient.141.XDK.C/libHttpClient.141.XDK.C.vcxproj b/Build/libHttpClient.141.XDK.C/libHttpClient.141.XDK.C.vcxproj index 3c7c817d..b8aee127 100644 --- a/Build/libHttpClient.141.XDK.C/libHttpClient.141.XDK.C.vcxproj +++ b/Build/libHttpClient.141.XDK.C/libHttpClient.141.XDK.C.vcxproj @@ -131,6 +131,7 @@ + diff --git a/Build/libHttpClient.141.XDK.C/libHttpClient.141.XDK.C.vcxproj.filters b/Build/libHttpClient.141.XDK.C/libHttpClient.141.XDK.C.vcxproj.filters index 4a982d7a..8e7b34ef 100644 --- a/Build/libHttpClient.141.XDK.C/libHttpClient.141.XDK.C.vcxproj.filters +++ b/Build/libHttpClient.141.XDK.C/libHttpClient.141.XDK.C.vcxproj.filters @@ -159,6 +159,9 @@ C++ Source\Task + + C++ Source\Task + C++ Source\Task diff --git a/Build/libHttpClient.142.Android.C/libHttpClient.142.Android.C.vcxproj b/Build/libHttpClient.142.Android.C/libHttpClient.142.Android.C.vcxproj index 519466fa..b947b79d 100644 --- a/Build/libHttpClient.142.Android.C/libHttpClient.142.Android.C.vcxproj +++ b/Build/libHttpClient.142.Android.C/libHttpClient.142.Android.C.vcxproj @@ -35,7 +35,7 @@ - {5e0ce391-1ac5-4930-921e-2577a4b5c530} + {80FE2440-4F89-4A47-88C6-E86447BC2319} Android Clang_5_0 android-19 @@ -46,7 +46,6 @@ 3.0 Android true - 25.0.3 @@ -126,6 +125,7 @@ + diff --git a/Build/libHttpClient.142.Android.C/libHttpClient.142.Android.C.vcxproj.filters b/Build/libHttpClient.142.Android.C/libHttpClient.142.Android.C.vcxproj.filters index 6dd2980c..f64b4559 100644 --- a/Build/libHttpClient.142.Android.C/libHttpClient.142.Android.C.vcxproj.filters +++ b/Build/libHttpClient.142.Android.C/libHttpClient.142.Android.C.vcxproj.filters @@ -147,6 +147,9 @@ C++ Source\Task + + C++ Source\Task + C++ Source\Task @@ -192,46 +195,46 @@ - {A740C412-FB5D-3AAA-8C89-79F901BDFCAA} + {AB68AAD7-BF0D-34C3-BFBA-FB7594810503} - {27CFE030-22C9-3F80-BE9E-6C85269BA8CF} + {8B9EEA6A-3165-3A83-8CEB-648270B8C445} - {7F5C193A-6335-3652-8522-F362DE77CEC5} + {F6086770-20F5-36E7-9347-F8E511A64421} - {3A5140CA-AFB5-3F07-934D-A8067647C112} + {A616C757-EB09-3284-8BA4-4C0B391C1672} - {4C46FCD2-8942-35E4-A9F6-E32517A218FC} + {7607E023-D56D-300A-AAE7-35C1337C6EF3} - {3FE50A42-C399-35C5-807F-53541A10E202} + {15D2B7C4-8092-30BB-B45A-23FB8BB289F7} - {F9631A84-9D6B-300A-B12D-A52D1FCE33D5} + {658327C4-885D-3CF1-9061-DA642B97898A} - {1CBA16A9-7A69-31BB-8A4F-0C4D1CC3CF66} + {5DE6DC08-DC97-3EDE-86DC-D80C73AB8212} - {7E7975B0-87A8-3B63-AC39-F4B973EBFE5E} + {57AC3982-96A9-3B0B-A7DF-8DF69AD19ECC} - {A5C44A5C-F557-3485-B0F3-96924ED16997} + {075BEBE6-800C-3FB2-84E2-6443C472E199} - {B18AF8F5-F9A5-3AA5-BCEA-7699667F4BEF} + {B2E61810-6BF3-36DB-9286-C42708123F18} - {0EAC292A-EAC0-3C04-9776-AA439EE69493} + {0974FCD6-8409-3FA3-A118-A58ED0FD041C} - {1B92AFE8-7559-338C-9B27-68248D7AF766} + {683D025C-B830-3477-9275-4EA8A76EDB09} - {D805E6E3-877C-379F-9EEB-B64746DE0E67} + {2F64C9BE-5116-3E71-971F-592C0D76A89D} diff --git a/Build/libHttpClient.142.UWP.C/libHttpClient.142.UWP.C.vcxproj b/Build/libHttpClient.142.UWP.C/libHttpClient.142.UWP.C.vcxproj index 0825dca6..ab108591 100644 --- a/Build/libHttpClient.142.UWP.C/libHttpClient.142.UWP.C.vcxproj +++ b/Build/libHttpClient.142.UWP.C/libHttpClient.142.UWP.C.vcxproj @@ -169,6 +169,7 @@ + @@ -188,4 +189,4 @@ - + \ No newline at end of file diff --git a/Build/libHttpClient.142.UWP.C/libHttpClient.142.UWP.C.vcxproj.filters b/Build/libHttpClient.142.UWP.C/libHttpClient.142.UWP.C.vcxproj.filters index 838ccfd8..0adb352f 100644 --- a/Build/libHttpClient.142.UWP.C/libHttpClient.142.UWP.C.vcxproj.filters +++ b/Build/libHttpClient.142.UWP.C/libHttpClient.142.UWP.C.vcxproj.filters @@ -159,6 +159,9 @@ C++ Source\Task + + C++ Source\Task + C++ Source\Task @@ -201,46 +204,46 @@ - {A740C412-FB5D-3AAA-8C89-79F901BDFCAA} + {AB68AAD7-BF0D-34C3-BFBA-FB7594810503} - {27CFE030-22C9-3F80-BE9E-6C85269BA8CF} + {8B9EEA6A-3165-3A83-8CEB-648270B8C445} - {DFE90BE8-BA04-38D4-B957-489922F25F6C} + {28B59420-8446-3F26-9AB1-72123FF92816} - {3A5140CA-AFB5-3F07-934D-A8067647C112} + {A616C757-EB09-3284-8BA4-4C0B391C1672} - {4C46FCD2-8942-35E4-A9F6-E32517A218FC} + {7607E023-D56D-300A-AAE7-35C1337C6EF3} - {9BC3BA0D-F317-3F19-87DD-76A8B0C73BEF} + {E77621FA-48B7-342B-A48A-7652D1F73D57} - {F9631A84-9D6B-300A-B12D-A52D1FCE33D5} + {658327C4-885D-3CF1-9061-DA642B97898A} - {42447043-740A-346F-BF7B-15D083F4D78C} + {89A6373D-D2E3-3B5B-B6CF-4AB7CDFB13B8} - {7E7975B0-87A8-3B63-AC39-F4B973EBFE5E} + {57AC3982-96A9-3B0B-A7DF-8DF69AD19ECC} - {A5C44A5C-F557-3485-B0F3-96924ED16997} + {075BEBE6-800C-3FB2-84E2-6443C472E199} - {4075F186-2D52-323A-9287-AE69359740B8} + {D0B227E9-0B82-3AF7-82EC-9581DA9FD1A3} - {0EAC292A-EAC0-3C04-9776-AA439EE69493} + {0974FCD6-8409-3FA3-A118-A58ED0FD041C} - {DA11A23D-43F8-30E7-A583-24AACCBE0501} + {E4F43182-9F97-3EBF-8BB9-4E51EB7E5C4D} - {D805E6E3-877C-379F-9EEB-B64746DE0E67} + {2F64C9BE-5116-3E71-971F-592C0D76A89D} diff --git a/Build/libHttpClient.142.Win32.C/libHttpClient.142.Win32.C.vcxproj b/Build/libHttpClient.142.Win32.C/libHttpClient.142.Win32.C.vcxproj index f1b6503a..f440f496 100644 --- a/Build/libHttpClient.142.Win32.C/libHttpClient.142.Win32.C.vcxproj +++ b/Build/libHttpClient.142.Win32.C/libHttpClient.142.Win32.C.vcxproj @@ -150,6 +150,7 @@ + diff --git a/Build/libHttpClient.142.Win32.C/libHttpClient.142.Win32.C.vcxproj.filters b/Build/libHttpClient.142.Win32.C/libHttpClient.142.Win32.C.vcxproj.filters index 913e1808..a547ce5b 100644 --- a/Build/libHttpClient.142.Win32.C/libHttpClient.142.Win32.C.vcxproj.filters +++ b/Build/libHttpClient.142.Win32.C/libHttpClient.142.Win32.C.vcxproj.filters @@ -138,6 +138,9 @@ C++ Source\Task + + C++ Source\Task + C++ Source\Task @@ -183,46 +186,46 @@ - {A740C412-FB5D-3AAA-8C89-79F901BDFCAA} + {AB68AAD7-BF0D-34C3-BFBA-FB7594810503} - {27CFE030-22C9-3F80-BE9E-6C85269BA8CF} + {8B9EEA6A-3165-3A83-8CEB-648270B8C445} - {DFE90BE8-BA04-38D4-B957-489922F25F6C} + {28B59420-8446-3F26-9AB1-72123FF92816} - {3A5140CA-AFB5-3F07-934D-A8067647C112} + {A616C757-EB09-3284-8BA4-4C0B391C1672} - {4C46FCD2-8942-35E4-A9F6-E32517A218FC} + {7607E023-D56D-300A-AAE7-35C1337C6EF3} - {B97A6D17-B3B3-3752-AFCE-0EC15FD7B2D2} + {6095F35A-0F36-3DA9-8D1A-8F2DA598B6E0} - {F9631A84-9D6B-300A-B12D-A52D1FCE33D5} + {658327C4-885D-3CF1-9061-DA642B97898A} - {42447043-740A-346F-BF7B-15D083F4D78C} + {89A6373D-D2E3-3B5B-B6CF-4AB7CDFB13B8} - {7E7975B0-87A8-3B63-AC39-F4B973EBFE5E} + {57AC3982-96A9-3B0B-A7DF-8DF69AD19ECC} - {A5C44A5C-F557-3485-B0F3-96924ED16997} + {075BEBE6-800C-3FB2-84E2-6443C472E199} - {4075F186-2D52-323A-9287-AE69359740B8} + {D0B227E9-0B82-3AF7-82EC-9581DA9FD1A3} - {0EAC292A-EAC0-3C04-9776-AA439EE69493} + {0974FCD6-8409-3FA3-A118-A58ED0FD041C} - {35985716-80A2-3783-AE12-674A2189F43D} + {350A88A7-908E-346C-8EEC-8C500141D764} - {D805E6E3-877C-379F-9EEB-B64746DE0E67} + {2F64C9BE-5116-3E71-971F-592C0D76A89D} diff --git a/Build/libHttpClient.142.XDK.C/libHttpClient.142.XDK.C.vcxproj b/Build/libHttpClient.142.XDK.C/libHttpClient.142.XDK.C.vcxproj index ad140064..950f26ed 100644 --- a/Build/libHttpClient.142.XDK.C/libHttpClient.142.XDK.C.vcxproj +++ b/Build/libHttpClient.142.XDK.C/libHttpClient.142.XDK.C.vcxproj @@ -12,7 +12,7 @@ title - {9E0EB8C2-40CD-448F-9B98-DAF9830EF9AD} + {57C681F7-280F-45B8-9FA9-D556C24F2053} Win32Proj libHttpClient.142.XDK.C xbox.httpclient diff --git a/Build/libHttpClient.142.XDK.C/libHttpClient.142.XDK.C.vcxproj.filters b/Build/libHttpClient.142.XDK.C/libHttpClient.142.XDK.C.vcxproj.filters index c830da69..4a982d7a 100644 --- a/Build/libHttpClient.142.XDK.C/libHttpClient.142.XDK.C.vcxproj.filters +++ b/Build/libHttpClient.142.XDK.C/libHttpClient.142.XDK.C.vcxproj.filters @@ -201,46 +201,46 @@ - {A740C412-FB5D-3AAA-8C89-79F901BDFCAA} + {AB68AAD7-BF0D-34C3-BFBA-FB7594810503} - {27CFE030-22C9-3F80-BE9E-6C85269BA8CF} + {8B9EEA6A-3165-3A83-8CEB-648270B8C445} - {DFE90BE8-BA04-38D4-B957-489922F25F6C} + {28B59420-8446-3F26-9AB1-72123FF92816} - {3A5140CA-AFB5-3F07-934D-A8067647C112} + {A616C757-EB09-3284-8BA4-4C0B391C1672} - {4C46FCD2-8942-35E4-A9F6-E32517A218FC} + {7607E023-D56D-300A-AAE7-35C1337C6EF3} - {9BC3BA0D-F317-3F19-87DD-76A8B0C73BEF} + {E77621FA-48B7-342B-A48A-7652D1F73D57} - {F9631A84-9D6B-300A-B12D-A52D1FCE33D5} + {658327C4-885D-3CF1-9061-DA642B97898A} - {42447043-740A-346F-BF7B-15D083F4D78C} + {89A6373D-D2E3-3B5B-B6CF-4AB7CDFB13B8} - {7E7975B0-87A8-3B63-AC39-F4B973EBFE5E} + {57AC3982-96A9-3B0B-A7DF-8DF69AD19ECC} - {A5C44A5C-F557-3485-B0F3-96924ED16997} + {075BEBE6-800C-3FB2-84E2-6443C472E199} - {4075F186-2D52-323A-9287-AE69359740B8} + {D0B227E9-0B82-3AF7-82EC-9581DA9FD1A3} - {0EAC292A-EAC0-3C04-9776-AA439EE69493} + {0974FCD6-8409-3FA3-A118-A58ED0FD041C} - {DA11A23D-43F8-30E7-A583-24AACCBE0501} + {E4F43182-9F97-3EBF-8BB9-4E51EB7E5C4D} - {D805E6E3-877C-379F-9EEB-B64746DE0E67} + {2F64C9BE-5116-3E71-971F-592C0D76A89D} diff --git a/Build/libHttpClient.UnitTest.141.TAEF/libHttpClient.UnitTest.141.TAEF.vcxproj b/Build/libHttpClient.UnitTest.141.TAEF/libHttpClient.UnitTest.141.TAEF.vcxproj index 27a0b021..2374c254 100644 --- a/Build/libHttpClient.UnitTest.141.TAEF/libHttpClient.UnitTest.141.TAEF.vcxproj +++ b/Build/libHttpClient.UnitTest.141.TAEF/libHttpClient.UnitTest.141.TAEF.vcxproj @@ -157,6 +157,7 @@ + diff --git a/Build/libHttpClient.UnitTest.141.TAEF/libHttpClient.UnitTest.141.TAEF.vcxproj.filters b/Build/libHttpClient.UnitTest.141.TAEF/libHttpClient.UnitTest.141.TAEF.vcxproj.filters index dafddb5c..00377e1a 100644 --- a/Build/libHttpClient.UnitTest.141.TAEF/libHttpClient.UnitTest.141.TAEF.vcxproj.filters +++ b/Build/libHttpClient.UnitTest.141.TAEF/libHttpClient.UnitTest.141.TAEF.vcxproj.filters @@ -159,6 +159,9 @@ C++ Source\Task + + C++ Source\Task + C++ Source\Task diff --git a/Build/libHttpClient.UnitTest.141.TE/libHttpClient.UnitTest.141.TE.vcxproj b/Build/libHttpClient.UnitTest.141.TE/libHttpClient.UnitTest.141.TE.vcxproj index 086a3200..65b78655 100644 --- a/Build/libHttpClient.UnitTest.141.TE/libHttpClient.UnitTest.141.TE.vcxproj +++ b/Build/libHttpClient.UnitTest.141.TE/libHttpClient.UnitTest.141.TE.vcxproj @@ -221,6 +221,7 @@ + diff --git a/Build/libHttpClient.UnitTest.141.TE/libHttpClient.UnitTest.141.TE.vcxproj.filters b/Build/libHttpClient.UnitTest.141.TE/libHttpClient.UnitTest.141.TE.vcxproj.filters index 7bea9fd4..49282e03 100644 --- a/Build/libHttpClient.UnitTest.141.TE/libHttpClient.UnitTest.141.TE.vcxproj.filters +++ b/Build/libHttpClient.UnitTest.141.TE/libHttpClient.UnitTest.141.TE.vcxproj.filters @@ -156,6 +156,9 @@ C++ Source\Task + + C++ Source\Task + C++ Source\Task diff --git a/Build/libHttpClient.UnitTest.142.TAEF/libHttpClient.UnitTest.142.TAEF.vcxproj b/Build/libHttpClient.UnitTest.142.TAEF/libHttpClient.UnitTest.142.TAEF.vcxproj index 8d530c52..207cab26 100644 --- a/Build/libHttpClient.UnitTest.142.TAEF/libHttpClient.UnitTest.142.TAEF.vcxproj +++ b/Build/libHttpClient.UnitTest.142.TAEF/libHttpClient.UnitTest.142.TAEF.vcxproj @@ -20,7 +20,7 @@ libHttpClient.UnitTest.142.TAEF - {EBFE0314-0869-46B8-ABCE-A3EE223893B8} + {9E0EB8C2-40CD-448F-9B98-DAF9830EF9AD} libHttpClient.Test 10.0.17763.0 10.0.10240.0 @@ -145,6 +145,7 @@ + diff --git a/Build/libHttpClient.UnitTest.142.TAEF/libHttpClient.UnitTest.142.TAEF.vcxproj.filters b/Build/libHttpClient.UnitTest.142.TAEF/libHttpClient.UnitTest.142.TAEF.vcxproj.filters index dafddb5c..00377e1a 100644 --- a/Build/libHttpClient.UnitTest.142.TAEF/libHttpClient.UnitTest.142.TAEF.vcxproj.filters +++ b/Build/libHttpClient.UnitTest.142.TAEF/libHttpClient.UnitTest.142.TAEF.vcxproj.filters @@ -159,6 +159,9 @@ C++ Source\Task + + C++ Source\Task + C++ Source\Task diff --git a/Build/libHttpClient.UnitTest.142.TE/libHttpClient.UnitTest.142.TE.vcxproj b/Build/libHttpClient.UnitTest.142.TE/libHttpClient.UnitTest.142.TE.vcxproj index 0974375b..8f069ff2 100644 --- a/Build/libHttpClient.UnitTest.142.TE/libHttpClient.UnitTest.142.TE.vcxproj +++ b/Build/libHttpClient.UnitTest.142.TE/libHttpClient.UnitTest.142.TE.vcxproj @@ -221,6 +221,7 @@ + diff --git a/Build/libHttpClient.UnitTest.142.TE/libHttpClient.UnitTest.142.TE.vcxproj.filters b/Build/libHttpClient.UnitTest.142.TE/libHttpClient.UnitTest.142.TE.vcxproj.filters index 7bea9fd4..49282e03 100644 --- a/Build/libHttpClient.UnitTest.142.TE/libHttpClient.UnitTest.142.TE.vcxproj.filters +++ b/Build/libHttpClient.UnitTest.142.TE/libHttpClient.UnitTest.142.TE.vcxproj.filters @@ -156,6 +156,9 @@ C++ Source\Task + + C++ Source\Task + C++ Source\Task diff --git a/Include/XAsyncProvider.h b/Include/XAsyncProvider.h index c44a5bc8..65dd752e 100644 --- a/Include/XAsyncProvider.h +++ b/Include/XAsyncProvider.h @@ -113,36 +113,6 @@ STDAPI XAsyncBegin( _In_ XAsyncProvider* provider ) noexcept; -/// -/// Initializes an async block for use. Once begun calls such -/// as XAsyncGetStatus will provide meaningful data. It is assumed the -/// async work will begin on some system defined thread after this call -/// returns. The token and function parameters can be used to help identify -/// mismatched Begin/GetResult calls. The token is typically the function -/// pointer of the async API you are implementing, and the functionName parameter -/// is typically the __FUNCTION__ compiler macro. -/// -/// This variant of XAsyncBegin will allocate additional memory of size contextSize -/// and use this as the context pointer for async provider callbacks. The memory -/// pointer is returned in 'context'. The lifetime of this memory is managed -/// by the async library and will be freed automatically when the call -/// completes. -/// -/// A pointer to the XAsyncBlock that holds data for the call. -/// An optional arbitrary pointer that can be used to identify this call. -/// An optional string that names the async call. This is typically the __FUNCTION__ compiler macro. -/// The function callback to invoke to implement the async call. -/// The size, in bytes, of additional context memory to allocate. -/// The allocated context object pointer. -STDAPI XAsyncBeginAlloc( - _Inout_ XAsyncBlock* asyncBlock, - _In_opt_ const void* identity, - _In_opt_ const char* identityName, - _In_ XAsyncProvider* provider, - _In_ size_t contextSize, - _Out_ void** context - ) noexcept; - /// /// Schedules a callback to do async work. Calling this is optional. If the async work can be done /// through a system - async mechanism like overlapped IO or async COM, there is no need to schedule diff --git a/Source/Common/ResultMacros.h b/Source/Common/ResultMacros.h index d0a0dfb0..ece5fa18 100644 --- a/Source/Common/ResultMacros.h +++ b/Source/Common/ResultMacros.h @@ -18,3 +18,5 @@ HC_TRACE_ERROR(HTTPCLIENT, fmt, ##__VA_ARGS__); \ ASSERT(false); \ + +#define FAIL_FAST_IF_FAILED(hr) do { HRESULT __hrRet = hr; if (FAILED(__hrRet)) { FAIL_FAST_MSG("%s 0x%08", #hr, __hrRet); }} while (0, 0) \ No newline at end of file diff --git a/Source/Common/pch_common.h b/Source/Common/pch_common.h index 4d63481f..fae6f40a 100644 --- a/Source/Common/pch_common.h +++ b/Source/Common/pch_common.h @@ -27,6 +27,7 @@ #include #include +#include #else #define __STDC_LIMIT_MACROS diff --git a/Source/Task/AsyncLib.cpp b/Source/Task/AsyncLib.cpp index efcaed42..afd35d41 100644 --- a/Source/Task/AsyncLib.cpp +++ b/Source/Task/AsyncLib.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. #include "pch.h" +#include "XTaskQueuePriv.h" #define ASYNC_BLOCK_SIG 0x41535942 // ASYB #define ASYNC_STATE_SIG 0x41535445 // ASTE @@ -9,6 +10,9 @@ // Used by unit tests to verify we cleanup memory correctly. std::atomic s_AsyncLibGlobalStateCount{ 0 }; +// Set externally to enable pumping waits. +bool s_AsyncLibEnablePumpingWait = false; + enum class ProviderCleanupLocation { Destructor, @@ -42,6 +46,10 @@ struct AsyncState std::condition_variable waitCondition; bool waitSatisfied = false; +#if _WIN32 + HANDLE waitEvent = nullptr; +#endif + const void* identity = nullptr; const char* identityName = nullptr; @@ -89,9 +97,17 @@ private: if (queue != nullptr) { + XTaskQueueResumeTermination(queue); XTaskQueueCloseHandle(queue); } +#if _WIN32 + if (waitEvent != nullptr) + { + CloseHandle(waitEvent); + } +#endif + --s_AsyncLibGlobalStateCount; } }; @@ -339,8 +355,8 @@ private: static void CALLBACK CompletionCallback(_In_ void* context, _In_ bool canceled); static void CALLBACK WorkerCallback(_In_ void* context, _In_ bool canceled); -static void SignalCompletion(_In_ AsyncStateRef const& state); static void SignalWait(_In_ AsyncStateRef const& state); +static HRESULT SignalCompletion(_In_ AsyncStateRef const& state); static HRESULT AllocStateNoCompletion(_Inout_ XAsyncBlock* asyncBlock, _Inout_ AsyncBlockInternal* internal, _In_ size_t contextSize); static HRESULT AllocState(_Inout_ XAsyncBlock* asyncBlock, _In_ size_t contextSize); static void CleanupState(_Inout_ AsyncStateRef&& state); @@ -368,9 +384,16 @@ static HRESULT AllocStateNoCompletion(_Inout_ XAsyncBlock* asyncBlock, _Inout_ A { RETURN_IF_FAILED(XTaskQueueDuplicateHandle(queue, &state->queue)); } - + + RETURN_IF_FAILED(XTaskQueueSuspendTermination(state->queue)); + state->userAsyncBlock = asyncBlock; state->providerData.async = &state->providerAsyncBlock; + +#if _WIN32 + state->waitEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + RETURN_LAST_ERROR_IF(state->waitEvent == nullptr); +#endif internal->state = state.Detach(); @@ -437,12 +460,14 @@ static void CleanupProviderForLocation(_Inout_ AsyncStateRef& state, _In_ Provid } } -static void SignalCompletion(_In_ AsyncStateRef const& state) +static HRESULT SignalCompletion(_In_ AsyncStateRef const& state) { + HRESULT hr = S_OK; + if (state->providerData.async->callback != nullptr) { AsyncStateRef callbackState(state.Get()); - HRESULT hr = XTaskQueueSubmitCallback( + hr = XTaskQueueSubmitCallback( state->queue, XTaskQueuePort::Completion, callbackState.Get(), @@ -457,6 +482,8 @@ static void SignalCompletion(_In_ AsyncStateRef const& state) { SignalWait(state); } + + return hr; } static void SignalWait(_In_ AsyncStateRef const& state) @@ -465,6 +492,9 @@ static void SignalWait(_In_ AsyncStateRef const& state) std::lock_guard lock(state->waitMutex); state->waitSatisfied = true; } +#if _WIN32 + SetEvent(state->waitEvent); +#endif state->waitCondition.notify_all(); } @@ -501,8 +531,6 @@ static void CALLBACK WorkerCallback( return; } - bool completedNow = false; - // If the queue is canceling callbacks, simply cancel this work. Since no // new work for this call will be scheduled, if the call didn't cancel // immediately do it ourselves. @@ -536,20 +564,7 @@ static void CALLBACK WorkerCallback( result = E_UNEXPECTED; } - { - AsyncBlockInternalGuard internal{ &state->providerAsyncBlock }; - completedNow = internal.TrySetTerminalStatus(result); - - if (completedNow) - { - internal.ExtractState(); - } - } - - if (completedNow) - { - SignalCompletion(state); - } + XAsyncComplete(&state->providerAsyncBlock, result, 0); } } @@ -557,11 +572,6 @@ static void CALLBACK WorkerCallback( // will change the provider cleanup to be "AfterWork", which is here. Cleanup // the provider if we need to. CleanupProviderForLocation(state, ProviderCleanupLocation::AfterDoWork); - - if (completedNow) - { - CleanupState(std::move(state)); - } } // @@ -600,6 +610,27 @@ STDAPI XAsyncGetStatus( } else { +// This codebase compiles for multiple platforms on GitHub. This is only +// supported on win32 desktop platforms. It is enabled for Windows builds. +#if _WIN32 +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + + // If we're being asked to wait from a STA thread, use a CoWait to ensure we + // don't totally gum up the thread. Then fall back to our normal condition + // variable check (both are signaled during completion). + + if (s_AsyncLibEnablePumpingWait) + { + APTTYPE aptType; + APTTYPEQUALIFIER aptQualifier; + if (SUCCEEDED(CoGetApartmentType(&aptType, &aptQualifier)) && aptType != APTTYPE_MTA && aptType != APTTYPE_NA) + { + DWORD idx; + (void)CoWaitForMultipleHandles(COWAIT_DEFAULT, INFINITE, 1, &state->waitEvent, &idx); + } + } +#endif +#endif { std::unique_lock lock(state->waitMutex); @@ -610,7 +641,10 @@ STDAPI XAsyncGetStatus( } } - result = XAsyncGetStatus(asyncBlock, false); + { + AsyncBlockInternalGuard internal{ asyncBlock }; + result = internal.GetStatus(); + } } } @@ -673,16 +707,30 @@ STDAPI XAsyncRun( __FUNCTION__, [](XAsyncOp op, const XAsyncProviderData* data) { - if (op == XAsyncOp::DoWork) + switch (op) { - XAsyncWork* work = reinterpret_cast(data->context); - HRESULT hr = work(data->async); - XAsyncComplete(data->async, hr, 0); + case XAsyncOp::Begin: + return XAsyncSchedule(data->async, 0); + + case XAsyncOp::DoWork: + { + XAsyncWork* work = reinterpret_cast(data->context); + HRESULT hr = work(data->async); + XAsyncComplete(data->async, hr, 0); + } + break; + + case XAsyncOp::Cancel: + case XAsyncOp::Cleanup: + case XAsyncOp::GetResult: + break; + } + return S_OK; })); - RETURN_HR(XAsyncSchedule(asyncBlock, 0)); + return S_OK; } // @@ -718,7 +766,9 @@ STDAPI XAsyncBegin( // We've successfully setup the call. Now kick off a // Begin opcode. If this call fails, we use it to fail - // the async call, instead of failing XAsyncBegin. + // the async call, instead of failing XAsyncBegin. This is + // necessary to ensure that the async call state is properly + // cleaned up, both for us and for the user call. HRESULT hr = provider(XAsyncOp::Begin, &state->providerData); if (FAILED(hr)) @@ -750,10 +800,21 @@ STDAPI XAsyncBeginAlloc( _In_opt_ const char* identityName, _In_ XAsyncProvider* provider, _In_ size_t contextSize, - _Out_ void** context + _In_ size_t parameterBlockSize, + _In_opt_ void* parameterBlock ) noexcept { RETURN_HR_IF(E_INVALIDARG, contextSize == 0); + + if (parameterBlockSize != 0) + { + RETURN_HR_IF(E_INVALIDARG, parameterBlock == nullptr || parameterBlockSize > contextSize); + } + else + { + RETURN_HR_IF(E_INVALIDARG, parameterBlock != nullptr); + } + RETURN_IF_FAILED(AllocState(asyncBlock, contextSize)); AsyncStateRef state; @@ -772,13 +833,20 @@ STDAPI XAsyncBeginAlloc( ASSERT(state->providerData.context != nullptr); memset(state->providerData.context, 0, contextSize); - *context = state->providerData.context; + + if (parameterBlockSize != 0) + { + memcpy(state->providerData.context, parameterBlock, parameterBlockSize); + } // We've successfully setup the call. Now kick off a // Begin opcode. If this call fails, we use it to fail - // the async call, instead of failing XAsyncBegin. - + // the async call, instead of failing XAsyncBegin. This is + // necessary to ensure that the async call state is properly + // cleaned up, both for us and for the user call. + HRESULT hr = provider(XAsyncOp::Begin, &state->providerData); + if (FAILED(hr)) { XAsyncComplete(asyncBlock, hr, 0); @@ -865,7 +933,7 @@ STDAPI_(void) XAsyncComplete( // clean up now. Also clean up if the call failed. The caller should // have passed zero in here in that case but be tolerant for cases like // sizeof(). - if (requiredBufferSize == 0 || FAILED(result)) + if ((requiredBufferSize == 0 || FAILED(result)) && completedNow) { // If we are going to cleanup steal the reference from the block. doCleanup = true; @@ -886,7 +954,7 @@ STDAPI_(void) XAsyncComplete( // Only signal / adjust needed buffer size if we were first to complete. if (completedNow) { - SignalCompletion(state); + FAIL_FAST_IF_FAILED(SignalCompletion(state)); } // At this point asyncBlock may be unsafe to touch. As we've cleaned up diff --git a/Source/Task/TaskQueue.cpp b/Source/Task/TaskQueue.cpp index c34ef91f..40991cf0 100644 --- a/Source/Task/TaskQueue.cpp +++ b/Source/Task/TaskQueue.cpp @@ -271,6 +271,9 @@ HRESULT TaskQueuePortImpl::Initialize( m_terminationList.reset(new (std::nothrow) LocklessQueue(0)); RETURN_IF_NULL_ALLOC(m_terminationList); + m_pendingTerminationList.reset(new (std::nothrow) LocklessQueue(*m_terminationList.get())); + RETURN_IF_NULL_ALLOC(m_pendingTerminationList); + RETURN_IF_FAILED(m_timer.Initialize(this, [](void* context) { TaskQueuePortImpl* pthis = static_cast(context); @@ -509,9 +512,31 @@ void __stdcall TaskQueuePortImpl::Terminate( _In_ void* token) { TerminationEntry* term = static_cast(token); - + + // Prevent anything else from coming into the queue + term->portContext->SetStatus(TaskQueuePortStatus::Terminated); + CancelPendingEntries(term->portContext, true); + // Are there existing suspends? AddSuspend returns + // true if this is the first suspend added. + if (term->portContext->AddSuspend()) + { + ScheduleTermination(term); + } + else + { + m_pendingTerminationList->push_back(term, term->node); + term->node = 0; + } + + // Balance our add + term->portContext->RemoveSuspend(); +} + +void __stdcall TaskQueuePortImpl::ScheduleTermination( + _In_ TerminationEntry* term) +{ // Insert the termination callback into the queue. Even if the // main queue is empty, we still signal it and run through // a cycle. This ensures we flush the queue out with no @@ -529,7 +554,6 @@ void __stdcall TaskQueuePortImpl::Terminate( // We will not signal until we are marked as terminated. The queue could // still be moving while we are running this terminate call. - term->portContext->SetStatus(TaskQueuePortStatus::Terminated); SignalQueue(); // We must ensure we poke the queue threads in case there's @@ -683,6 +707,42 @@ bool __stdcall TaskQueuePortImpl::IsEmpty() return empty; } +HRESULT __stdcall TaskQueuePortImpl::SuspendTermination( + _In_ ITaskQueuePortContext* portContext + ) +{ + // This is necessary to ensure there is no race. + portContext->AddSuspend(); + HRESULT hr = VerifyNotTerminated(portContext); + + if (FAILED(hr)) + { + portContext->RemoveSuspend(); + RETURN_HR(hr); + } + + return S_OK; +} + +void __stdcall TaskQueuePortImpl::ResumeTermination( + _In_ ITaskQueuePortContext* portContext) +{ + if (portContext->RemoveSuspend()) + { + // Removed the last external callback. Look for + // parked terminations and reschedule them. + + TerminationEntry *entry; + uint64_t address; + + while (m_pendingTerminationList->pop_front(entry, address)) + { + entry->node = address; + ScheduleTermination(entry); + } + } +} + HRESULT TaskQueuePortImpl::VerifyNotTerminated( _In_ ITaskQueuePortContext* portContext) { @@ -1160,6 +1220,34 @@ void __stdcall TaskQueuePortContextImpl::ItemQueued() m_submitCallback->Invoke(m_type); } +bool __stdcall TaskQueuePortContextImpl::AddSuspend() +{ + return (m_suspendCount.fetch_add(1) == 0); +} + +bool __stdcall TaskQueuePortContextImpl::RemoveSuspend() +{ + for(;;) + { + uint32_t current = m_suspendCount.load(); + + // These should always be balanced and there is no + // valid case where this should happen, even + // for multiple therads racing. + + if (current == 0) + { + ASSERT(false); + return true; + } + + if (m_suspendCount.compare_exchange_weak(current, current -1)) + { + return current == 1; + } + } +} + // // TaskQueueImpl // @@ -1613,7 +1701,8 @@ STDAPI XTaskQueueSubmitDelayedCallback( referenced_ptr portContext; RETURN_IF_FAILED(aq->GetPortContext(port, portContext.address_of())); - RETURN_HR(portContext->GetPort()->QueueItem(portContext.get(), delayMs, callbackContext, callback)); + RETURN_IF_FAILED(portContext->GetPort()->QueueItem(portContext.get(), delayMs, callbackContext, callback)); + return S_OK; } // @@ -1691,7 +1780,8 @@ STDAPI XTaskQueueRegisterMonitor( { referenced_ptr aq(GetQueue(queue)); RETURN_HR_IF(E_INVALIDARG, aq == nullptr); - RETURN_HR(aq->RegisterSubmitCallback(callbackContext, callback, token)); + RETURN_IF_FAILED(aq->RegisterSubmitCallback(callbackContext, callback, token)); + return S_OK; } // @@ -1795,3 +1885,45 @@ STDAPI_(void) XTaskQueueSetCurrentProcessTaskQueue( XTaskQueueCloseHandle(previous); } } + +// +// Submits a callback that will be invoked asynchronously via some external +// system. This is used to prevent queue termination while other work +// is happening. +// +STDAPI XTaskQueueSuspendTermination( + _In_ XTaskQueueHandle queue + ) noexcept +{ + referenced_ptr aq(GetQueue(queue)); + RETURN_HR_IF(E_INVALIDARG, aq == nullptr); + + referenced_ptr portContext; + RETURN_IF_FAILED(aq->GetPortContext(XTaskQueuePort::Work, portContext.address_of())); + + RETURN_IF_FAILED(portContext->GetPort()->SuspendTermination(portContext.get())); + return S_OK; +} + +// +// Tells the queue that a previously submitted external callback +// has been dispatched. +// +STDAPI_(void) XTaskQueueResumeTermination( + _In_ XTaskQueueHandle queue + ) noexcept +{ + referenced_ptr aq(GetQueue(queue)); + if (aq == nullptr) + { + return; + } + + referenced_ptr portContext; + if (FAILED(aq->GetPortContext(XTaskQueuePort::Work, portContext.address_of()))) + { + return; + } + + portContext->GetPort()->ResumeTermination(portContext.get()); +} diff --git a/Source/Task/TaskQueueImpl.h b/Source/Task/TaskQueueImpl.h index 5e85d15b..26fb59a8 100644 --- a/Source/Task/TaskQueueImpl.h +++ b/Source/Task/TaskQueueImpl.h @@ -202,6 +202,12 @@ public: bool __stdcall IsEmpty(); + HRESULT __stdcall SuspendTermination( + _In_ ITaskQueuePortContext* portContext); + + void __stdcall ResumeTermination( + _In_ ITaskQueuePortContext* portContext); + private: struct WaitRegistration; @@ -246,6 +252,7 @@ private: std::unique_ptr> m_queueList; std::unique_ptr> m_pendingList; std::unique_ptr> m_terminationList; + std::unique_ptr> m_pendingTerminationList; OS::WaitTimer m_timer; OS::ThreadPool m_threadPool; std::atomic m_timerDue = { UINT64_MAX }; @@ -285,6 +292,7 @@ private: void SubmitPendingCallback(); void SignalTerminations(); + void ScheduleTermination(_In_ TerminationEntry* term); void SignalQueue(); @@ -339,6 +347,9 @@ public: void __stdcall ItemQueued() override; + bool __stdcall AddSuspend() override; + bool __stdcall RemoveSuspend() override; + referenced_ptr Port; referenced_ptr Source; @@ -348,6 +359,7 @@ private: XTaskQueuePort m_type = XTaskQueuePort::Work; SubmitCallback* m_submitCallback = nullptr; std::atomic m_status = { TaskQueuePortStatus::Active }; + std::atomic m_suspendCount = { 0 }; }; class TaskQueueImpl : public Api diff --git a/Source/Task/TaskQueueP.h b/Source/Task/TaskQueueP.h index 6230c054..a6961639 100644 --- a/Source/Task/TaskQueueP.h +++ b/Source/Task/TaskQueueP.h @@ -67,12 +67,18 @@ struct ITaskQueuePort: IApi _In_ ITaskQueuePortContext* portContext) = 0; virtual bool __stdcall DrainOneItem() = 0; - + virtual bool __stdcall Wait( _In_ ITaskQueuePortContext* portContext, _In_ uint32_t timeout) = 0; virtual bool __stdcall IsEmpty() = 0; + + virtual HRESULT __stdcall SuspendTermination( + _In_ ITaskQueuePortContext* portContext) = 0; + + virtual void __stdcall ResumeTermination( + _In_ ITaskQueuePortContext* portContext) = 0; }; // The status of a port on the queue. @@ -102,6 +108,9 @@ struct ITaskQueuePortContext : IApi _In_ TaskQueuePortStatus status) = 0; virtual void __stdcall ItemQueued() = 0; + + virtual bool __stdcall AddSuspend() = 0; + virtual bool __stdcall RemoveSuspend() = 0; }; // The task queue. The public flat API is built entirely on diff --git a/Source/Task/XAsyncProviderPriv.h b/Source/Task/XAsyncProviderPriv.h new file mode 100644 index 00000000..21ba2c79 --- /dev/null +++ b/Source/Task/XAsyncProviderPriv.h @@ -0,0 +1,39 @@ +// Copyright(c) Microsoft Corporation. All rights reserved. +// +// These APIs should be reserved for driving unit test harnesses. + +#pragma once + +#include "XAsyncProvider.h" + +/// +/// Initializes an async block for use. Once begun calls such +/// as XAsyncGetStatus will provide meaningful data. It is assumed the +/// async work will begin on some system defined thread after this call +/// returns. The token and function parameters can be used to help identify +/// mismatched Begin/GetResult calls. The token is typically the function +/// pointer of the async API you are implementing, and the functionName parameter +/// is typically the __FUNCTION__ compiler macro. +/// +/// This variant of XAsyncBegin will allocate additional memory of size contextSize +/// and use this as the context pointer for async provider callbacks. The memory +/// pointer is returned in 'context'. The lifetime of this memory is managed +/// by the async library and will be freed automatically when the call +/// completes. +/// +/// A pointer to the XAsyncBlock that holds data for the call. +/// An optional arbitrary pointer that can be used to identify this call. +/// An optional string that names the async call. This is typically the __FUNCTION__ compiler macro. +/// The function callback to invoke to implement the async call. +/// The size, in bytes, of additional context memory to allocate. +/// The size of a parameter block to copy into the allocated context. +/// The parameter block to copy. This will be copied into the allocated context. +STDAPI XAsyncBeginAlloc( + _Inout_ XAsyncBlock* asyncBlock, + _In_opt_ const void* identity, + _In_opt_ const char* identityName, + _In_ XAsyncProvider* provider, + _In_ size_t contextSize, + _In_ size_t parameterBlockSize, + _In_opt_ void* parameterBlock + ) noexcept; diff --git a/Source/Task/XTaskQueuePriv.h b/Source/Task/XTaskQueuePriv.h index 23d2fa62..648580fc 100644 --- a/Source/Task/XTaskQueuePriv.h +++ b/Source/Task/XTaskQueuePriv.h @@ -14,4 +14,23 @@ /// The port to check. STDAPI_(bool) XTaskQueueIsEmpty( _In_ XTaskQueueHandle queue, - _In_ XTaskQueuePort port); + _In_ XTaskQueuePort port + ) noexcept; + +/// +/// Suspends terminations on the task queue. May return an error if +/// the queue is already terminated. +/// +/// The queue to suspend terminations. +STDAPI XTaskQueueSuspendTermination( + _In_ XTaskQueueHandle queue + ) noexcept; + +/// +/// Resumes the ability to terminate the task queue. If a termination was +/// attempted it will be continued. +/// +/// The queue resume terminations. +STDAPI_(void) XTaskQueueResumeTermination( + _In_ XTaskQueueHandle queue + ) noexcept; diff --git a/Tests/UnitTests/Tests/AsyncBlockTests.cpp b/Tests/UnitTests/Tests/AsyncBlockTests.cpp index e09e60da..3e71d882 100644 --- a/Tests/UnitTests/Tests/AsyncBlockTests.cpp +++ b/Tests/UnitTests/Tests/AsyncBlockTests.cpp @@ -4,6 +4,7 @@ #include "UnitTestIncludes.h" #include "XAsync.h" #include "XAsyncProvider.h" +#include "XAsyncProviderPriv.h" #include "XTaskQueue.h" #include "XTaskQueuePriv.h" @@ -71,7 +72,7 @@ DEFINE_TEST_CLASS(AsyncBlockTests) private: XTaskQueueHandle queue = nullptr; - + struct FactorialCallData { DWORD value = 0; @@ -219,6 +220,32 @@ private: return S_OK; } + static HRESULT CALLBACK FactorialWorkerDistributedWithSchedule(XAsyncOp opCode, const XAsyncProviderData* data) + { + if (opCode == XAsyncOp::Begin) + { + FactorialCallData* d = (FactorialCallData*)data->context; + + // leak a ref on this guy so we don't try to free it. We need + // to do two addrefs because a new object starts with refcount + // of zero. The factorial async process will addref/release so + // we need two to "leak" it (not really leaked; the memory is + // owned by the async logic) + + d->AddRef(); + d->AddRef(); + } + + HRESULT hr = FactorialWorkerDistributed(opCode, data); + + if (SUCCEEDED(hr) && opCode == XAsyncOp::Begin) + { + hr = XAsyncSchedule(data->async, 0); + } + + return hr; + } + static HRESULT FactorialAsync(FactorialCallData* data, XAsyncBlock* async) { HRESULT hr = XAsyncBegin(async, data, FactorialAsync, __FUNCTION__, FactorialWorkerSimple); @@ -241,23 +268,7 @@ private: static HRESULT FactorialAllocateAsync(DWORD value, XAsyncBlock* async) { - void* context; - HRESULT hr = XAsyncBeginAlloc(async, FactorialAsync, __FUNCTION__, FactorialWorkerDistributed, sizeof(FactorialCallData), &context); - if (SUCCEEDED(hr)) - { - FactorialCallData* data = new (context) FactorialCallData; - data->value = value; - - // leak a ref on this guy so we don't try to free it. We need - // to do two addrefs because a new object starts with refcount - // of zero. The factorial async process will addref/release so - // we need two to "leak" it (not really leaked; the memory is - // owned by the async logic) - - data->AddRef(); - data->AddRef(); - hr = XAsyncSchedule(async, 0); - } + HRESULT hr = XAsyncBeginAlloc(async, FactorialAsync, __FUNCTION__, FactorialWorkerDistributedWithSchedule, sizeof(FactorialCallData), sizeof(DWORD), &value); return hr; } @@ -342,6 +353,7 @@ public: AsyncBlockTests::~AsyncBlockTests() { VERIFY_ARE_EQUAL(s_AsyncLibGlobalStateCount, (DWORD)0); + XTaskQueueTerminate(queue, true, nullptr, nullptr); XTaskQueueCloseHandle(queue); } @@ -987,4 +999,58 @@ public: VERIFY_SUCCEEDED(XAsyncGetStatus(&async, true)); VERIFY_ARE_EQUAL((DWORD)WAIT_OBJECT_0, WaitForSingleObject(context.evt, 2500)); } + + DEFINE_TEST_CASE(VerifyBeginAfterTerminate) + { + XAsyncBlock async{}; + VERIFY_SUCCEEDED(XTaskQueueCreate(XTaskQueueDispatchMode::ThreadPool, XTaskQueueDispatchMode::ThreadPool, &async.queue)); + + auto provider = [](XAsyncOp, const XAsyncProviderData*) + { + return S_OK; + }; + + // Terminate the queue + XTaskQueueTerminate(async.queue, true, nullptr, nullptr); + + // XAsyncBegin should fail early with an abort. + VERIFY_ARE_EQUAL(E_ABORT, XAsyncBegin(&async, nullptr, nullptr, nullptr, provider)); + + XTaskQueueCloseHandle(async.queue); + } + + DEFINE_TEST_CASE(VerifyFailureInDoWork) + { + XAsyncBlock async{}; + VERIFY_SUCCEEDED(XTaskQueueCreate(XTaskQueueDispatchMode::ThreadPool, XTaskQueueDispatchMode::ThreadPool, &async.queue)); + + constexpr static HRESULT hrSpecial = 0x8009ABCD; + + auto provider = [](XAsyncOp op, const XAsyncProviderData* data) + { + switch(op) + { + case XAsyncOp::Begin: + return XAsyncSchedule(data->async, 0); + + case XAsyncOp::Cleanup: + break; + + case XAsyncOp::DoWork: + return hrSpecial; + + default: + VERIFY_FAIL(); + break; + } + return S_OK; + }; + + // Ensure that the call runs through and correctly reports our special + // error. + VERIFY_SUCCEEDED(XAsyncBegin(&async, nullptr, nullptr, nullptr, provider)); + VERIFY_ARE_EQUAL(hrSpecial, XAsyncGetStatus(&async, true)); + + XTaskQueueCloseHandle(async.queue); + } }; diff --git a/Tests/UnitTests/Tests/TaskQueueTests.cpp b/Tests/UnitTests/Tests/TaskQueueTests.cpp index 199c1aed..bc437107 100644 --- a/Tests/UnitTests/Tests/TaskQueueTests.cpp +++ b/Tests/UnitTests/Tests/TaskQueueTests.cpp @@ -110,6 +110,11 @@ public: TEST_CLASS_CLEANUP(ClassCleanup) { + // + // Note: this is a global refcount for tracking + // leaks in the task queue. If any other tests fail + // this may also fail, as those tests could have leaked. + // uint32_t gr = ApiDiag::g_globalApiRefs; VERIFY_ARE_EQUAL(0u, gr); return true; @@ -1184,4 +1189,28 @@ public: NextStep(&step, 7, L"Closing outer queue"); XTaskQueueCloseHandle(outer.Release()); } + + DEFINE_TEST_CASE(VerifyTerminateWaitsForSuspends) + { + AutoQueueHandle queue; + VERIFY_SUCCEEDED(XTaskQueueCreate(XTaskQueueDispatchMode::ThreadPool, XTaskQueueDispatchMode::ThreadPool, &queue)); + + AutoHandle waitHandle = CreateEvent(nullptr, TRUE, FALSE, nullptr); + VERIFY_IS_NOT_NULL(waitHandle); + + auto terminatedCallback = [](void* context) + { + HANDLE h = (HANDLE)context; + SetEvent(h); + }; + + VERIFY_SUCCEEDED(XTaskQueueSuspendTermination(queue)); + VERIFY_SUCCEEDED(XTaskQueueTerminate(queue, false, waitHandle.Handle(), terminatedCallback)); + + VERIFY_ARE_EQUAL((DWORD)WAIT_TIMEOUT, WaitForSingleObject(waitHandle, 2000)); + + XTaskQueueResumeTermination(queue); + + VERIFY_ARE_EQUAL((DWORD)WAIT_OBJECT_0, WaitForSingleObject(waitHandle, 2000)); + } }; diff --git a/Utilities/CMake/CMakeLists.txt b/Utilities/CMake/CMakeLists.txt index 76b48e8e..515550ac 100644 --- a/Utilities/CMake/CMakeLists.txt +++ b/Utilities/CMake/CMakeLists.txt @@ -107,6 +107,7 @@ set(Task_Source_Files ../../../Source/Task/ThreadPool.h ../../../Source/Task/WaitTimer.h ../../../Source/Task/XTaskQueuePriv.h + ../../../Source/Task/XAsyncProviderPriv.h ) set(Task_Windows_Source_Files