├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── include └── execq │ ├── IExecutionQueue.h │ ├── IExecutionStream.h │ ├── execq.h │ └── internal │ ├── CancelTokenProvider.h │ ├── ExecutionPool.h │ ├── ExecutionQueue.h │ ├── ExecutionStream.h │ ├── TaskProviderList.h │ ├── ThreadWorker.h │ └── execq_private.h ├── src ├── CancelTokenProvider.cpp ├── ExecutionPool.cpp ├── ExecutionStream.cpp ├── TaskProviderList.cpp ├── ThreadWorker.cpp └── execq.cpp └── tests ├── CancelTokenProviderTest.cpp ├── ExecqTestUtil.h ├── ExecutionQueueTest.cpp ├── ExecutionStreamTest.cpp ├── TaskExecutionQueueTest.cpp └── TaskProviderListTest.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdParty/googletest"] 2 | path = 3rdParty/googletest 3 | url = https://github.com/google/googletest.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.2) 2 | 3 | project(execq DESCRIPTION "Threadpool-like implementation that supports task execution in queue-like and stream-like ways.") 4 | 5 | if (NOT WIN32) 6 | add_compile_options(-std=c++11) 7 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 8 | add_compile_options(-stdlib=libc++) 9 | endif() 10 | endif() 11 | 12 | if (APPLE) 13 | set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9") 14 | endif() 15 | 16 | OPTION(EXECQ_TESTING_ENABLE "Build execq's unit-tests." OFF) 17 | 18 | ### execq library ### 19 | 20 | set(LIB_SOURCES 21 | include/execq/IExecutionStream.h 22 | include/execq/IExecutionQueue.h 23 | include/execq/execq.h 24 | 25 | include/execq/internal/execq_private.h 26 | include/execq/internal/ExecutionPool.h 27 | include/execq/internal/ExecutionQueue.h 28 | include/execq/internal/ExecutionStream.h 29 | include/execq/internal/ThreadWorker.h 30 | include/execq/internal/TaskProviderList.h 31 | include/execq/internal/CancelTokenProvider.h 32 | 33 | src/execq.cpp 34 | src/ExecutionPool.cpp 35 | src/ExecutionStream.cpp 36 | src/ThreadWorker.cpp 37 | src/TaskProviderList.cpp 38 | src/CancelTokenProvider.cpp 39 | ) 40 | 41 | add_library(execq STATIC ${LIB_SOURCES}) 42 | 43 | target_include_directories(execq PUBLIC 44 | "include" 45 | ) 46 | 47 | include_directories(execq PRIVATE 48 | "include/execq" 49 | "include/execq/internal" 50 | ) 51 | 52 | 53 | ### execq unit-tests ### 54 | 55 | if (EXECQ_TESTING_ENABLE) 56 | set(TEST_SOURCES 57 | tests/ExecqTestUtil.h 58 | tests/CancelTokenProviderTest.cpp 59 | tests/ExecutionStreamTest.cpp 60 | tests/ExecutionQueueTest.cpp 61 | tests/TaskExecutionQueueTest.cpp 62 | tests/TaskProviderListTest.cpp 63 | ) 64 | add_executable(execq_tests ${TEST_SOURCES}) 65 | 66 | # setup 3rdParty 67 | add_subdirectory(3rdParty/googletest) 68 | set_target_properties(gmock PROPERTIES FOLDER 3rdParty) 69 | set_target_properties(gmock_main PROPERTIES FOLDER 3rdParty) 70 | set_target_properties(gtest PROPERTIES FOLDER 3rdParty) 71 | set_target_properties(gtest_main PROPERTIES FOLDER 3rdParty) 72 | 73 | target_link_libraries(execq_tests execq gtest gmock gmock_main) 74 | endif() 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### execq 2 | **execq** is kind of task-based approach of processing data using threadpool idea with extended features. 3 | It supports different task sources and maintains task execution in parallel on N threads (according to hardware concurrency). 4 | - providers are `queues` and `streams` that allow to execute tasks in different ways 5 | - `queues` provide ability to process the object just 'putting it to the queue' 6 | - supports serial and concurrent `queues` 7 | - work as you like: submitting an object to process on the `queue` returns nonblocking (unlike std::async) std::future that can be used to get result as far as the object is processed 8 | - maintains optimal thread count to avoid excess CPU thread context switches 9 | - runs tasks from diffirent `queues`/`streams` 'by turn', avoiding starvation for task that have been added very late 10 | - designed to process multiple non-blocking task (generally you do not want to sleep/wait inside task-processing function) 11 | - C++11 compilant 12 | 13 | 14 | ### Queues and Streams 15 | execq deals with concurrent task execution using two approaches: `queue-based` and `stream-based` 16 | 17 | *You are free to use multimple queues and streams and any combinations of them!* 18 | 19 | #### 1.1 Queue-based approach 20 | Designed to process objects as 'push-and-forget'. Objects pushed into the queues are processed as soon as any thread is ready to handle it. 21 | 22 | ExecutionQueue combines usual queue, synchronization mechanisms and execution inside threadpool. 23 | 24 | Internally ExecutionQueue tracks tasks are being executed. If destroyed, the queue marks all running and pendings tasks as 'canceled'. 25 | Even if task was canceled before execution, it wouldn't be discarded and will be called on its turn but with 'isCanceled' == true. 26 | 27 | ExecutionQueue can be: 28 | - concurrent: process objects in parallel on multiple threads _// CreateConcurrentExecutionQueue_ 29 | - serial: process objects strictly in 'one-after-one' order. You can be sure that no tasks are executed simultaneously _// CreateSerialExecutionQueue_ 30 | 31 | execq allows to create 'IExecutionQueue' (both serial and concurrent) instance to process your objects in specific IExecutionPool. 32 | 33 | IExecutionPool is kind of opaque threadpool. The same IExecutionPool object usually is used with multiple `queues` and `streams` 34 | 35 | Now that is no need to write you own queue and synchronization around it - all is done inside! 36 | 37 | ```cpp 38 | #include 39 | 40 | // The function is called in parallel on the next free thread 41 | // with the next object from the queue. 42 | void ProcessObject(const std::atomic_bool& isCanceled, std::string&& object) 43 | { 44 | if (isCanceled) 45 | { 46 | std::cout << "Queue has been canceled. Skipping object..."; 47 | return; 48 | } 49 | 50 | std::cout << "Processing object: " << object << '\n'; 51 | } 52 | 53 | int main(void) 54 | { 55 | std::shared_ptr pool = execq::CreateExecutionPool(); 56 | 57 | std::unique_ptr> queue = execq::CreateConcurrentExecutionQueue(pool, &ProcessObject); 58 | 59 | queue->push("qwe"); 60 | queue->push("some string"); 61 | queue->push(""); 62 | 63 | // when destroyed, queue waits until all tasks are executed 64 | 65 | return 0; 66 | } 67 | ``` 68 | 69 | ##### Standalone serial queue 70 | Sometimes you may need just single-thread implementation of the queue to process things in the right order. 71 | For this purpose there is an ability to created pool-independent serial queue. 72 | 73 | ```cpp 74 | #include 75 | 76 | // The function is called in parallel on the next free thread 77 | // with the next object from the queue. 78 | void ProcessObjectOneByOne(const std::atomic_bool& isCanceled, std::string&& object) 79 | { 80 | if (isCanceled) 81 | { 82 | std::cout << "Queue has been canceled. Skipping object..."; 83 | return; 84 | } 85 | 86 | std::cout << "Processing object: " << object << '\n'; 87 | } 88 | 89 | int main(void) 90 | { 91 | std::unique_ptr> queue = execq::CreateSerialExecutionQueue(&ProcessObjectOneByOne); 92 | 93 | queue->push("qwe"); 94 | queue->push("some string"); 95 | queue->push(""); 96 | 97 | // when destroyed, queue waits until all tasks are executed 98 | 99 | return 0; 100 | } 101 | ``` 102 | 103 | #### 1.2 Queue-based approach: future inside! 104 | All ExecutionQueues when pushing object into it return std::future. 105 | Future object is bound to the pushed object and referers to result of object processing. 106 | Note: returned std::future objects could be simply discarded. They wouldn't block in std::future destructor. 107 | 108 | ```cpp 109 | #include 110 | 111 | // The function is called in parallel on the next free thread 112 | // with the next object from the queue. 113 | size_t GetStringSize(const std::atomic_bool& isCanceled, std::string&& object) 114 | { 115 | if (isCanceled) 116 | { 117 | std::cout << "Queue has been canceled. Skipping object..."; 118 | return 0; 119 | } 120 | 121 | std::cout << "Processing object: " << object << '\n'; 122 | 123 | return object.size(); 124 | } 125 | 126 | int main(void) 127 | { 128 | std::shared_ptr pool = execq::CreateExecutionPool(); 129 | 130 | std::unique_ptr> queue = execq::CreateConcurrentExecutionQueue(pool, &GetStringSize); 131 | 132 | std::future future1 = queue->push("qwe"); 133 | std::future future2 = queue->push("some string"); 134 | std::future future3 = queue->push("hello future"); 135 | 136 | const size_t totalSize = future1.get() + future2.get() + future3.get(); 137 | 138 | return 0; 139 | } 140 | ``` 141 | 142 | _execq supports std::future, so ou can just wait until the object is processed._ 143 | 144 | #### 2. Stream-based approach. 145 | Designed to process uncountable amount of tasks as fast as possible, i.e. process next task whenever new thread is available. 146 | 147 | execq allows to create 'IExecutionStream' object that will execute your code each time the thread in the pool is ready to execute next task. 148 | That approach should be considered as the most effective way to process unlimited (or almost unlimited) tasks. 149 | 150 | ```cpp 151 | #include 152 | 153 | // The function is called each time the thread is ready to execute next task. 154 | // It is called only if stream is started. 155 | void ProcessNextObject(const std::atomic_bool& isCanceled) 156 | { 157 | if (isCanceled) 158 | { 159 | std::cout << "Stream has been canceled. Skipping..."; 160 | return; 161 | } 162 | 163 | static std::atomic_int s_someObject { 0 }; 164 | 165 | const int nextObject = s_someObject++; 166 | 167 | std::cout << "Processing object: " << nextObject << '\n'; 168 | } 169 | 170 | int main(void) 171 | { 172 | std::shared_ptr pool = execq::CreateExecutionPool(); 173 | 174 | std::unique_ptr stream = execq::CreateExecutionStream(pool, &ProcessNextObject); 175 | 176 | stream->start(); 177 | 178 | // Only for example purposes. Usually here (if in 'main') could be RunLoop/EventLoop. 179 | // Wait until some objects are processed. 180 | sleep(5); 181 | 182 | return 0; 183 | } 184 | ``` 185 | 186 | ### Design principles & Tech. details 187 | Consider to use single ExecutionPool object (across whole application) with multiple queues and streams. 188 | Combine queues and streams for free to achieve your goals. 189 | Be free to assign tasks to queue or operate stream even from the inside of it's callback. 190 | 191 | #### 'by-turn' execution 192 | execq is designed in special way of dealing with the tasks of queues and streams to avoid starvation. 193 | 194 | Let's assume simple example: there is 2 queues. 195 | First, 100 object are pushed to queue #1. 196 | After 1 object is pushed to queue #2. 197 | 198 | Now few tasks from queue #1 are being executed. But next task for execute will be the task from queue #2, and only then tasks from queue #1. 199 | 200 | #### Avoiding queue starvation 201 | Some tasks could be very time-comsumptive. That means they will block all pool threads execution for a long time. 202 | This causes i.e. starvation: none of other queue tasks will be executed unless one of existing tasks is done. 203 | 204 | To prevent this, each queue and stream additionally has it's own thread. This thread is some kind of 'insurance' thread, where the tasks from the queue/stream could be executed even if all pool's threads are busy for a long time. 205 | 206 | ### Work to be done 207 | - Replace using of std::packaged_task with reference counting 208 | 209 | ### Tests 210 | By default, unit-tests are off. To enable them, just add CMake option -DEXECQ_TESTING_ENABLE=ON 211 | -------------------------------------------------------------------------------- /include/execq/IExecutionQueue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | #include 29 | 30 | namespace execq 31 | { 32 | template 33 | class IExecutionQueue; 34 | 35 | /** 36 | * @class IExecutionQueue 37 | * @brief High-level interface that provides access to queue-based tasks execution. 38 | * @templatefield T Type of the object to be processed on the queue. 39 | * @templatefield R Type of the result of object processing. Can be 'void'. 40 | */ 41 | template 42 | class IExecutionQueue 43 | { 44 | public: 45 | virtual ~IExecutionQueue() = default; 46 | 47 | /** 48 | * @brief Pushes-by-copy an object to be processed on the queue. 49 | * @discussion You can freely ignore return value: it would not block in future's destructor. 50 | * @return Future object to obtain result when the task is done. 51 | */ 52 | std::future push(const T& object); 53 | 54 | /** 55 | * @brief Pushes-by-move an object to be processed on the queue. 56 | * @discussion You can freely ignore return value: it would not block in future's destructor. 57 | * @return Future object to obtain result when the task is done. 58 | */ 59 | std::future push(T&& object); 60 | 61 | /** 62 | * @brief Emplaces an object to be processed on the queue. 63 | * @discussion You can freely ignore return value: it would not block in future's destructor. 64 | * @return Future object to obtain result when the task is done. 65 | */ 66 | template 67 | std::future emplace(Args&&... args); 68 | 69 | /** 70 | * @brief Makrs all tasks as canceled. 71 | * @discussion Be aware that new tasks added after 'cancel' call will not be marked as 'canceled'. 72 | */ 73 | virtual void cancel() = 0; 74 | 75 | private: 76 | virtual std::future pushImpl(std::unique_ptr object) = 0; 77 | }; 78 | } 79 | 80 | template 81 | std::future execq::IExecutionQueue::push(const T& object) 82 | { 83 | return pushImpl(std::unique_ptr(new T { object })); 84 | } 85 | 86 | template 87 | std::future execq::IExecutionQueue::push(T&& object) 88 | { 89 | return pushImpl(std::unique_ptr(new T { std::move(object) })); 90 | } 91 | 92 | template 93 | template 94 | std::future execq::IExecutionQueue::emplace(Args&&... args) 95 | { 96 | return pushImpl(std::unique_ptr(new T { std::forward(args)... })); 97 | } 98 | -------------------------------------------------------------------------------- /include/execq/IExecutionStream.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | 29 | namespace execq 30 | { 31 | /** 32 | * @class IExecutionStream 33 | * @brief High-level interface that provides access to stream-based tasks execution. 34 | * 35 | * @discussion ExecutionStream executes tasks simultaneously in the most efficient way 36 | * on all available Pool threads every time the thread lacks of work. 37 | * 38 | * @discussion Stream could be used when number of tasks is unknown. 39 | * It could be such cases like filesystem traverse: number of files is not determined, but you want to process 40 | * all of them in the most efficient way. 41 | */ 42 | class IExecutionStream 43 | { 44 | public: 45 | virtual ~IExecutionStream() = default; 46 | 47 | /** 48 | * @brief Starts execution stream. 49 | * Each time when thread in pool becomes free, execution stream will be prompted of next task to execute. 50 | */ 51 | virtual void start() = 0; 52 | 53 | /** 54 | * @brief Stops execution stream. 55 | * Execution stream will not be prompted of next tasks to execute until 'start' is called. 56 | * All tasks being executed during stop will normally continue. 57 | */ 58 | virtual void stop() = 0; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /include/execq/execq.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "IExecutionQueue.h" 28 | #include "IExecutionStream.h" 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | namespace execq 35 | { 36 | /** 37 | * @class IExecutionPool 38 | * @brief ThreadPool-like object that provides context for task execution. 39 | */ 40 | class IExecutionPool; 41 | 42 | /** 43 | * @brief Creates pool with hardware-optimal number of threads. 44 | * @discussion Usually you want to create single instance of IExecutionPool for multiple IExecutionQueue/IExecutionStream to achive best performance. 45 | */ 46 | std::shared_ptr CreateExecutionPool(); 47 | 48 | /** 49 | * @brief Creates pool with manually-specified number of threads. 50 | * @discussion Sometimes number of threads should be restricted or can be detected in some non-default way. 51 | * @param threadCount Number of threads for execution context. If number of threads less than 2, exeption will be raised. 52 | */ 53 | std::shared_ptr CreateExecutionPool(const uint32_t threadCount); 54 | 55 | 56 | 57 | /** 58 | * @brief Creates concurrent queue with specific processing function. 59 | * @discussion All objects pushed into this queue will be processed on either one of pool threads or on the queue-specific thread. 60 | * @discussion Tasks in the queue run concurrently on available threads. 61 | * @discussion Queue is not designed to execute long-term tasks like waiting some event etc. 62 | * For such purposes use separate thread or serial queue without execution pool. 63 | */ 64 | template 65 | std::unique_ptr> CreateConcurrentExecutionQueue(std::shared_ptr executionPool, 66 | std::function executor); 67 | 68 | /** 69 | * @brief Creates serial queue with specific processing function. 70 | * @discussion All objects pushed into this queue will be processed on either one of pool threads or on the queue-specific thread. 71 | * @discussion Tasks in the queue run in serial (one-after-one) order. 72 | * @discussion Queue is not designed to execute long-term tasks like waiting some event etc. 73 | * For such purposes use separate thread or serial queue without execution pool. 74 | */ 75 | template 76 | std::unique_ptr> CreateSerialExecutionQueue(std::shared_ptr executionPool, 77 | std::function executor); 78 | 79 | /** 80 | * @brief Creates serial queue with specific processing function. 81 | * @discussion All objects pushed into this queue will be processed on the queue-specific thread. 82 | * @discussion Tasks in the queue run in serial (one-after-one) order. 83 | * @discussion This queue can be used to execute long-term tasks like waiting some event etc. 84 | */ 85 | template 86 | std::unique_ptr> CreateSerialExecutionQueue(std::function executor); 87 | 88 | 89 | /** 90 | * @brief Creates execution stream with specific executee function. Stream is stopped by default. 91 | * @discussion When stream started, 'executee' function will be called each time when ExecutionPool have free thread. 92 | * @discussion Stream is not designed to execute long-term tasks like waiting some event etc. 93 | * For such purposes use separate thread or serial queue without execution pool. 94 | */ 95 | std::unique_ptr CreateExecutionStream(std::shared_ptr executionPool, 96 | std::function executee); 97 | 98 | 99 | 100 | template 101 | using QueueTask = std::packaged_task; 102 | 103 | /** 104 | * @brief Creates concurrent queue that processes custom tasks. 105 | * @discussion All objects pushed into this queue will be processed on either one of pool threads or on the queue-specific thread. 106 | * @discussion Tasks in the queue run concurrently on available threads. 107 | * @discussion Queue is not designed to execute long-term tasks like waiting some event etc. 108 | * For such purposes use separate thread or serial queue without execution pool. 109 | */ 110 | template 111 | std::unique_ptr)>> CreateConcurrentTaskExecutionQueue(std::shared_ptr executionPool); 112 | 113 | /** 114 | * @brief Creates serial queue that processes custom tasks. 115 | * @discussion All objects pushed into this queue will be processed on either one of pool threads or on the queue-specific thread. 116 | * @discussion Tasks in the queue run in serial (one-after-one) order. 117 | * @discussion Queue is not designed to execute long-term tasks like waiting some event etc. 118 | * For such purposes use separate thread or serial queue without execution pool. 119 | */ 120 | template 121 | std::unique_ptr)>> CreateSerialTaskExecutionQueue(std::shared_ptr executionPool); 122 | 123 | /** 124 | * @brief Creates serial queue that processes custom tasks. 125 | * @discussion All objects pushed into this queue will be processed on the queue-specific thread. 126 | * @discussion Tasks in the queue run in serial (one-after-one) order. 127 | * @discussion This queue can be used to execute long-term tasks like waiting some event etc. 128 | */ 129 | template 130 | std::unique_ptr)>> CreateSerialTaskExecutionQueue(); 131 | 132 | } 133 | 134 | #include "execq/internal/execq_private.h" 135 | -------------------------------------------------------------------------------- /include/execq/internal/CancelTokenProvider.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | namespace execq 32 | { 33 | namespace impl 34 | { 35 | using CancelToken = std::shared_ptr; 36 | class CancelTokenProvider 37 | { 38 | public: 39 | CancelToken token(); 40 | void cancel(); 41 | void cancelAndRenew(); 42 | 43 | private: 44 | void cancelAndRenew(const bool renew); 45 | 46 | private: 47 | CancelToken m_currentToken = std::make_shared(false); 48 | std::mutex m_mutex; 49 | }; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /include/execq/internal/ExecutionPool.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "execq/internal/TaskProviderList.h" 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | namespace execq 34 | { 35 | class IExecutionPool 36 | { 37 | public: 38 | virtual ~IExecutionPool() = default; 39 | 40 | virtual void addProvider(impl::ITaskProvider& provider) = 0; 41 | virtual void removeProvider(impl::ITaskProvider& provider) = 0; 42 | 43 | virtual bool notifyOneWorker() = 0; 44 | virtual void notifyAllWorkers() = 0; 45 | }; 46 | 47 | namespace impl 48 | { 49 | class ExecutionPool: public IExecutionPool 50 | { 51 | public: 52 | ExecutionPool(const uint32_t threadCount, const IThreadWorkerFactory& workerFactory); 53 | 54 | virtual void addProvider(ITaskProvider& provider) final; 55 | virtual void removeProvider(ITaskProvider& provider) final; 56 | 57 | virtual bool notifyOneWorker() final; 58 | virtual void notifyAllWorkers() final; 59 | 60 | private: 61 | std::atomic_bool m_valid { true }; 62 | TaskProviderList m_providerGroup; 63 | 64 | std::vector> m_workers; 65 | }; 66 | 67 | 68 | namespace details 69 | { 70 | bool NotifyWorkers(const std::vector>& workers, const bool single); 71 | } 72 | } 73 | } 74 | 75 | 76 | -------------------------------------------------------------------------------- /include/execq/internal/ExecutionQueue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "execq/IExecutionQueue.h" 28 | #include "execq/internal/CancelTokenProvider.h" 29 | #include "execq/internal/ExecutionPool.h" 30 | 31 | #include 32 | 33 | namespace execq 34 | { 35 | namespace impl 36 | { 37 | template 38 | struct QueuedObject 39 | { 40 | std::unique_ptr object; 41 | std::promise promise; 42 | CancelToken cancelToken; 43 | }; 44 | 45 | template 46 | class ExecutionQueue: public IExecutionQueue, private ITaskProvider 47 | { 48 | public: 49 | ExecutionQueue(const bool serial, std::shared_ptr executionPool, 50 | const IThreadWorkerFactory& workerFactory, 51 | std::function executor); 52 | ~ExecutionQueue(); 53 | 54 | public: // IExecutionQueue 55 | virtual void cancel() final; 56 | 57 | private: // IExecutionQueue 58 | virtual std::future pushImpl(std::unique_ptr object) final; 59 | 60 | private: // IThreadWorkerPoolTaskProvider 61 | virtual Task nextTask() final; 62 | 63 | private: 64 | void execute(T&& object, std::promise& promise, const std::atomic_bool& canceled); 65 | template 66 | void execute(T&& object, std::promise& promise, const std::atomic_bool& canceled); 67 | 68 | void pushObject(std::unique_ptr> object, bool& alreadyHasTask); 69 | std::unique_ptr> popObject(); 70 | 71 | void notifyWorkers(); 72 | bool hasTask(); 73 | void waitAllTasks(); 74 | 75 | private: 76 | std::atomic_size_t m_taskRunningCount { 0 }; 77 | 78 | std::atomic_bool m_hasTask { false }; 79 | std::queue>> m_taskQueue; 80 | std::mutex m_taskQueueMutex; 81 | std::condition_variable m_taskQueueCondition; 82 | 83 | CancelTokenProvider m_cancelTokenProvider; 84 | 85 | const bool m_isSerial = false; 86 | const std::shared_ptr m_executionPool; 87 | const std::function m_executor; 88 | 89 | const std::unique_ptr m_additionalWorker; 90 | }; 91 | } 92 | } 93 | 94 | template 95 | execq::impl::ExecutionQueue::ExecutionQueue(const bool serial, std::shared_ptr executionPool, 96 | const IThreadWorkerFactory& workerFactory, 97 | std::function executor) 98 | : m_isSerial(serial) 99 | , m_executionPool(executionPool) 100 | , m_executor(std::move(executor)) 101 | , m_additionalWorker(workerFactory.createWorker(*this)) 102 | { 103 | if (m_executionPool) 104 | { 105 | m_executionPool->addProvider(*this); 106 | } 107 | } 108 | 109 | template 110 | execq::impl::ExecutionQueue::~ExecutionQueue() 111 | { 112 | m_cancelTokenProvider.cancel(); 113 | waitAllTasks(); 114 | if (m_executionPool) 115 | { 116 | m_executionPool->removeProvider(*this); 117 | } 118 | } 119 | 120 | // IExecutionQueue 121 | 122 | template 123 | std::future execq::impl::ExecutionQueue::pushImpl(std::unique_ptr object) 124 | { 125 | using QueuedObject = QueuedObject; 126 | 127 | std::promise promise; 128 | std::future future = promise.get_future(); 129 | 130 | std::unique_ptr queuedObject(new QueuedObject { std::move(object), std::move(promise), m_cancelTokenProvider.token() }); 131 | 132 | bool alreadyHasTask = false; 133 | pushObject(std::move(queuedObject), alreadyHasTask); 134 | 135 | const bool shouldNotify = !m_isSerial || !alreadyHasTask; 136 | if (shouldNotify) 137 | { 138 | notifyWorkers(); 139 | } 140 | 141 | return future; 142 | } 143 | 144 | template 145 | void execq::impl::ExecutionQueue::cancel() 146 | { 147 | m_cancelTokenProvider.cancelAndRenew(); 148 | } 149 | 150 | // IThreadWorkerPoolTaskProvider 151 | 152 | template 153 | execq::impl::Task execq::impl::ExecutionQueue::nextTask() 154 | { 155 | if (!hasTask()) 156 | { 157 | return Task(); 158 | } 159 | 160 | m_taskRunningCount++; 161 | return Task([&] { 162 | std::unique_ptr> object = popObject(); 163 | if (object) 164 | { 165 | execute(std::move(*object->object), object->promise, *object->cancelToken); 166 | } 167 | 168 | if (--m_taskRunningCount > 0) 169 | { 170 | return; 171 | } 172 | 173 | if (!m_hasTask) 174 | { 175 | m_taskQueueCondition.notify_all(); 176 | } 177 | else if (m_isSerial) // if there are more tasks and queue is serial, notify workers 178 | { 179 | notifyWorkers(); 180 | } 181 | }); 182 | } 183 | 184 | // Private 185 | 186 | template 187 | void execq::impl::ExecutionQueue::execute(T&& object, std::promise& promise, const std::atomic_bool& canceled) 188 | { 189 | try 190 | { 191 | m_executor(canceled, std::move(object)); 192 | promise.set_value(); 193 | } 194 | catch(...) 195 | { 196 | try 197 | { 198 | promise.set_exception(std::current_exception()); 199 | } 200 | catch(...) 201 | {} // set_exception() may throw too 202 | } 203 | } 204 | 205 | template 206 | template 207 | void execq::impl::ExecutionQueue::execute(T&& object, std::promise& promise, const std::atomic_bool& canceled) 208 | { 209 | try 210 | { 211 | promise.set_value(m_executor(canceled, std::move(object))); 212 | } 213 | catch(...) 214 | { 215 | try 216 | { 217 | promise.set_exception(std::current_exception()); 218 | } 219 | catch(...) 220 | {} // set_exception() may throw too 221 | } 222 | } 223 | 224 | template 225 | void execq::impl::ExecutionQueue::pushObject(std::unique_ptr> object, bool& alreadyHasTask) 226 | { 227 | std::lock_guard lock(m_taskQueueMutex); 228 | 229 | alreadyHasTask = m_hasTask; 230 | m_hasTask = true; 231 | m_taskQueue.push(std::move(object)); 232 | } 233 | 234 | template 235 | std::unique_ptr> execq::impl::ExecutionQueue::popObject() 236 | { 237 | std::lock_guard lock(m_taskQueueMutex); 238 | if (m_taskQueue.empty()) 239 | { 240 | return nullptr; 241 | } 242 | 243 | std::unique_ptr> object = std::move(m_taskQueue.front()); 244 | m_taskQueue.pop(); 245 | 246 | m_hasTask = !m_taskQueue.empty(); 247 | 248 | return object; 249 | } 250 | 251 | template 252 | bool execq::impl::ExecutionQueue::hasTask() 253 | { 254 | if (!m_hasTask) 255 | { 256 | return false; 257 | } 258 | 259 | if (!m_isSerial) 260 | { 261 | return true; 262 | } 263 | 264 | return !m_taskRunningCount; 265 | } 266 | 267 | template 268 | void execq::impl::ExecutionQueue::notifyWorkers() 269 | { 270 | if (!m_executionPool || !m_executionPool->notifyOneWorker()) 271 | { 272 | m_additionalWorker->notifyWorker(); 273 | } 274 | } 275 | 276 | template 277 | void execq::impl::ExecutionQueue::waitAllTasks() 278 | { 279 | std::unique_lock lock(m_taskQueueMutex); 280 | while (m_taskRunningCount > 0 || !m_taskQueue.empty()) 281 | { 282 | m_taskQueueCondition.wait(lock); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /include/execq/internal/ExecutionStream.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "execq/IExecutionStream.h" 28 | #include "execq/internal/ExecutionPool.h" 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | namespace execq 36 | { 37 | namespace impl 38 | { 39 | class ExecutionStream: public IExecutionStream, private ITaskProvider 40 | { 41 | public: 42 | ExecutionStream(std::shared_ptr executionPool, 43 | const IThreadWorkerFactory& workerFactory, 44 | std::function executee); 45 | ~ExecutionStream(); 46 | 47 | public: // IExecutionStream 48 | virtual void start() final; 49 | virtual void stop() final; 50 | 51 | private: // ITaskProvider 52 | virtual Task nextTask() final; 53 | 54 | private: 55 | void waitPendingTasks(); 56 | 57 | private: 58 | std::atomic_bool m_stopped { true }; 59 | 60 | std::atomic_size_t m_tasksRunningCount { 0 }; 61 | std::mutex m_taskCompleteMutex; 62 | std::condition_variable m_taskCompleteCondition; 63 | 64 | const std::shared_ptr m_executionPool; 65 | const std::function m_executee; 66 | 67 | const std::unique_ptr m_additionalWorker; 68 | }; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /include/execq/internal/TaskProviderList.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "execq/internal/ThreadWorker.h" 28 | 29 | #include 30 | #include 31 | 32 | namespace execq 33 | { 34 | namespace impl 35 | { 36 | class TaskProviderList: public ITaskProvider 37 | { 38 | public: // ITaskProvider 39 | virtual Task nextTask() final; 40 | 41 | public: 42 | void addProvider(ITaskProvider& provider); 43 | void removeProvider(ITaskProvider& provider); 44 | 45 | private: 46 | using TaskProviders_lt = std::list; 47 | TaskProviders_lt m_taskProviders; 48 | TaskProviders_lt::iterator m_currentTaskProviderIt; 49 | std::mutex m_mutex; 50 | }; 51 | } 52 | } 53 | 54 | 55 | -------------------------------------------------------------------------------- /include/execq/internal/ThreadWorker.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | namespace execq 34 | { 35 | namespace impl 36 | { 37 | using Task = std::packaged_task; 38 | class ITaskProvider 39 | { 40 | public: 41 | virtual ~ITaskProvider() = default; 42 | 43 | virtual Task nextTask() = 0; 44 | }; 45 | 46 | 47 | class IThreadWorker 48 | { 49 | public: 50 | virtual ~IThreadWorker() = default; 51 | 52 | virtual bool notifyWorker() = 0; 53 | }; 54 | 55 | 56 | class IThreadWorkerFactory 57 | { 58 | public: 59 | static std::shared_ptr defaultFactory(); 60 | 61 | virtual ~IThreadWorkerFactory() = default; 62 | 63 | virtual std::unique_ptr createWorker(impl::ITaskProvider& provider) const = 0; 64 | }; 65 | } 66 | } 67 | 68 | 69 | -------------------------------------------------------------------------------- /include/execq/internal/execq_private.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "execq/internal/ExecutionQueue.h" 28 | 29 | namespace execq 30 | { 31 | namespace details 32 | { 33 | template 34 | void ExecuteQueueTask(const std::atomic_bool& isCanceled, QueueTask&& task) 35 | { 36 | if (task.valid()) 37 | { 38 | task(isCanceled); 39 | } 40 | } 41 | } 42 | } 43 | 44 | template 45 | std::unique_ptr> execq::CreateConcurrentExecutionQueue(std::shared_ptr executionPool, 46 | std::function executor) 47 | { 48 | return std::unique_ptr>(new impl::ExecutionQueue(false, 49 | executionPool, 50 | *impl::IThreadWorkerFactory::defaultFactory(), 51 | std::move(executor))); 52 | } 53 | 54 | template 55 | std::unique_ptr> execq::CreateSerialExecutionQueue(std::shared_ptr executionPool, 56 | std::function executor) 57 | { 58 | return std::unique_ptr>(new impl::ExecutionQueue(false, 59 | executionPool, 60 | *impl::IThreadWorkerFactory::defaultFactory(), 61 | std::move(executor))); 62 | } 63 | 64 | template 65 | std::unique_ptr> execq::CreateSerialExecutionQueue(std::function executor) 66 | { 67 | return std::unique_ptr>(new impl::ExecutionQueue(true, 68 | nullptr, 69 | *impl::IThreadWorkerFactory::defaultFactory(), 70 | std::move(executor))); 71 | } 72 | 73 | template 74 | std::unique_ptr)>> execq::CreateConcurrentTaskExecutionQueue(std::shared_ptr executionPool) 75 | { 76 | return CreateConcurrentExecutionQueue>(executionPool, &details::ExecuteQueueTask); 77 | } 78 | 79 | template 80 | std::unique_ptr)>> execq::CreateSerialTaskExecutionQueue(std::shared_ptr executionPool) 81 | { 82 | return CreateSerialExecutionQueue>(executionPool, &details::ExecuteQueueTask); 83 | } 84 | 85 | template 86 | std::unique_ptr)>> execq::CreateSerialTaskExecutionQueue() 87 | { 88 | return CreateSerialExecutionQueue>(&details::ExecuteQueueTask); 89 | } 90 | -------------------------------------------------------------------------------- /src/CancelTokenProvider.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include "CancelTokenProvider.h" 26 | 27 | execq::impl::CancelToken execq::impl::CancelTokenProvider::token() 28 | { 29 | std::lock_guard lock(m_mutex); 30 | return m_currentToken; 31 | } 32 | 33 | void execq::impl::CancelTokenProvider::cancel() 34 | { 35 | cancelAndRenew(false); 36 | } 37 | 38 | void execq::impl::CancelTokenProvider::cancelAndRenew() 39 | { 40 | cancelAndRenew(true); 41 | } 42 | 43 | void execq::impl::CancelTokenProvider::cancelAndRenew(const bool renew) 44 | { 45 | std::lock_guard lock(m_mutex); 46 | if (m_currentToken) 47 | { 48 | *m_currentToken = true; 49 | } 50 | if (renew) 51 | { 52 | m_currentToken = std::make_shared(false); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ExecutionPool.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include "ExecutionPool.h" 26 | 27 | execq::impl::ExecutionPool::ExecutionPool(const uint32_t threadCount, const IThreadWorkerFactory& workerFactory) 28 | { 29 | for (uint32_t i = 0; i < threadCount; i++) 30 | { 31 | m_workers.emplace_back(workerFactory.createWorker(m_providerGroup)); 32 | } 33 | } 34 | 35 | void execq::impl::ExecutionPool::addProvider(ITaskProvider& provider) 36 | { 37 | m_providerGroup.addProvider(provider); 38 | } 39 | 40 | void execq::impl::ExecutionPool::removeProvider(ITaskProvider& provider) 41 | { 42 | m_providerGroup.removeProvider(provider); 43 | } 44 | 45 | bool execq::impl::ExecutionPool::notifyOneWorker() 46 | { 47 | return details::NotifyWorkers(m_workers, true); 48 | } 49 | 50 | void execq::impl::ExecutionPool::notifyAllWorkers() 51 | { 52 | details::NotifyWorkers(m_workers, false); 53 | } 54 | 55 | // Details 56 | 57 | bool execq::impl::details::NotifyWorkers(const std::vector>& workers, const bool single) 58 | { 59 | bool notified = false; 60 | for (const auto& worker : workers) 61 | { 62 | notified |= worker->notifyWorker(); 63 | if (notified && single) 64 | { 65 | return true; 66 | } 67 | } 68 | 69 | return notified; 70 | } 71 | -------------------------------------------------------------------------------- /src/ExecutionStream.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include "ExecutionStream.h" 26 | 27 | execq::impl::ExecutionStream::ExecutionStream(std::shared_ptr executionPool, 28 | const IThreadWorkerFactory& workerFactory, 29 | std::function executee) 30 | : m_executionPool(executionPool) 31 | , m_executee(std::move(executee)) 32 | , m_additionalWorker(workerFactory.createWorker(*this)) 33 | { 34 | m_executionPool->addProvider(*this); 35 | } 36 | 37 | execq::impl::ExecutionStream::~ExecutionStream() 38 | { 39 | stop(); 40 | waitPendingTasks(); 41 | m_executionPool->removeProvider(*this); 42 | } 43 | 44 | // IExecutionStream 45 | 46 | void execq::impl::ExecutionStream::start() 47 | { 48 | m_stopped = false; 49 | m_executionPool->notifyAllWorkers(); 50 | m_additionalWorker->notifyWorker(); 51 | } 52 | 53 | void execq::impl::ExecutionStream::stop() 54 | { 55 | m_stopped = true; 56 | } 57 | 58 | // IThreadWorkerPoolTaskProvider 59 | 60 | execq::impl::Task execq::impl::ExecutionStream::nextTask() 61 | { 62 | if (m_stopped) 63 | { 64 | return Task(); 65 | } 66 | 67 | m_tasksRunningCount++; 68 | return Task([&] { 69 | m_executee(m_stopped); 70 | m_tasksRunningCount--; 71 | 72 | if (!m_tasksRunningCount) 73 | { 74 | m_taskCompleteCondition.notify_all(); 75 | } 76 | }); 77 | } 78 | 79 | // Private 80 | 81 | void execq::impl::ExecutionStream::waitPendingTasks() 82 | { 83 | std::unique_lock lock(m_taskCompleteMutex); 84 | while (m_tasksRunningCount > 0) 85 | { 86 | m_taskCompleteCondition.wait(lock); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/TaskProviderList.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include "TaskProviderList.h" 27 | 28 | execq::impl::Task execq::impl::TaskProviderList::nextTask() 29 | { 30 | std::lock_guard lock(m_mutex); 31 | 32 | const size_t taskProvidersCount = m_taskProviders.size(); 33 | const auto listEndIt = m_taskProviders.end(); 34 | 35 | for (size_t i = 0; i < taskProvidersCount; i++) 36 | { 37 | if (m_currentTaskProviderIt == listEndIt) 38 | { 39 | m_currentTaskProviderIt = m_taskProviders.begin(); 40 | } 41 | 42 | ITaskProvider* const provider = *(m_currentTaskProviderIt++); 43 | Task task = provider->nextTask(); 44 | if (task.valid()) 45 | { 46 | return task; 47 | } 48 | } 49 | 50 | return Task(); 51 | } 52 | 53 | void execq::impl::TaskProviderList::addProvider(ITaskProvider& provider) 54 | { 55 | std::lock_guard lock(m_mutex); 56 | m_taskProviders.push_back(&provider); 57 | m_currentTaskProviderIt = m_taskProviders.begin(); 58 | } 59 | 60 | void execq::impl::TaskProviderList::removeProvider(ITaskProvider& provider) 61 | { 62 | std::lock_guard lock(m_mutex); 63 | const auto it = std::find(m_taskProviders.begin(), m_taskProviders.end(), &provider); 64 | if (it != m_taskProviders.end()) 65 | { 66 | m_taskProviders.erase(it); 67 | m_currentTaskProviderIt = m_taskProviders.begin(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/ThreadWorker.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include "ThreadWorker.h" 26 | 27 | namespace execq 28 | { 29 | namespace impl 30 | { 31 | class ThreadWorker: public IThreadWorker 32 | { 33 | public: 34 | explicit ThreadWorker(ITaskProvider& provider); 35 | virtual ~ThreadWorker(); 36 | 37 | virtual bool notifyWorker() final; 38 | 39 | private: 40 | void threadMain(); 41 | void shutdown(); 42 | 43 | private: 44 | std::atomic_bool m_shouldQuit { false }; 45 | std::atomic_bool m_checkNextTask { false }; 46 | std::condition_variable m_condition; 47 | std::mutex m_mutex; 48 | std::unique_ptr m_thread; 49 | 50 | ITaskProvider& m_provider; 51 | }; 52 | } 53 | } 54 | 55 | std::shared_ptr execq::impl::IThreadWorkerFactory::defaultFactory() 56 | { 57 | class ThreadWorkerFactory: public IThreadWorkerFactory 58 | { 59 | public: 60 | virtual std::unique_ptr createWorker(ITaskProvider& provider) const final 61 | { 62 | return std::unique_ptr(new ThreadWorker(provider)); 63 | } 64 | }; 65 | 66 | static std::shared_ptr s_factory = std::make_shared(); 67 | return s_factory; 68 | } 69 | 70 | execq::impl::ThreadWorker::ThreadWorker(ITaskProvider& provider) 71 | : m_provider(provider) 72 | {} 73 | 74 | execq::impl::ThreadWorker::~ThreadWorker() 75 | { 76 | shutdown(); 77 | if (m_thread && m_thread->joinable()) 78 | { 79 | m_thread->join(); 80 | } 81 | } 82 | 83 | bool execq::impl::ThreadWorker::notifyWorker() 84 | { 85 | std::lock_guard lock(m_mutex); 86 | if (m_checkNextTask) 87 | { 88 | return false; 89 | } 90 | 91 | m_checkNextTask = true; 92 | if (!m_thread) 93 | { 94 | m_thread.reset(new std::thread(&ThreadWorker::threadMain, this)); 95 | } 96 | 97 | m_condition.notify_one(); 98 | 99 | return true; 100 | } 101 | 102 | void execq::impl::ThreadWorker::shutdown() 103 | { 104 | std::lock_guard lock(m_mutex); 105 | m_shouldQuit = true; 106 | m_condition.notify_one(); 107 | } 108 | 109 | void execq::impl::ThreadWorker::threadMain() 110 | { 111 | while (true) 112 | { 113 | if (m_shouldQuit) 114 | { 115 | break; 116 | } 117 | 118 | m_checkNextTask = false; 119 | Task task = m_provider.nextTask(); 120 | if (task.valid()) 121 | { 122 | task(); 123 | continue; 124 | } 125 | 126 | std::unique_lock lock(m_mutex); 127 | if (m_checkNextTask) 128 | { 129 | continue; 130 | } 131 | 132 | if (m_shouldQuit) 133 | { 134 | break; 135 | } 136 | 137 | m_condition.wait(lock); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/execq.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include "execq.h" 26 | #include "ExecutionStream.h" 27 | 28 | namespace 29 | { 30 | uint32_t GetOptimalThreadCount() 31 | { 32 | 33 | const uint32_t defaultThreadCount = 4; 34 | const uint32_t hardwareThreadCount = std::thread::hardware_concurrency(); 35 | 36 | return hardwareThreadCount ? hardwareThreadCount : defaultThreadCount; 37 | } 38 | 39 | std::shared_ptr CreateDefaultExecutionPool(const uint32_t threadCount) 40 | { 41 | return std::make_shared(threadCount, *execq::impl::IThreadWorkerFactory::defaultFactory()); 42 | } 43 | } 44 | 45 | std::shared_ptr execq::CreateExecutionPool() 46 | { 47 | return CreateDefaultExecutionPool(GetOptimalThreadCount()); 48 | } 49 | 50 | std::shared_ptr execq::CreateExecutionPool(const uint32_t threadCount) 51 | { 52 | if (!threadCount) 53 | { 54 | throw std::runtime_error("Failed to create IExecutionPool: thread count could not be zero."); 55 | } 56 | else if (threadCount == 1) 57 | { 58 | throw std::runtime_error("Failed to create IExecutionPool: for single-thread execution use pool-independent serial queue."); 59 | } 60 | 61 | return CreateDefaultExecutionPool(threadCount); 62 | } 63 | 64 | std::unique_ptr execq::CreateExecutionStream(std::shared_ptr executionPool, 65 | std::function executee) 66 | { 67 | return std::unique_ptr(new impl::ExecutionStream(executionPool, 68 | *impl::IThreadWorkerFactory::defaultFactory(), 69 | std::move(executee))); 70 | } 71 | -------------------------------------------------------------------------------- /tests/CancelTokenProviderTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include "CancelTokenProvider.h" 26 | 27 | #include 28 | 29 | TEST(ExecutionPool, CancelTokenProvider) 30 | { 31 | execq::impl::CancelTokenProvider provider; 32 | 33 | execq::impl::CancelToken token = provider.token(); 34 | ASSERT_NE(token, nullptr); 35 | 36 | // be default, token is not canceled 37 | EXPECT_FALSE(token->load()); 38 | 39 | provider.cancel(); 40 | 41 | // both old and current provider's token are canceled 42 | EXPECT_TRUE(token->load()); 43 | EXPECT_TRUE(provider.token()->load()); 44 | 45 | provider.cancelAndRenew(); 46 | 47 | // old token is canceled, current provider's token is not 48 | EXPECT_TRUE(token->load()); 49 | EXPECT_FALSE(provider.token()->load()); 50 | } 51 | -------------------------------------------------------------------------------- /tests/ExecqTestUtil.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "ExecutionPool.h" 28 | 29 | #include 30 | 31 | namespace execq 32 | { 33 | namespace test 34 | { 35 | class MockExecutionPool: public execq::IExecutionPool 36 | { 37 | public: 38 | MOCK_METHOD1(addProvider, void(execq::impl::ITaskProvider& provider)); 39 | MOCK_METHOD1(removeProvider, void(execq::impl::ITaskProvider& provider)); 40 | 41 | MOCK_METHOD0(notifyOneWorker, bool()); 42 | MOCK_METHOD0(notifyAllWorkers, void()); 43 | }; 44 | 45 | class MockThreadWorkerFactory: public execq::impl::IThreadWorkerFactory 46 | { 47 | public: 48 | MOCK_CONST_METHOD1(createWorker, std::unique_ptr(execq::impl::ITaskProvider& provider)); 49 | }; 50 | 51 | class MockThreadWorker: public execq::impl::IThreadWorker 52 | { 53 | public: 54 | MOCK_METHOD0(notifyWorker, bool()); 55 | }; 56 | 57 | static const std::chrono::milliseconds kLongTermJob { 100 }; 58 | static const std::chrono::milliseconds kTimeout { 500 }; 59 | 60 | inline void WaitForLongTermJob() 61 | { 62 | std::this_thread::sleep_for(kLongTermJob); 63 | } 64 | 65 | MATCHER_P(CompareRvalue, value, "") 66 | { 67 | return value == arg; 68 | } 69 | 70 | MATCHER_P(CompareWithAtomic, value, "") 71 | { 72 | return value == arg; 73 | } 74 | 75 | MATCHER_P(SaveArgAddress, value, "") 76 | { 77 | *value = &arg; 78 | return true; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/ExecutionQueueTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include "execq.h" 26 | #include "ExecqTestUtil.h" 27 | 28 | using namespace execq::test; 29 | 30 | TEST(ExecutionPool, ExecutionQueue_SingleTask) 31 | { 32 | auto pool = execq::CreateExecutionPool(); 33 | 34 | ::testing::MockFunction mockExecutor; 35 | auto queue = execq::CreateConcurrentExecutionQueue(pool, mockExecutor.AsStdFunction()); 36 | 37 | EXPECT_CALL(mockExecutor, Call(::testing::_, CompareRvalue("qwe"))) 38 | .WillOnce(::testing::Return()); 39 | 40 | queue->push("qwe"); 41 | } 42 | 43 | TEST(ExecutionPool, ExecutionQueue_SingleTask_WithFuture) 44 | { 45 | auto pool = execq::CreateExecutionPool(); 46 | 47 | ::testing::MockFunction mockExecutor; 48 | auto queue = execq::CreateConcurrentExecutionQueue(pool, mockExecutor.AsStdFunction()); 49 | 50 | // sleep for a some time and then return the same object 51 | EXPECT_CALL(mockExecutor, Call(::testing::_, CompareRvalue("qwe"))) 52 | .WillOnce(::testing::Invoke([] (const std::atomic_bool& shouldQuit, std::string s) { 53 | WaitForLongTermJob(); 54 | return s; 55 | })); 56 | 57 | std::future result = queue->push("qwe"); 58 | 59 | EXPECT_TRUE(result.wait_for(kTimeout) == std::future_status::ready); 60 | } 61 | 62 | TEST(ExecutionPool, ExecutionQueue_MultipleTasks) 63 | { 64 | auto pool = execq::CreateExecutionPool(); 65 | 66 | ::testing::MockFunction mockExecutor; 67 | auto queue = execq::CreateConcurrentExecutionQueue(pool, mockExecutor.AsStdFunction()); 68 | 69 | const size_t count = 1000; 70 | EXPECT_CALL(mockExecutor, Call(::testing::_, ::testing::_)) 71 | .Times(count).WillRepeatedly(::testing::Return()); 72 | 73 | for (size_t i = 0; i < count; i++) 74 | { 75 | queue->push(arc4random()); 76 | } 77 | } 78 | 79 | TEST(ExecutionPool, ExecutionQueue_TaskExecutionWhenQueueDestroyed) 80 | { 81 | auto pool = execq::CreateExecutionPool(); 82 | 83 | std::promise> isExecutedPromise; 84 | auto isExecuted = isExecutedPromise.get_future(); 85 | auto queue = execq::CreateConcurrentExecutionQueue(pool, [&isExecutedPromise] (const std::atomic_bool& shouldQuit, 86 | std::string&& object) { 87 | // wait for double time comparing to time waiting before reset 88 | WaitForLongTermJob(); 89 | isExecutedPromise.set_value(std::make_pair(shouldQuit.load(), object)); 90 | }); 91 | queue->push("qwe"); 92 | 93 | // delete queue 94 | queue.reset(); 95 | 96 | ASSERT_TRUE(isExecuted.valid()); 97 | 98 | std::pair executeState = isExecuted.get(); 99 | EXPECT_EQ(executeState.first, true); 100 | EXPECT_EQ(executeState.second, "qwe"); 101 | } 102 | 103 | TEST(ExecutionPool, ExecutionQueue_ExecutionPool_Concurrent) 104 | { 105 | auto executionPool = std::make_shared(); 106 | MockThreadWorkerFactory workerFactory {}; 107 | 108 | // Queue must 'register' itself in ExecutionPool when created 109 | execq::impl::ITaskProvider* registeredProvider = nullptr; 110 | EXPECT_CALL(*executionPool, addProvider(SaveArgAddress(®isteredProvider))) 111 | .WillOnce(::testing::Return()); 112 | 113 | 114 | // Queue also creates additional single thread worker for its own needs 115 | std::unique_ptr additionalWorkerPtr(new MockThreadWorker{}); 116 | MockThreadWorker& additionalWorker = *additionalWorkerPtr; 117 | EXPECT_CALL(workerFactory, createWorker(::testing::_)) 118 | .WillOnce(::testing::Return(::testing::ByMove(std::move(additionalWorkerPtr)))); 119 | 120 | 121 | // Create queue with mock execution function 122 | ::testing::MockFunction mockExecutor; 123 | execq::impl::ExecutionQueue queue(false, executionPool, workerFactory, mockExecutor.AsStdFunction()); 124 | ASSERT_NE(registeredProvider, nullptr); 125 | 126 | 127 | // When new object comes to the queue, it notifies workers 128 | EXPECT_CALL(*executionPool, notifyOneWorker()) 129 | .WillOnce(::testing::Return(true)); 130 | queue.push("qwe"); 131 | 132 | 133 | // If all workers of the pool are busy, trigger additional (own) worker 134 | EXPECT_CALL(*executionPool, notifyOneWorker()) 135 | .WillOnce(::testing::Return(false)); 136 | EXPECT_CALL(additionalWorker, notifyWorker()) 137 | .WillOnce(::testing::Return(true)); 138 | queue.push("asd"); 139 | 140 | 141 | // Test that executors method is called in the proper moment 142 | execq::impl::Task task = registeredProvider->nextTask(); 143 | ASSERT_TRUE(task.valid()); 144 | EXPECT_CALL(mockExecutor, Call(CompareWithAtomic(false), CompareRvalue("qwe"))) 145 | .WillOnce(::testing::Return()); 146 | task(); 147 | 148 | task = registeredProvider->nextTask(); 149 | ASSERT_TRUE(task.valid()); 150 | EXPECT_CALL(mockExecutor, Call(CompareWithAtomic(false), CompareRvalue("asd"))) 151 | .WillOnce(::testing::Return()); 152 | task(); 153 | 154 | 155 | // No tasks - no execution 156 | EXPECT_FALSE(registeredProvider->nextTask().valid()); 157 | 158 | 159 | // Queue must 'unregister' itself in ExecutionPool when destroyed 160 | EXPECT_CALL(*executionPool, removeProvider(::testing::_)) 161 | .WillOnce(::testing::Return()); 162 | } 163 | 164 | TEST(ExecutionPool, ExecutionQueue_ExecutionPool_Serial) 165 | { 166 | auto executionPool = std::make_shared(); 167 | MockThreadWorkerFactory workerFactory {}; 168 | 169 | // Queue must 'register' itself in ExecutionPool when created 170 | execq::impl::ITaskProvider* registeredProvider = nullptr; 171 | EXPECT_CALL(*executionPool, addProvider(SaveArgAddress(®isteredProvider))) 172 | .WillOnce(::testing::Return()); 173 | 174 | // Queue also creates additional single thread worker for its own needs 175 | std::unique_ptr additionalWorkerPtr(new MockThreadWorker{}); 176 | MockThreadWorker& additionalWorker = *additionalWorkerPtr; 177 | EXPECT_CALL(workerFactory, createWorker(::testing::_)) 178 | .WillOnce(::testing::Return(::testing::ByMove(std::move(additionalWorkerPtr)))); 179 | 180 | 181 | // Create queue with mock execution function 182 | ::testing::MockFunction mockExecutor; 183 | execq::impl::ExecutionQueue queue(true, executionPool, workerFactory, mockExecutor.AsStdFunction()); 184 | ASSERT_NE(registeredProvider, nullptr); 185 | 186 | 187 | // When new object comes to the queue, it notifies workers 188 | EXPECT_CALL(*executionPool, notifyOneWorker()) 189 | .WillOnce(::testing::Return(true)); 190 | queue.push("qwe"); 191 | 192 | 193 | // The serial queue already has a task for execution, no reason to notify workers 194 | EXPECT_CALL(*executionPool, notifyOneWorker()) 195 | .Times(0); 196 | EXPECT_CALL(additionalWorker, notifyWorker()) 197 | .Times(0); 198 | queue.push("asd"); 199 | 200 | 201 | // Test that executors method is called in the proper moment 202 | execq::impl::Task task = registeredProvider->nextTask(); 203 | ASSERT_TRUE(task.valid()); 204 | EXPECT_CALL(mockExecutor, Call(CompareWithAtomic(false), CompareRvalue("qwe"))) 205 | .WillOnce(::testing::Return()); 206 | 207 | // For serial queue, if the operation completes and there is any task to execute, it will notify workers to handle it 208 | EXPECT_CALL(*executionPool, notifyOneWorker()) 209 | .WillOnce(::testing::Return(true)); 210 | task(); 211 | 212 | 213 | task = registeredProvider->nextTask(); 214 | ASSERT_TRUE(task.valid()); 215 | EXPECT_CALL(mockExecutor, Call(CompareWithAtomic(false), CompareRvalue("asd"))) 216 | .WillOnce(::testing::Return()); 217 | task(); 218 | 219 | 220 | // No tasks - no execution 221 | EXPECT_FALSE(registeredProvider->nextTask().valid()); 222 | 223 | 224 | // Queue must 'unregister' itself in ExecutionPool when destroyed 225 | EXPECT_CALL(*executionPool, removeProvider(::testing::_)) 226 | .WillOnce(::testing::Return()); 227 | } 228 | 229 | TEST(ExecutionPool, ExecutionQueue_Cancelability) 230 | { 231 | auto executionPool = std::make_shared(); 232 | MockThreadWorkerFactory workerFactory {}; 233 | 234 | // Assume worker pool always has free workers 235 | EXPECT_CALL(*executionPool, notifyOneWorker()) 236 | .WillRepeatedly(::testing::Return(true)); 237 | 238 | 239 | // Queue must 'register' itself in ExecutionPool when created 240 | execq::impl::ITaskProvider* registeredProvider = nullptr; 241 | EXPECT_CALL(*executionPool, addProvider(SaveArgAddress(®isteredProvider))) 242 | .WillOnce(::testing::Return()); 243 | 244 | 245 | // Queue also creates additional single thread worker for its own needs 246 | std::unique_ptr additionalWorkerPtr(new MockThreadWorker{}); 247 | EXPECT_CALL(workerFactory, createWorker(::testing::_)) 248 | .WillOnce(::testing::Return(::testing::ByMove(std::move(additionalWorkerPtr)))); 249 | 250 | 251 | // Create queue with mock execution function 252 | ::testing::MockFunction mockExecutor; 253 | execq::impl::ExecutionQueue queue(false, executionPool, workerFactory, mockExecutor.AsStdFunction()); 254 | ASSERT_NE(registeredProvider, nullptr); 255 | 256 | 257 | // When only objects pushed before 'cancel' call are really canceled 258 | queue.push("qwe"); 259 | queue.cancel(); 260 | queue.push("asd"); 261 | 262 | 263 | EXPECT_CALL(mockExecutor, Call(CompareWithAtomic(true), CompareRvalue("qwe"))) 264 | .WillOnce(::testing::Return()); 265 | 266 | EXPECT_CALL(mockExecutor, Call(CompareWithAtomic(false), CompareRvalue("asd"))) 267 | .WillOnce(::testing::Return()); 268 | 269 | 270 | execq::impl::Task task = registeredProvider->nextTask(); 271 | ASSERT_TRUE(task.valid()); 272 | task(); 273 | 274 | task = registeredProvider->nextTask(); 275 | ASSERT_TRUE(task.valid()); 276 | task(); 277 | 278 | 279 | // Queue must 'unregister' itself in ExecutionPool when destroyed 280 | EXPECT_CALL(*executionPool, removeProvider(::testing::_)) 281 | .WillOnce(::testing::Return()); 282 | } 283 | -------------------------------------------------------------------------------- /tests/ExecutionStreamTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include "execq.h" 26 | #include "ExecutionPool.h" 27 | #include "ExecutionStream.h" 28 | #include "ExecqTestUtil.h" 29 | 30 | using namespace execq::test; 31 | 32 | TEST(ExecutionPool, ExecutionStream_UsualRun) 33 | { 34 | auto pool = execq::CreateExecutionPool(); 35 | 36 | ::testing::MockFunction mockExecutor; 37 | auto stream = execq::CreateExecutionStream(pool, mockExecutor.AsStdFunction()); 38 | 39 | // counters must be shared_ptrs to be not destroyed at the end of test scope. 40 | auto executedTaskCount = std::make_shared(0); 41 | auto canceledTaskCount = std::make_shared(0); 42 | EXPECT_CALL(mockExecutor, Call(::testing::_)) 43 | .WillRepeatedly(::testing::Invoke([executedTaskCount, canceledTaskCount] (const std::atomic_bool& isCanceled) { 44 | if (isCanceled) 45 | { 46 | (*canceledTaskCount)++; 47 | } 48 | else 49 | { 50 | (*executedTaskCount)++; 51 | WaitForLongTermJob(); 52 | } 53 | })); 54 | 55 | stream->start(); 56 | 57 | // wait some time to be sure that object has been delivered to corresponding thread. 58 | WaitForLongTermJob(); 59 | 60 | // At least few tasks must be executed 61 | EXPECT_GE(executedTaskCount->load(), 0); 62 | EXPECT_EQ(canceledTaskCount->load(), 0); 63 | } 64 | 65 | TEST(ExecutionPool, ExecutionStream_WorkerPool) 66 | { 67 | auto executionPool = std::make_shared(); 68 | MockThreadWorkerFactory workerFactory {}; 69 | 70 | // Stream must 'register' itself in ExecutionPool when created 71 | execq::impl::ITaskProvider* registeredProvider = nullptr; 72 | EXPECT_CALL(*executionPool, addProvider(SaveArgAddress(®isteredProvider))) 73 | .WillOnce(::testing::Return()); 74 | 75 | 76 | // Strean also creates additional single thread worker for its own needs 77 | std::unique_ptr additionalWorkerPtr(new MockThreadWorker{}); 78 | MockThreadWorker& additionalWorker = *additionalWorkerPtr; 79 | EXPECT_CALL(workerFactory, createWorker(::testing::_)) 80 | .WillOnce(::testing::Return(::testing::ByMove(std::move(additionalWorkerPtr)))); 81 | 82 | 83 | // Create stream with mock execution function 84 | ::testing::MockFunction mockExecutor; 85 | execq::impl::ExecutionStream stream(executionPool, workerFactory, mockExecutor.AsStdFunction()); 86 | ASSERT_NE(registeredProvider, nullptr); 87 | 88 | 89 | // When steram starts, it notifies all workers 90 | EXPECT_CALL(*executionPool, notifyAllWorkers()) 91 | .WillOnce(::testing::Return()); 92 | EXPECT_CALL(additionalWorker, notifyWorker()) 93 | .WillOnce(::testing::Return(true)); 94 | 95 | stream.start(); 96 | 97 | 98 | // While stream is started, it always produce valid tasks 99 | execq::impl::Task task = registeredProvider->nextTask(); 100 | EXPECT_TRUE(task.valid()); 101 | EXPECT_CALL(mockExecutor, Call(::testing::_)) 102 | .WillOnce(::testing::Return()); 103 | task(); 104 | 105 | 106 | stream.stop(); 107 | 108 | // When stopped, tasks are invalid 109 | EXPECT_FALSE(registeredProvider->nextTask().valid()); 110 | 111 | 112 | // Stream must 'unregister' itself in ExecutionPool when destroyed 113 | EXPECT_CALL(*executionPool, removeProvider(::testing::_)) 114 | .WillOnce(::testing::Return()); 115 | } 116 | -------------------------------------------------------------------------------- /tests/TaskExecutionQueueTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include "execq.h" 26 | #include "ExecqTestUtil.h" 27 | 28 | using namespace execq::test; 29 | 30 | TEST(ExecutionPool, ExecutionQueue_TaskQueue_Concurrent) 31 | { 32 | auto pool = execq::CreateExecutionPool(); 33 | 34 | ::testing::MockFunction mockExecutor; 35 | auto queue = execq::CreateConcurrentTaskExecutionQueue(pool); 36 | 37 | EXPECT_CALL(mockExecutor, Call(::testing::_)) 38 | .WillOnce(::testing::Return()); 39 | 40 | queue->emplace(mockExecutor.AsStdFunction()); 41 | } 42 | 43 | TEST(ExecutionPool, ExecutionQueue_TaskQueue_SerialWithPool) 44 | { 45 | auto pool = execq::CreateExecutionPool(); 46 | 47 | ::testing::MockFunction mockExecutor; 48 | auto queue = execq::CreateSerialTaskExecutionQueue(pool); 49 | 50 | EXPECT_CALL(mockExecutor, Call(::testing::_)) 51 | .WillOnce(::testing::Return()); 52 | 53 | queue->emplace(mockExecutor.AsStdFunction()); 54 | } 55 | 56 | TEST(ExecutionPool, ExecutionQueue_TaskQueue_SerialStandalone) 57 | { 58 | ::testing::MockFunction mockExecutor; 59 | auto queue = execq::CreateSerialTaskExecutionQueue(); 60 | 61 | EXPECT_CALL(mockExecutor, Call(::testing::_)) 62 | .WillOnce(::testing::Return()); 63 | 64 | queue->emplace(mockExecutor.AsStdFunction()); 65 | } 66 | -------------------------------------------------------------------------------- /tests/TaskProviderListTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2018 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include "TaskProviderList.h" 26 | #include "ExecqTestUtil.h" 27 | 28 | using namespace ::testing; 29 | 30 | namespace 31 | { 32 | class MockTaskProvider: public execq::impl::ITaskProvider 33 | { 34 | public: 35 | MOCK_METHOD0(nextTask, execq::impl::Task()); 36 | }; 37 | 38 | execq::impl::Task MakeValidTask() 39 | { 40 | return execq::impl::Task([] {}); 41 | } 42 | 43 | execq::impl::Task MakeInvalidTask() 44 | { 45 | return execq::impl::Task(); 46 | } 47 | } 48 | 49 | TEST(ExecutionPool, TaskProviderList_NoItems) 50 | { 51 | execq::impl::TaskProviderList providers; 52 | 53 | EXPECT_FALSE(providers.nextTask().valid()); 54 | } 55 | 56 | TEST(ExecutionPool, TaskProviderList_SindleItem) 57 | { 58 | execq::impl::TaskProviderList providers; 59 | 60 | MockTaskProvider provider; 61 | providers.addProvider(provider); 62 | 63 | 64 | EXPECT_CALL(provider, nextTask()) 65 | .WillOnce([] { return MakeValidTask(); }) 66 | .WillOnce([] { return MakeInvalidTask(); }); 67 | 68 | ASSERT_TRUE(providers.nextTask().valid()); 69 | EXPECT_FALSE(providers.nextTask().valid()); 70 | } 71 | 72 | TEST(ExecutionPool, TaskProviderList_MultipleItems) 73 | { 74 | execq::impl::TaskProviderList providers; 75 | 76 | // fill group with providers 77 | MockTaskProvider provider1; 78 | providers.addProvider(provider1); 79 | 80 | MockTaskProvider provider2; 81 | providers.addProvider(provider2); 82 | 83 | MockTaskProvider provider3; 84 | providers.addProvider(provider3); 85 | 86 | // Provider #1 and #3 have one valid task. Provider #2 hasn't valid tasks 87 | EXPECT_CALL(provider1, nextTask()) 88 | .WillOnce([] { return MakeValidTask(); }) 89 | .WillRepeatedly([] { return MakeInvalidTask(); }); 90 | 91 | EXPECT_CALL(provider3, nextTask()) 92 | .WillOnce([] { return MakeValidTask(); }) 93 | .WillRepeatedly([] { return MakeInvalidTask(); }); 94 | 95 | EXPECT_CALL(provider2, nextTask()) 96 | .WillRepeatedly([] { return MakeInvalidTask(); }); 97 | 98 | 99 | // Checking & Executing task from the #1 provider 100 | ASSERT_TRUE(providers.nextTask().valid()); 101 | 102 | // Checking #2 provider. Checking & Executing task from the #3 provider 103 | ASSERT_TRUE(providers.nextTask().valid()); 104 | 105 | // Next times all providers will be checked, but not executed because of lack of tasks 106 | EXPECT_FALSE(providers.nextTask().valid()); 107 | } 108 | 109 | TEST(ExecutionPool, TaskProviderList_Add_Remove) 110 | { 111 | execq::impl::TaskProviderList providers; 112 | 113 | // fill group with providers 114 | MockTaskProvider provider1; 115 | providers.addProvider(provider1); 116 | 117 | MockTaskProvider provider2; 118 | providers.addProvider(provider2); 119 | 120 | // Then remove first provider, so it shouldn't be called in any way 121 | providers.removeProvider(provider1); 122 | 123 | // Make an expectations 124 | EXPECT_CALL(provider1, nextTask()) 125 | .Times(0); 126 | 127 | EXPECT_CALL(provider2, nextTask()) 128 | .WillOnce([] { return MakeValidTask(); }) 129 | .WillOnce([] { return MakeValidTask(); }) 130 | .WillOnce([] { return MakeInvalidTask(); }); 131 | 132 | 133 | // Check it 134 | EXPECT_TRUE(providers.nextTask().valid()); 135 | EXPECT_TRUE(providers.nextTask().valid()); 136 | EXPECT_FALSE(providers.nextTask().valid()); 137 | } 138 | 139 | TEST(ExecutionPool, ThreadWorkerPool_NotifyWorkers_Single) 140 | { 141 | using namespace execq::impl; 142 | using namespace ::testing; 143 | 144 | std::vector> workers; 145 | 146 | std::unique_ptr worker1Ptr(new execq::test::MockThreadWorker{}); 147 | execq::test::MockThreadWorker& worker1 = *worker1Ptr; 148 | workers.push_back(std::move(worker1Ptr)); 149 | 150 | std::unique_ptr worker2Ptr(new execq::test::MockThreadWorker{}); 151 | execq::test::MockThreadWorker& worker2 = *worker2Ptr; 152 | workers.push_back(std::move(worker2Ptr)); 153 | 154 | 155 | // If the first worker is notified, the second wouldn't 156 | EXPECT_CALL(worker1, notifyWorker()) 157 | .WillOnce(Return(true)); 158 | EXPECT_CALL(worker2, notifyWorker()) 159 | .Times(0); 160 | 161 | const bool notifySingle = true; 162 | EXPECT_TRUE(details::NotifyWorkers(workers, notifySingle)); 163 | 164 | 165 | // If the first worker responds negatively for notification, the second should be notified. An so on 166 | EXPECT_CALL(worker1, notifyWorker()) 167 | .WillOnce(Return(false)); 168 | EXPECT_CALL(worker2, notifyWorker()) 169 | .WillOnce(Return(true)); 170 | 171 | EXPECT_TRUE(details::NotifyWorkers(workers, notifySingle)); 172 | 173 | 174 | // If both workers respond negatively, just general result will be 'false' 175 | EXPECT_CALL(worker1, notifyWorker()) 176 | .WillOnce(Return(false)); 177 | EXPECT_CALL(worker2, notifyWorker()) 178 | .WillOnce(Return(false)); 179 | 180 | EXPECT_FALSE(details::NotifyWorkers(workers, notifySingle)); 181 | } 182 | 183 | TEST(ExecutionPool, ThreadWorkerPool_NotifyWorkers_All) 184 | { 185 | using namespace execq::impl; 186 | using namespace ::testing; 187 | 188 | std::vector> workers; 189 | 190 | std::unique_ptr worker1Ptr(new execq::test::MockThreadWorker{}); 191 | execq::test::MockThreadWorker& worker1 = *worker1Ptr; 192 | workers.push_back(std::move(worker1Ptr)); 193 | 194 | std::unique_ptr worker2Ptr(new execq::test::MockThreadWorker{}); 195 | execq::test::MockThreadWorker& worker2 = *worker2Ptr; 196 | workers.push_back(std::move(worker2Ptr)); 197 | 198 | 199 | // All workers are notified regardless of their responce 200 | EXPECT_CALL(worker1, notifyWorker()) 201 | .WillOnce(Return(true)); 202 | EXPECT_CALL(worker2, notifyWorker()) 203 | .WillOnce(Return(false)); 204 | 205 | const bool notifySingle = false; 206 | EXPECT_TRUE(details::NotifyWorkers(workers, notifySingle)); 207 | 208 | 209 | EXPECT_CALL(worker1, notifyWorker()) 210 | .WillOnce(Return(false)); 211 | EXPECT_CALL(worker2, notifyWorker()) 212 | .WillOnce(Return(false)); 213 | 214 | EXPECT_FALSE(details::NotifyWorkers(workers, notifySingle)); 215 | } 216 | --------------------------------------------------------------------------------