├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── benchmark ├── README.txt └── go │ └── server.go ├── coroutines ├── CMakeLists.txt ├── algorithm.hpp ├── channel.hpp ├── channel_closed.hpp ├── condition_variable.hpp ├── coroutine.cpp ├── coroutine.hpp ├── generator.hpp ├── globals.cpp ├── globals.hpp ├── lock_free_channel.hpp ├── locking_channel.hpp ├── logging.hpp ├── monitor.cpp ├── monitor.hpp ├── mutex.hpp ├── processor.cpp ├── processor.hpp ├── processor_container.cpp ├── processor_container.hpp ├── scheduler.cpp ├── scheduler.hpp └── spsc_queue.hpp ├── coroutines_io ├── CMakeLists.txt ├── base_pollable.cpp ├── base_pollable.hpp ├── buffer.hpp ├── detail │ ├── poller.cpp │ └── poller.hpp ├── file.cpp ├── file.hpp ├── globals.cpp ├── globals.hpp ├── io_scheduler.cpp ├── io_scheduler.hpp ├── socket_streambuf.hpp ├── tcp_acceptor.cpp ├── tcp_acceptor.hpp ├── tcp_resolver.cpp ├── tcp_resolver.hpp ├── tcp_socket.cpp └── tcp_socket.hpp ├── http_test ├── CMakeLists.txt ├── client.cpp ├── client_connection.cpp ├── client_connection.hpp ├── http_request.cpp ├── http_request.hpp ├── http_response.cpp ├── http_response.hpp └── server.cpp ├── profiling ├── CMakeLists.txt ├── profiling.cpp └── profiling.hpp ├── profiling_analyzer ├── CMakeLists.txt └── main.cpp ├── profiling_gui ├── CMakeLists.txt ├── coroutinesmodel.cpp ├── coroutinesmodel.hpp ├── flowdiagram.cpp ├── flowdiagram.hpp ├── flowdiagram_items.cpp ├── flowdiagram_items.hpp ├── globals.cpp ├── globals.hpp ├── horizontalview.cpp ├── horizontalview.hpp ├── main.cpp ├── mainwindow.cpp ├── mainwindow.hpp └── mainwindow.ui ├── profiling_reader ├── CMakeLists.txt ├── reader.cpp └── reader.hpp ├── test ├── CMakeLists.txt ├── channel_tests.cpp ├── clang_asan.sh ├── fibonacci.py ├── fixtures.cpp ├── fixtures.hpp ├── generator_tests.cpp ├── generator_tests.hpp ├── main.cpp ├── mutex_tests.cpp └── scheduler_tests.cpp ├── test_io ├── CMakeLists.txt └── main.cpp └── torture ├── CMakeLists.txt ├── buffer.hpp ├── lzma_decompress.cpp ├── lzma_decompress.hpp ├── main.cpp ├── parallel.cpp ├── parallel.hpp └── serial.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.pro.user 2 | CMakeLists.txt.user 3 | .asan 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(coroutines) 2 | cmake_minimum_required(VERSION 2.8) 3 | 4 | find_package( Boost 1.54.0 ) 5 | include_directories(${Boost_INCLUDE_DIRS}) 6 | 7 | include_directories(${CMAKE_SOURCE_DIR}) 8 | 9 | set(CMAKE_CXX_FLAGS 10 | "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wno-unused-local-typedefs") 11 | 12 | add_subdirectory(coroutines) 13 | add_subdirectory(coroutines_io) 14 | 15 | add_subdirectory(profiling) 16 | add_subdirectory(profiling_reader) 17 | add_subdirectory(profiling_analyzer) 18 | add_subdirectory(profiling_gui) 19 | 20 | add_subdirectory(test) 21 | add_subdirectory(test_io) 22 | add_subdirectory(torture) 23 | add_subdirectory(http_test) 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | coroutines 2 | ========== 3 | 4 | Experiments with coroutines in C++ 5 | 6 | Examples here: http://maciekgajewskiprogramming.blogspot.nl/ 7 | -------------------------------------------------------------------------------- /benchmark/README.txt: -------------------------------------------------------------------------------- 1 | Various small HTTP servers benchamrked agains coroutines 2 | 3 | -------------------------------------------------------------------------------- /benchmark/go/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "io" 6 | "runtime" 7 | ) 8 | 9 | func HelloServer(w http.ResponseWriter, req *http.Request) { 10 | w.Header().Set("Content-Type", "text/plain") 11 | w.Header().Set("Connection", "keep-alive") 12 | w.Header().Set("Content-Length", "14") 13 | io.WriteString(w, "hello, world!\n") 14 | } 15 | 16 | func main() { 17 | runtime.GOMAXPROCS(4) 18 | http.HandleFunc("/", HelloServer) 19 | http.ListenAndServe(":8081", nil) 20 | } 21 | 22 | -------------------------------------------------------------------------------- /coroutines/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package( Boost 1.54.0 COMPONENTS context system) 2 | include_directories(${Boost_INCLUDE_DIRS}) 3 | 4 | if(ENABLE_PROFILING) 5 | message(STATUS "Profiling enabled") 6 | add_definitions(-DCOROUTINES_PROFILING) 7 | set(PROFILING_LIB profiling) 8 | 9 | if(ENABLE_SPINLOCK_PROFILING) 10 | message(STATUS "Spinlock profiling enabled") 11 | add_definitions(-DCOROUTINES_SPINLOCKS_PROFILING) 12 | endif() 13 | 14 | endif() 15 | 16 | 17 | add_library(coroutines STATIC 18 | algorithm.hpp 19 | channel.hpp 20 | channel_closed.hpp 21 | condition_variable.hpp 22 | coroutine.cpp coroutine.hpp 23 | generator.hpp 24 | globals.cpp globals.hpp 25 | lock_free_channel.hpp 26 | locking_channel.hpp 27 | monitor.cpp monitor.hpp 28 | mutex.hpp 29 | scheduler.cpp scheduler.hpp 30 | spsc_queue.hpp 31 | logging.hpp 32 | processor.cpp processor.hpp 33 | processor_container.cpp processor_container.hpp 34 | ) 35 | 36 | target_link_libraries(coroutines 37 | ${PROFILING_LIB} 38 | ${Boost_LIBRARIES} 39 | pthread 40 | ) 41 | 42 | 43 | -------------------------------------------------------------------------------- /coroutines/algorithm.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #ifndef COROUTINES_ALGORITHM_HPP 3 | #define COROUTINES_ALGORITHM_HPP 4 | 5 | #include 6 | 7 | // various algorithms used in the lib 8 | 9 | namespace coroutines { 10 | 11 | // finds a pointer in smart-pointer container 12 | template 13 | auto find_ptr(Container& ctr, const T* ptr) -> decltype(ctr.begin()) 14 | { 15 | auto it = std::find_if( 16 | ctr.begin(), ctr.end(), 17 | [ptr](const typename Container::value_type& i) 18 | { 19 | return i.get() == ptr; 20 | }); 21 | return it; 22 | } 23 | 24 | // copies elements from IN to OUT, if predicate is true, trnasofrimg tjhem using UNARY_OP 25 | template 26 | void transform_if(InputIterator begin, InputIterator end, OutputIterator out, UnaryOp trans, Predicate pred) 27 | { 28 | std::for_each(begin, end, [&](const decltype(*begin)& item) 29 | { 30 | if(pred(item)) 31 | { 32 | *(out++) = trans(item); 33 | } 34 | }); 35 | } 36 | 37 | 38 | } 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /coroutines/channel.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #ifndef COROUTINES_CHANNEL_HPP 4 | #define COROUTINES_CHANNEL_HPP 5 | 6 | #include "coroutines/locking_channel.hpp" 7 | 8 | #include 9 | #include 10 | 11 | namespace coroutines { 12 | 13 | 14 | // writer enppoint to a channel 15 | template::writer> 16 | class channel_writer 17 | { 18 | public: 19 | 20 | channel_writer() noexcept = default; 21 | channel_writer(const channel_writer&) = default; 22 | channel_writer(channel_writer&& o) noexcept 23 | { 24 | std::swap(o._impl, _impl); 25 | } 26 | 27 | channel_writer(std::shared_ptr&& impl) noexcept 28 | : _impl(std::move(impl)) 29 | { } 30 | 31 | channel_writer(const std::shared_ptr& impl) noexcept 32 | : _impl(impl) 33 | { } 34 | 35 | ~channel_writer() 36 | { 37 | } 38 | 39 | channel_writer& operator=(channel_writer&& o) 40 | { 41 | std::swap(o._impl, _impl); 42 | return *this; 43 | } 44 | 45 | void put(T val) 46 | { 47 | if (_impl) 48 | _impl->put(std::move(val)); 49 | else 50 | throw channel_closed(); 51 | } 52 | 53 | // does not throw when channel is closed 54 | void put_nothrow(T val) 55 | { 56 | try 57 | { 58 | if (_impl) 59 | _impl->put(std::move(val)); 60 | } 61 | catch(const channel_closed&) 62 | { 63 | } 64 | } 65 | 66 | void close() 67 | { 68 | _impl.reset(); 69 | } 70 | 71 | private: 72 | 73 | std::shared_ptr _impl; 74 | }; 75 | 76 | template::reader> 77 | class channel_reader 78 | { 79 | public: 80 | 81 | channel_reader() noexcept = default; 82 | channel_reader(const channel_reader&) = default; 83 | channel_reader(channel_reader&& o) 84 | { 85 | std::swap(o._impl, _impl); 86 | } 87 | 88 | channel_reader& operator=(channel_reader&& o) 89 | { 90 | std::swap(o._impl, _impl); 91 | return *this; 92 | } 93 | 94 | channel_reader(std::shared_ptr&& impl) noexcept 95 | : _impl(std::move(impl)) 96 | { } 97 | 98 | channel_reader(const std::shared_ptr& impl) 99 | : _impl(impl) 100 | { } 101 | 102 | ~channel_reader() 103 | { 104 | } 105 | 106 | 107 | T get() 108 | { 109 | if (_impl) 110 | return _impl->get(); 111 | else 112 | throw channel_closed(); 113 | } 114 | 115 | bool try_get(T& b) 116 | { 117 | if (_impl) 118 | return _impl->try_get(b); 119 | else 120 | throw channel_closed(); 121 | } 122 | 123 | void close() 124 | { 125 | _impl.reset(); 126 | } 127 | 128 | bool is_closed() const noexcept 129 | { 130 | return !_impl; 131 | } 132 | 133 | private: 134 | 135 | std::shared_ptr _impl; 136 | }; 137 | 138 | template> 139 | struct channel_pair 140 | { 141 | typedef channel_reader reader_type; 142 | typedef channel_writer writer_type; 143 | 144 | // factory 145 | static channel_pair make(scheduler& sched, std::size_t capacity, const std::string& name) 146 | { 147 | std::shared_ptr channel(std::make_shared(sched, capacity, name)); 148 | std::shared_ptr writer(std::make_shared(channel)); 149 | std::shared_ptr reader(std::make_shared(channel)); 150 | 151 | return channel_pair(reader_type(reader), writer_type(writer)); 152 | } 153 | 154 | channel_pair(const channel_pair& o) = default; 155 | 156 | channel_pair(channel_pair&& o) 157 | : reader(std::move(o.reader)) 158 | , writer(std::move(o.writer)) 159 | { } 160 | 161 | channel_pair(reader_type&& r, writer_type&& w) 162 | : reader(std::move(r)) 163 | , writer(std::move(w)) 164 | { } 165 | 166 | reader_type reader; 167 | writer_type writer; 168 | }; 169 | 170 | } 171 | 172 | #endif 173 | -------------------------------------------------------------------------------- /coroutines/channel_closed.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #ifndef COROUTINES_CHANNEL_CLOSED_HPP 4 | #define COROUTINES_CHANNEL_CLOSED_HPP 5 | 6 | #include 7 | 8 | namespace coroutines { 9 | 10 | // Exception thrown when channel is closed 11 | struct channel_closed : public std::exception 12 | { 13 | virtual const char* what() const noexcept { return "channel closed"; } 14 | }; 15 | 16 | } 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /coroutines/condition_variable.hpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #ifndef CONDITION_VARIABLE_HPP 3 | #define CONDITION_VARIABLE_HPP 4 | 5 | #include "monitor.hpp" 6 | 7 | namespace coroutines { 8 | 9 | // coroutine version of condition variables. 10 | // partially source-compatible with std::consdition_variable_any 11 | class condition_variable 12 | { 13 | public: 14 | 15 | condition_variable(scheduler& sched) 16 | : _monitor(sched) 17 | { } 18 | 19 | void notify_all() 20 | { 21 | _monitor.wake_all(); 22 | } 23 | 24 | void notify_one() 25 | { 26 | _monitor.wake_one(); 27 | } 28 | 29 | // Unlocks the lock and waits in an atomic way. 30 | template 31 | void wait(const std::string& checkpoint_name, Lock& lock); 32 | 33 | template 34 | void wait(const std::string& checkpoint_name, Lock& lock, Predicate pred) 35 | { 36 | while(!pred()) 37 | wait(checkpoint_name, lock); 38 | } 39 | 40 | private: 41 | 42 | monitor _monitor; 43 | }; 44 | 45 | 46 | template 47 | void condition_variable::wait(const std::string& checkpoint_name, Lock& lock) 48 | { 49 | _monitor.wait(checkpoint_name, [&lock]() 50 | { 51 | // this code will bve called after the coroutine yields and its added to monitor 52 | lock.unlock(); 53 | }); 54 | lock.lock(); 55 | } 56 | 57 | } 58 | 59 | #endif // CONDITION_VARIABLE_HPP 60 | -------------------------------------------------------------------------------- /coroutines/coroutine.cpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #include "coroutines/coroutine.hpp" 3 | #include "coroutines/channel.hpp" 4 | #include "coroutines/scheduler.hpp" 5 | 6 | //#define CORO_LOGGING 7 | #include "coroutines/logging.hpp" 8 | 9 | #include "profiling/profiling.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace coroutines { 16 | 17 | static const unsigned DEFAULT_STACK_SIZE = 64*1024; // 64kb should be enough for anyone :) 18 | static thread_local coroutine* __current_coroutine = nullptr; 19 | 20 | coroutine::coroutine(scheduler& parent, std::string name, function_type&& fun) 21 | : _function(std::move(fun)) 22 | , _stack(new char[DEFAULT_STACK_SIZE]) 23 | #ifdef COROUTINES_SPINLOCKS_PROFILING 24 | , _run_mutex(std::string("coro " + name + " run mutex").c_str()) 25 | #endif 26 | , _parent(parent) 27 | , _name(std::move(name)) 28 | { 29 | CORO_PROF("coroutine", this, "created", _name.c_str()); 30 | 31 | _new_context = boost::context::make_fcontext( 32 | _stack + DEFAULT_STACK_SIZE, 33 | DEFAULT_STACK_SIZE, 34 | &coroutine::static_context_function); 35 | 36 | } 37 | 38 | coroutine::~coroutine() 39 | { 40 | CORO_PROF("coroutine", this, "destroyed"); 41 | CORO_LOG("CORO=", this, " destroyed"); 42 | if (_new_context) 43 | { 44 | std::cerr<< "FATAL: coroutine '" << _name << "' destroyed before completed. Last checkpoint: " << _last_checkpoint << std::endl; 45 | } 46 | assert(!_new_context); 47 | delete[] _stack; 48 | } 49 | 50 | void coroutine::run() 51 | { 52 | { 53 | std::lock_guard lock(_run_mutex); // the coro may be reshdelued in epilogue, and run imemdiately in different thread 54 | 55 | CORO_LOG("CORO starting or resuming '", _name, "'"); 56 | assert(_new_context); 57 | 58 | coroutine* previous = __current_coroutine; 59 | __current_coroutine = this; 60 | 61 | CORO_PROF("coroutine", this, "enter"); 62 | boost::context::jump_fcontext(&_caller_context, _new_context, reinterpret_cast(this)); 63 | CORO_PROF("coroutine", this, "exit"); 64 | 65 | __current_coroutine = previous; 66 | 67 | CORO_LOG("CORO=", this, " finished or preemepted"); 68 | 69 | if (_epilogue) 70 | { 71 | assert(_new_context); 72 | _epilogue(this); 73 | _epilogue = nullptr; 74 | } 75 | } 76 | 77 | if (!_new_context) 78 | { 79 | _parent.coroutine_finished(this); // this wil destroy the object (delete this) 80 | } 81 | } 82 | 83 | coroutine* coroutine::current_corutine() 84 | { 85 | return __current_coroutine; 86 | } 87 | 88 | void coroutine::yield(const std::string& checkpoint_name, epilogue_type epilogue) 89 | { 90 | assert(__current_coroutine == this); 91 | 92 | _last_checkpoint = checkpoint_name; 93 | 94 | _epilogue = std::move(epilogue); 95 | boost::context::jump_fcontext(_new_context, &_caller_context, 0); 96 | } 97 | 98 | void coroutine::static_context_function(intptr_t param) 99 | { 100 | coroutine* _this = reinterpret_cast(param); 101 | _this->context_function(); 102 | } 103 | 104 | void coroutine::context_function() 105 | { 106 | CORO_LOG("CORO: starting '", _name, "'"); 107 | try 108 | { 109 | _last_checkpoint = "started"; 110 | _function(); 111 | _last_checkpoint = "finished cleanly"; 112 | } 113 | catch(const channel_closed&) 114 | { 115 | _last_checkpoint = "finished after channel close"; 116 | } 117 | catch(const std::exception& e) 118 | { 119 | _last_checkpoint = std::string("uncaught exception: ") + e.what(); 120 | std::cerr << "Uncaught exception in " << _name << " : " << e.what() << std::endl; 121 | std::terminate(); 122 | } 123 | catch(...) 124 | { 125 | _last_checkpoint = "uncaught exception"; 126 | std::cerr << "Uncaught exception in " << _name << std::endl; 127 | std::terminate(); 128 | } 129 | 130 | CORO_LOG("CORO: finished cleanly '", _name, "'"); 131 | 132 | auto temp = _new_context; 133 | _new_context = nullptr;// to mark the completion 134 | boost::context::jump_fcontext(temp, &_caller_context, 0); 135 | } 136 | 137 | 138 | 139 | } 140 | -------------------------------------------------------------------------------- /coroutines/coroutine.hpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #ifndef COROUTINES_COROUTINE_HPP 3 | #define COROUTINES_COROUTINE_HPP 4 | 5 | #include "coroutines/mutex.hpp" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace coroutines { 16 | 17 | class scheduler; 18 | 19 | class coroutine 20 | { 21 | public: 22 | typedef std::function function_type; 23 | typedef std::function epilogue_type; 24 | 25 | coroutine(scheduler& parent, std::string name, function_type&& fun); 26 | ~coroutine(); 27 | 28 | coroutine(const coroutine&) = delete; 29 | coroutine(coroutine&&) = delete; 30 | 31 | void run(); 32 | 33 | // returns currently runnig coroutine 34 | static coroutine* current_corutine(); 35 | 36 | void yield(const std::string& checkpoint_name, epilogue_type epilogue = epilogue_type()); 37 | 38 | std::string name() const { return _name; } 39 | std::string last_checkpoint() const { return _last_checkpoint; } 40 | void set_checkpoint(const std::string& cp) { _last_checkpoint = cp; } 41 | 42 | private: 43 | 44 | 45 | static void static_context_function(intptr_t param); 46 | void context_function(); 47 | 48 | std::function _function; 49 | 50 | boost::context::fcontext_t _caller_context; 51 | boost::context::fcontext_t* _new_context = nullptr; 52 | 53 | char* _stack = nullptr; 54 | epilogue_type _epilogue; 55 | mutex _run_mutex; 56 | scheduler& _parent; 57 | std::string _name; 58 | std::string _last_checkpoint = "just created"; 59 | }; 60 | 61 | template 62 | class callable_wrapper 63 | { 64 | public: 65 | callable_wrapper(Callable&& c) 66 | : _callable(std::move(c)) 67 | { 68 | } 69 | 70 | void operator()() 71 | { 72 | _callable(); 73 | } 74 | 75 | private: 76 | 77 | Callable _callable; 78 | }; 79 | 80 | typedef std::unique_ptr coroutine_ptr; 81 | typedef coroutine* coroutine_weak_ptr; // this couldbe made smarter later on 82 | 83 | template 84 | coroutine_ptr make_coroutine(scheduler& parent, std::string name, Callable&& c) 85 | { 86 | callable_wrapper* wrapper = new callable_wrapper(std::move(c)); 87 | 88 | return coroutine_ptr(new coroutine(parent, std::move(name), [wrapper]() 89 | { 90 | try 91 | { 92 | (*wrapper)(); 93 | delete wrapper; 94 | } 95 | catch(...) 96 | { 97 | delete wrapper; 98 | throw; 99 | } 100 | 101 | })); 102 | } 103 | 104 | 105 | } 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /coroutines/generator.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #ifndef COROUTINES_GENERATOR_HPP 3 | #define COROUTINES_GENERATOR_HPP 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | namespace corountines { 15 | 16 | // exception thrown where generator function exists 17 | struct generator_finished : public std::exception 18 | { 19 | virtual const char* what() const noexcept { return "generator finished"; } 20 | }; 21 | 22 | template 23 | class generator 24 | { 25 | public: 26 | 27 | typedef std::function yield_function_type; 28 | typedef std::function generator_function_type; 29 | 30 | // former 'init()' 31 | generator(generator_function_type generator, std::size_t stack_size = DEFAULT_STACK_SIZE) 32 | : _generator(std::move(generator)) 33 | { 34 | // allocate stack for new context 35 | _stack = new char[stack_size]; 36 | 37 | // make a new context. The returned fcontext_t is created on the new stack, so there is no need to delete it 38 | _new_context = boost::context::make_fcontext( 39 | _stack + stack_size, // new stack pointer. On x86/64 it hast be the TOP of the stack (hence the "+ STACK_SIZE") 40 | stack_size, 41 | &generator::static_generator_function); // will call generator wrapper 42 | } 43 | 44 | // prevent copying 45 | generator(const generator&) = delete; 46 | 47 | // former 'cleanup()' 48 | ~generator() 49 | { 50 | delete _stack; 51 | _stack = nullptr; 52 | _new_context = nullptr; 53 | } 54 | 55 | ReturnType next() 56 | { 57 | // prevent calling when the generator function already finished 58 | if (_exception) 59 | std::rethrow_exception(_exception); 60 | 61 | // switch to function context. May set _exception 62 | boost::context::jump_fcontext(&_main_context, _new_context, reinterpret_cast(this)); 63 | if (_exception) 64 | std::rethrow_exception(_exception); 65 | else 66 | return *_return_value; 67 | } 68 | 69 | private: 70 | 71 | // former global variables 72 | boost::context::fcontext_t _main_context; // will hold the main execution context 73 | boost::context::fcontext_t* _new_context = nullptr; // will point to the new context 74 | static const int DEFAULT_STACK_SIZE= 64*1024; // completely arbitrary value 75 | char* _stack = nullptr; 76 | 77 | generator_function_type _generator; // generator function 78 | 79 | std::exception_ptr _exception = nullptr;// pointer to exception thrown by generator function 80 | boost::optional _return_value; // optional allows for using typed without defautl constructor 81 | 82 | 83 | // the actual generator function used to create context 84 | static void static_generator_function(intptr_t param) 85 | { 86 | generator* _this = reinterpret_cast(param); 87 | _this->generator_wrapper(); 88 | } 89 | 90 | void yield(const ReturnType& value) 91 | { 92 | _return_value = value; 93 | boost::context::jump_fcontext(_new_context, &_main_context, 0); // switch back to the main context 94 | } 95 | 96 | void generator_wrapper() 97 | { 98 | try 99 | { 100 | _generator([this](const ReturnType& value) // use lambda to bind this to yield 101 | { 102 | yield(value); 103 | }); 104 | throw generator_finished(); 105 | } 106 | catch(...) 107 | { 108 | // store the exception, is it can be thrown back in the main context 109 | _exception = std::current_exception(); 110 | boost::context::jump_fcontext(_new_context, &_main_context, 0); // switch back to the main context 111 | } 112 | } 113 | }; 114 | 115 | } 116 | 117 | #endif 118 | -------------------------------------------------------------------------------- /coroutines/globals.cpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #include "globals.hpp" 3 | 4 | namespace coroutines 5 | { 6 | 7 | static scheduler* __scheduler = nullptr; 8 | 9 | void set_scheduler(scheduler* sched) 10 | { 11 | __scheduler = sched; 12 | } 13 | 14 | scheduler* get_scheduler() 15 | { 16 | return __scheduler; 17 | } 18 | 19 | scheduler& get_scheduler_check() 20 | { 21 | assert(__scheduler); 22 | return *__scheduler; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /coroutines/globals.hpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #ifndef COROUTINES_GLOBALS_HPP 3 | #define COROUTINES_GLOBALS_HPP 4 | 5 | #include "coroutines/scheduler.hpp" 6 | #include "coroutines/processor.hpp" 7 | 8 | // global functions used in channle-based concurent programming 9 | 10 | namespace coroutines 11 | { 12 | 13 | void set_scheduler(scheduler* sched); 14 | scheduler* get_scheduler(); 15 | scheduler& get_scheduler_check(); // asserts scheduler not null 16 | 17 | template 18 | void go(std::string name, Callable&& fn, Args&&... args) 19 | { 20 | get_scheduler_check().go(name, std::forward(fn), std::forward(args)...); 21 | } 22 | 23 | template 24 | void go(const char* name, Callable&& fn, Args&&... args) 25 | { 26 | assert(get_scheduler()); 27 | get_scheduler_check().go(std::string(name), std::forward(fn), std::forward(args)...); 28 | } 29 | 30 | template 31 | void go(Callable&& fn, Args&&... args) 32 | { 33 | get_scheduler_check().go(std::forward(fn), std::forward(args)...); 34 | } 35 | 36 | // create channel 37 | template 38 | channel_pair make_channel(std::size_t capacity, const std::string& name = std::string()) 39 | { 40 | return get_scheduler_check().make_channel(capacity, name); 41 | } 42 | 43 | // begin blocking operation 44 | // starting coroutines is not allowed in blocking mode 45 | inline void block(const std::string& checkpoint_name = std::string()) 46 | { 47 | processor* pc = processor::current_processor(); 48 | coroutine::current_corutine()->set_checkpoint(checkpoint_name); 49 | assert(pc); 50 | pc->block(); 51 | } 52 | 53 | inline void block(const char* checkpoint_name) { block(std::string(checkpoint_name)); } 54 | 55 | // ends blocking mode. may preempt current coroutine 56 | inline void unblock(const std::string& checkpoint_name = std::string()) 57 | { 58 | processor* pc = processor::current_processor(); 59 | coroutine::current_corutine()->set_checkpoint(checkpoint_name); 60 | assert(pc); 61 | pc->unblock(); 62 | } 63 | 64 | inline void unblock(const char* checkpoint_name) { unblock(std::string(checkpoint_name)); } 65 | 66 | // blocks code framgment, exception-safe 67 | template 68 | void block(Callable callable) 69 | { 70 | block(); 71 | try 72 | { 73 | callable(); 74 | unblock(); 75 | } 76 | catch(...) 77 | { 78 | unblock(); 79 | throw; 80 | } 81 | } 82 | 83 | template 84 | void block(const std::string& checkpoint_name, Callable callable) 85 | { 86 | block(checkpoint_name + " block"); 87 | try 88 | { 89 | callable(); 90 | unblock(checkpoint_name + " unblock"); 91 | } 92 | catch(...) 93 | { 94 | unblock(checkpoint_name + " unblock after exception"); 95 | throw; 96 | } 97 | } 98 | 99 | } 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /coroutines/lock_free_channel.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINES_COROUTINE_CHANNEL_HPP 2 | #define COROUTINES_COROUTINE_CHANNEL_HPP 3 | 4 | #include "channel.hpp" 5 | #include "spsc_queue.hpp" 6 | #include "monitor.hpp" 7 | 8 | namespace coroutines { 9 | 10 | 11 | // DOES NOT WORK 12 | // it may work if two mutexes would be replace by single reader/writer lock 13 | // to be revisited 14 | template 15 | class lock_free_channel : public i_writer_impl, public i_reader_impl 16 | { 17 | public: 18 | 19 | // factory 20 | static channel_pair make(std::size_t capacity) 21 | { 22 | std::shared_ptr> me(new lock_free_channel(capacity)); 23 | return channel_pair(channel_reader(me), channel_writer(me)); 24 | } 25 | 26 | lock_free_channel(const lock_free_channel&) = delete; 27 | 28 | // called by producer 29 | virtual void put(T v) override; 30 | virtual void writer_close() override { _closed = true; } 31 | 32 | // caled by consumer 33 | virtual T get() override; 34 | virtual void reader_close() { _closed = true; } 35 | 36 | 37 | private: 38 | 39 | lock_free_channel(std::size_t capacity); 40 | void do_close(); 41 | 42 | spsc_queue _queue; 43 | 44 | monitor _reader_monitor, _writer_monitor; 45 | 46 | volatile bool _closed = false; 47 | }; 48 | 49 | template 50 | lock_free_channel::lock_free_channel(std::size_t capacity) 51 | : _queue(capacity+1) 52 | { 53 | 54 | } 55 | 56 | template 57 | void lock_free_channel::put(T v) 58 | { 59 | while(!_queue.put(v) && !_closed) 60 | { 61 | _writer_monitor.wait(); 62 | } 63 | 64 | if (_closed) 65 | throw channel_closed(); 66 | 67 | _reader_monitor.wake_one(); 68 | } 69 | 70 | template 71 | T lock_free_channel::get() 72 | { 73 | T v; 74 | bool success = false; 75 | while(!(success = _queue.get(v)) && !_closed) 76 | { 77 | _reader_monitor.wait(); 78 | } 79 | 80 | if (!success) 81 | { 82 | assert(_closed); 83 | throw channel_closed(); 84 | } 85 | 86 | _writer_monitor.wake_one(); 87 | return v; 88 | } 89 | 90 | 91 | 92 | } 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /coroutines/locking_channel.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINES_LOCKING_COROUTINE_CHANNEL_HPP 2 | #define COROUTINES_LOCKING_COROUTINE_CHANNEL_HPP 3 | 4 | #include "coroutines/channel.hpp" 5 | #include "coroutines/mutex.hpp" 6 | #include "coroutines/condition_variable.hpp" 7 | #include "coroutines/channel_closed.hpp" 8 | 9 | #include 10 | 11 | #include 12 | 13 | namespace coroutines { 14 | 15 | class scheduler; 16 | 17 | // non-lock-free implementation 18 | template 19 | class locking_channel 20 | { 21 | public: 22 | 23 | locking_channel(scheduler& _sched, std::size_t capacity, const std::string& name); 24 | locking_channel(const locking_channel&) = delete; 25 | ~locking_channel(); 26 | 27 | class writer 28 | { 29 | public: 30 | writer(const std::shared_ptr& impl) 31 | : _impl(impl) { } 32 | 33 | virtual void put(T v) { _impl->put(std::move(v)); } 34 | virtual void writer_close() { _impl->writer_close(); } 35 | virtual ~writer() { _impl->writer_close(); } 36 | 37 | private: 38 | 39 | std::shared_ptr _impl; 40 | }; 41 | 42 | class reader 43 | { 44 | public: 45 | reader(const std::shared_ptr& impl) 46 | : _impl(impl) { } 47 | 48 | virtual T get() { return std::move(_impl->get()); } 49 | virtual bool try_get(T& b) { return _impl->try_get(b); } 50 | virtual void reader_close() { _impl->reader_close(); } 51 | virtual ~reader() { _impl->reader_close(); } 52 | 53 | private: 54 | 55 | std::shared_ptr _impl; 56 | }; 57 | 58 | // called by producer 59 | void put(T v); 60 | void writer_close() { do_close(); } 61 | 62 | // caled by consumer 63 | T get(); 64 | bool try_get(T& b); 65 | void reader_close() { do_close(); } 66 | 67 | private: 68 | 69 | void do_close(); 70 | 71 | unsigned size() const 72 | { 73 | int s = _wr - _rd; 74 | if (s < 0) 75 | s += _capacity; 76 | //std::cout << "CHAN: _wr=" << _wr << ", _rd=" << _rd << ", size=" << s << std::endl; 77 | return (unsigned)s; 78 | } 79 | 80 | int wr_next() const 81 | { 82 | return (_wr + 1) % _capacity; 83 | } 84 | 85 | T* _data; 86 | int _rd = 0; 87 | int _wr = 0; 88 | std::size_t _capacity; 89 | condition_variable _producers_cv; 90 | condition_variable _consumers_cv; 91 | mutex _mutex; 92 | 93 | bool _closed = false; 94 | 95 | const std::string _read_checkpoint; 96 | const std::string _write_checkpoint; 97 | }; 98 | 99 | template 100 | locking_channel::locking_channel(scheduler& sched, std::size_t capacity, const std::string& name) 101 | : _data(static_cast(std::malloc(sizeof(T) * (capacity+1)))) 102 | , _capacity(capacity+1) 103 | , _producers_cv(sched) 104 | , _consumers_cv(sched) 105 | , _mutex("channel mutex") 106 | 107 | , _read_checkpoint(name + " : reading") 108 | , _write_checkpoint(name + " : writing") 109 | { 110 | assert(capacity >= 1); 111 | if (!_data) 112 | { 113 | throw std::bad_alloc(); 114 | } 115 | } 116 | 117 | template 118 | locking_channel::~locking_channel() 119 | { 120 | // destroy anything that could still be in there 121 | int rd = _rd; 122 | int wr = _wr; 123 | while(rd != wr) 124 | { 125 | _data[rd].~T(); 126 | rd = (rd+1) % _capacity; 127 | } 128 | std::free(_data); 129 | } 130 | 131 | template 132 | void locking_channel::put(T v) 133 | { 134 | std::lock_guard lock(_mutex); 135 | 136 | _producers_cv.wait(_write_checkpoint, _mutex, [=]()// WARNING: the value of _wr & _rd may be different before and after waiting (modified by another threads) 137 | { 138 | return _rd != wr_next() || _closed; 139 | }); 140 | 141 | if (_closed) 142 | throw channel_closed(); 143 | 144 | new(&_data[_wr]) T(std::move(v)); 145 | _wr = wr_next(); 146 | 147 | //std::cout << boost::format("CHAN: after write, %d left in channel. _wr=%d, _rd=%d") % size() % _wr % _rd << std::endl; 148 | if (size() == 1) 149 | _consumers_cv.notify_all(); 150 | } 151 | 152 | template 153 | T locking_channel::get() 154 | { 155 | std::lock_guard lock(_mutex); 156 | 157 | _consumers_cv.wait(_read_checkpoint, _mutex, [=]() { return _rd != _wr || _closed; }); 158 | 159 | if (_rd == _wr) 160 | { 161 | assert(_closed); 162 | throw channel_closed(); 163 | } 164 | 165 | T v(std::move(_data[_rd])); 166 | _data[_rd].~T(); 167 | _rd++; 168 | if (_rd == _capacity) 169 | _rd = 0; 170 | 171 | //std::cout << "CHAN: after read, " << size() << " left in channel" << std::endl; 172 | if (size() == _capacity - 2) 173 | _producers_cv.notify_all(); 174 | 175 | return v; 176 | } 177 | 178 | template 179 | bool locking_channel::try_get(T& b) 180 | { 181 | std::lock_guard lock(_mutex); 182 | 183 | if (_rd == _wr) 184 | { 185 | return false; 186 | } 187 | else 188 | { 189 | b = std::move(_data[_rd]); 190 | _data[_rd].~T(); 191 | _rd++; 192 | if (_rd == _capacity) 193 | _rd = 0; 194 | 195 | if (size() == _capacity - 2) 196 | _producers_cv.notify_all(); 197 | 198 | return true; 199 | } 200 | } 201 | 202 | 203 | template 204 | void locking_channel::do_close() 205 | { 206 | std::lock_guard lock(_mutex); 207 | _closed = true; 208 | _producers_cv.notify_all(); 209 | _consumers_cv.notify_all(); 210 | } 211 | 212 | 213 | } 214 | 215 | #endif 216 | -------------------------------------------------------------------------------- /coroutines/logging.hpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #ifndef COROUTINES_LOGGING_HPP 3 | #define COROUTINES_LOGGING_HPP 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace coroutines_logging { 10 | 11 | 12 | inline void _coro_log_impl(std::ostringstream& ss) {} 13 | 14 | template 15 | void _coro_log_impl(std::ostringstream& ss, const Arg& arg, const Args&... args) 16 | { 17 | ss << arg; 18 | _coro_log_impl(ss, args...); 19 | } 20 | 21 | template 22 | void _coro_log(const Args&... args) 23 | { 24 | std::ostringstream ss; 25 | ss << "[" << std::this_thread::get_id() << "] "; 26 | _coro_log_impl(ss, args...); 27 | ss << std::endl; 28 | std::cout << ss.str(); 29 | std::cout.flush(); 30 | } 31 | 32 | } 33 | 34 | #ifdef CORO_LOGGING 35 | template void CORO_LOG (const Args&... args) { coroutines_logging::_coro_log(args...); } 36 | #else 37 | #define CORO_LOG(...) ; 38 | #endif 39 | 40 | 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /coroutines/monitor.cpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #include "coroutines/monitor.hpp" 3 | 4 | #include "coroutines/coroutine.hpp" 5 | #include "coroutines/scheduler.hpp" 6 | 7 | //#define CORO_LOGGING 8 | #include "coroutines/logging.hpp" 9 | 10 | #include "profiling/profiling.hpp" 11 | 12 | 13 | namespace coroutines { 14 | 15 | monitor::monitor(scheduler& sched) 16 | : _waiting_mutex("monitor") 17 | , _scheduler(sched) 18 | { 19 | } 20 | 21 | monitor::~monitor() 22 | { 23 | //std::cout << "MONITOR: this=" << this << " deleting" << std::endl; 24 | assert(_waiting.empty()); 25 | } 26 | 27 | void monitor::wait(const std::string& checkopint_name, epilogue_type epilogue) 28 | { 29 | CORO_PROF("monitor", this, "wait", checkopint_name.c_str()); 30 | 31 | coroutine* coro = coroutine::current_corutine(); 32 | assert(coro); 33 | 34 | CORO_LOG("MONITOR: this=", this, " '", coro->name(), "' will wait"); 35 | 36 | coro->yield(checkopint_name, [this, epilogue](coroutine_weak_ptr coro) 37 | { 38 | CORO_LOG("MONITOR: this=", this, " '", coro->name(), "' added to queue"); 39 | { 40 | std::lock_guard lock(_waiting_mutex); 41 | _waiting.push_back(std::move(coro)); 42 | } 43 | if (epilogue) 44 | epilogue(); 45 | }); 46 | } 47 | 48 | void monitor::wake_all() 49 | { 50 | CORO_LOG("MONITOR: wake_all"); 51 | 52 | std::vector waiting; 53 | { 54 | std::lock_guard lock(_waiting_mutex); 55 | _waiting.swap(waiting); 56 | } 57 | 58 | CORO_LOG("MONITOR: waking up ", waiting.size(), " coroutine(s)"); 59 | 60 | if (!waiting.empty()) 61 | { 62 | CORO_PROF("monitor", this, "wake_all"); 63 | _scheduler.schedule(waiting.begin(), waiting.end()); 64 | } 65 | } 66 | 67 | void monitor::wake_one() 68 | { 69 | CORO_LOG("MONITOR: this=", this, " will wake one. q contains: '", _waiting.size()) 70 | 71 | coroutine_weak_ptr waiting = nullptr; 72 | { 73 | std::lock_guard lock(_waiting_mutex); 74 | if (!_waiting.empty()) 75 | { 76 | waiting = _waiting.back(); 77 | _waiting.pop_back(); 78 | } 79 | } 80 | 81 | if (waiting) 82 | { 83 | CORO_PROF("monitor", this, "wake_one"); 84 | CORO_LOG("MONITOR: this=", this, " waking up one coroutine ('", waiting->name(), "'), ", _waiting.size(), " left in q"); 85 | _scheduler.schedule(std::move(waiting)); 86 | } 87 | else 88 | { 89 | CORO_LOG("MONITOR: this=", this, " nothign to wake"); 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /coroutines/monitor.hpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #ifndef COROUTINES_MONITOR_HPP 3 | #define COROUTINES_MONITOR_HPP 4 | 5 | #include "coroutines/coroutine.hpp" 6 | #include "coroutines/mutex.hpp" 7 | 8 | #include 9 | #include 10 | 11 | namespace coroutines { 12 | 13 | class scheduler; 14 | 15 | // monitor is a syncronisation tool. 16 | // it allows one corotunie to wait for singla from another. 17 | class monitor 18 | { 19 | public: 20 | 21 | typedef std::function epilogue_type; 22 | 23 | monitor(scheduler& sched); 24 | monitor(const monitor&) = delete; 25 | ~monitor(); 26 | 27 | // called from corotunie context. Will cause the corountine to yield 28 | // Epilogue will be called after the coroutine is preemted 29 | void wait(const std::string& checkpoint_name, epilogue_type epilogue = epilogue_type()); 30 | 31 | // wakes all waiting corotunies 32 | void wake_all(); 33 | 34 | // wakes one of the waiting corountines 35 | void wake_one(); 36 | 37 | 38 | private: 39 | 40 | std::vector _waiting; 41 | mutex _waiting_mutex; 42 | 43 | scheduler& _scheduler; 44 | }; 45 | 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /coroutines/mutex.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #ifndef COROUTINES_MUTEX_HPP 4 | #define COROUTINES_MUTEX_HPP 5 | 6 | #include "profiling/profiling.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace coroutines { 13 | 14 | 15 | class spinlock 16 | { 17 | public: 18 | 19 | spinlock() 20 | : _flag(ATOMIC_FLAG_INIT) 21 | { } 22 | 23 | spinlock(const char* name) 24 | : _flag(ATOMIC_FLAG_INIT) 25 | { 26 | #ifdef COROUTINES_SPINLOCKS_PROFILING 27 | CORO_PROF("spinlock", this, "created", name); 28 | #else 29 | (void)name; 30 | #endif 31 | } 32 | 33 | void lock() 34 | { 35 | #ifdef COROUTINES_SPINLOCKS_PROFILING 36 | // only report contested lock events 37 | if (try_lock()) 38 | return; 39 | CORO_PROF("spinlock", this, "spinning begin"); 40 | #endif 41 | while(_flag.test_and_set(std::memory_order_acquire)) 42 | ; // spin 43 | #ifdef COROUTINES_SPINLOCKS_PROFILING 44 | CORO_PROF("spinlock", this, "spinning end"); 45 | #endif 46 | } 47 | 48 | bool try_lock() 49 | { 50 | #ifdef COROUTINES_SPINLOCKS_PROFILING 51 | if (!_flag.test_and_set(std::memory_order_acquire)) 52 | { 53 | CORO_PROF("spinlock", this, "locked"); 54 | return true; 55 | } 56 | else 57 | return false; 58 | #else 59 | return !_flag.test_and_set(std::memory_order_acquire); 60 | #endif 61 | } 62 | 63 | void unlock() 64 | { 65 | #ifdef COROUTINES_SPINLOCKS_PROFILING 66 | CORO_PROF("spinlock", this, "unlocked"); 67 | #endif 68 | _flag.clear(std::memory_order_release); 69 | } 70 | 71 | 72 | private: 73 | 74 | std::atomic_flag _flag; 75 | }; 76 | 77 | 78 | //typedef std::mutex mutex; 79 | typedef spinlock mutex; 80 | 81 | // based on folly's RWSpinLock 82 | class rw_spinlock 83 | { 84 | enum : std::int32_t { READER = 4, UPGRADED = 2, WRITER = 1 }; 85 | 86 | public: 87 | rw_spinlock() : _bits(0) {} 88 | 89 | rw_spinlock(const char* name) 90 | : _bits(0) 91 | { 92 | #ifdef COROUTINES_SPINLOCKS_PROFILING 93 | CORO_PROF("rw_spinlock", this, "created", name); 94 | #else 95 | (void)name; 96 | #endif 97 | } 98 | 99 | void lock() 100 | { 101 | #ifdef COROUTINES_SPINLOCKS_PROFILING 102 | // only report contested lock events 103 | if (try_lock()) 104 | return; 105 | CORO_PROF("rw_spinlock", this, "spinning begin"); 106 | #else 107 | while (!try_lock()) 108 | ; 109 | #endif 110 | 111 | #ifdef COROUTINES_SPINLOCKS_PROFILING 112 | CORO_PROF("rw_spinlock", this, "spinning end"); 113 | #endif 114 | } 115 | 116 | // Writer is responsible for clearing up both the UPGRADED and WRITER bits. 117 | void unlock() 118 | { 119 | #ifdef COROUTINES_SPINLOCKS_PROFILING 120 | CORO_PROF("rw_spinlock", this, "unlocked"); 121 | #endif 122 | static_assert(READER > WRITER + UPGRADED, "wrong bits!"); 123 | _bits.fetch_and(~(WRITER | UPGRADED), std::memory_order_release); 124 | } 125 | 126 | // SharedLockable Concept 127 | void lock_shared() 128 | { 129 | #ifdef COROUTINES_SPINLOCKS_PROFILING 130 | // only report contested lock events 131 | if (try_lock_shared()) 132 | return; 133 | CORO_PROF("rw_spinlock", this, "spinning begin"); 134 | #else 135 | while (!try_lock_shared()) 136 | ; 137 | #endif 138 | 139 | #ifdef COROUTINES_SPINLOCKS_PROFILING 140 | CORO_PROF("rw_spinlock", this, "spinning end"); 141 | #endif 142 | } 143 | 144 | void unlock_shared() 145 | { 146 | #ifdef COROUTINES_SPINLOCKS_PROFILING 147 | CORO_PROF("rw_spinlock", this, "unlocked shared"); 148 | #endif 149 | _bits.fetch_add(-READER, std::memory_order_release); 150 | } 151 | 152 | // Downgrade the lock from writer status to reader status. 153 | void unlock_and_lock_shared() 154 | { 155 | _bits.fetch_add(READER, std::memory_order_acquire); 156 | unlock(); 157 | } 158 | 159 | // UpgradeLockable Concept 160 | void lock_upgrade() 161 | { 162 | while (!try_lock_upgrade()) 163 | ; 164 | } 165 | 166 | void unlock_upgrade() 167 | { 168 | _bits.fetch_add(-UPGRADED, std::memory_order_acq_rel); 169 | } 170 | 171 | // unlock upgrade and try to acquire write lock 172 | void unlock_upgrade_and_lock() 173 | { 174 | while (!try_unlock_upgrade_and_lock()) 175 | ; 176 | } 177 | 178 | // unlock upgrade and read lock atomically 179 | void unlock_upgrade_and_lock_shared() 180 | { 181 | _bits.fetch_add(READER - UPGRADED, std::memory_order_acq_rel); 182 | } 183 | 184 | // write unlock and upgrade lock atomically 185 | void unlock_and_lock_upgrade() 186 | { 187 | // need to do it in two steps here -- as the UPGRADED bit might be OR-ed at 188 | // the same time when other threads are trying do try_lock_upgrade(). 189 | _bits.fetch_or(UPGRADED, std::memory_order_acquire); 190 | _bits.fetch_add(-WRITER, std::memory_order_release); 191 | } 192 | 193 | 194 | // Attempt to acquire writer permission. Return false if we didn't get it. 195 | bool try_lock() 196 | { 197 | std::int32_t expect = 0; 198 | #ifdef COROUTINES_SPINLOCKS_PROFILING 199 | if (_bits.compare_exchange_strong(expect, WRITER, std::memory_order_acq_rel)) 200 | { 201 | CORO_PROF("rw_spinlock", this, "locked"); 202 | return true; 203 | } 204 | else 205 | { 206 | return false; 207 | } 208 | #else 209 | return _bits.compare_exchange_strong(expect, WRITER, std::memory_order_acq_rel); 210 | #endif 211 | } 212 | 213 | // Try to get reader permission on the lock. This can fail if we 214 | // find out someone is a writer or upgrader. 215 | // Setting the UPGRADED bit would allow a writer-to-be to indicate 216 | // its intention to write and block any new readers while waiting 217 | // for existing readers to finish and release their read locks. This 218 | // helps avoid starving writers (promoted from upgraders). 219 | bool try_lock_shared() 220 | { 221 | // fetch_add is considerably (100%) faster than compare_exchange, 222 | // so here we are optimizing for the common (lock success) case. 223 | std::int32_t value = _bits.fetch_add(READER, std::memory_order_acquire); 224 | if (value & (WRITER|UPGRADED)) 225 | { 226 | _bits.fetch_add(-READER, std::memory_order_release); 227 | return false; 228 | 229 | } 230 | #ifdef COROUTINES_SPINLOCKS_PROFILING 231 | CORO_PROF("rw_spinlock", this, "locked shared"); 232 | #endif 233 | return true; 234 | } 235 | 236 | // try to unlock upgrade and write lock atomically 237 | bool try_unlock_upgrade_and_lock() 238 | { 239 | std::int32_t expect = UPGRADED; 240 | return _bits.compare_exchange_strong(expect, WRITER, std::memory_order_acq_rel); 241 | } 242 | 243 | // try to acquire an upgradable lock. 244 | bool try_lock_upgrade() 245 | { 246 | std::int32_t value = _bits.fetch_or(UPGRADED, std::memory_order_acquire); 247 | 248 | // Note: when failed, we cannot flip the UPGRADED bit back, 249 | // as in this case there is either another upgrade lock or a write lock. 250 | // If it's a write lock, the bit will get cleared up when that lock's done 251 | // with unlock(). 252 | return ((value & (UPGRADED | WRITER)) == 0); 253 | } 254 | 255 | private: 256 | 257 | std::atomic _bits; 258 | }; 259 | 260 | template 261 | class reader_guard 262 | { 263 | public: 264 | reader_guard(MutexType& mutex) 265 | : _mutex(mutex) 266 | { 267 | _mutex.lock_shared(); 268 | } 269 | 270 | ~reader_guard() 271 | { 272 | _mutex.unlock_shared(); 273 | } 274 | private: 275 | 276 | MutexType& _mutex; 277 | }; 278 | 279 | typedef rw_spinlock shared_mutex; 280 | 281 | } 282 | 283 | #endif 284 | -------------------------------------------------------------------------------- /coroutines/processor.cpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #include "coroutines/processor.hpp" 3 | #include "coroutines/scheduler.hpp" 4 | #include "profiling/profiling.hpp" 5 | 6 | //#define CORO_LOGGING 7 | #include "coroutines/logging.hpp" 8 | 9 | #include 10 | #include 11 | 12 | namespace coroutines { 13 | 14 | static thread_local processor* __current_processor= nullptr; 15 | 16 | processor::processor(scheduler& sched) 17 | : _scheduler(sched) 18 | , _queue_mutex("processor queue mutex") 19 | , _thread([this]() { routine(); }) 20 | { 21 | } 22 | 23 | processor::~processor() 24 | { 25 | CORO_LOG("PROC=", this, " destroyed"); 26 | 27 | _thread.join(); 28 | } 29 | 30 | template 31 | bool processor::enqueue(InputIterator first, InputIterator last) 32 | { 33 | { 34 | std::lock_guard lock(_queue_mutex); 35 | 36 | if (_stopped || _blocked) 37 | return false; 38 | 39 | _queue.insert(_queue.end(), first, last); 40 | } 41 | 42 | CORO_LOG("PROC=", this, " enqueued ", std::distance(first, last), " coros, waking up"); 43 | 44 | _cv.notify_one(); 45 | return true; 46 | } 47 | 48 | // force instantiation for std::vector 49 | template bool processor::enqueue::iterator>(std::vector::iterator, std::vector::iterator); 50 | // and for raw pointers 51 | template bool processor::enqueue(coroutine_weak_ptr*, coroutine_weak_ptr*); 52 | 53 | bool processor::enqueue(coroutine_weak_ptr coro) 54 | { 55 | return enqueue(&coro, &coro + 1); 56 | } 57 | 58 | bool processor::stop() 59 | { 60 | std::lock_guard lock(_queue_mutex); 61 | _stopped = true; 62 | _cv.notify_one(); 63 | 64 | return _queue.empty() || _executing; 65 | } 66 | 67 | bool processor::stop_if_idle() 68 | { 69 | std::lock_guard lock(_queue_mutex); 70 | if (_queue.empty() && !_executing) 71 | { 72 | _stopped = true; 73 | _cv.notify_one(); 74 | return true; 75 | } 76 | return false; 77 | } 78 | 79 | 80 | void processor::steal(std::vector& out) 81 | { 82 | { 83 | std::lock_guard lock(_queue_mutex); 84 | 85 | unsigned to_steal = _queue.size() / 2; // rounds down 86 | if (to_steal > 0) 87 | { 88 | unsigned to_leave = _queue.size() - to_steal; 89 | auto it = _queue.begin(); 90 | std::advance(it, to_leave); 91 | 92 | out.reserve(out.size() + to_steal); 93 | std::for_each(it, _queue.end(), [&out](coroutine_weak_ptr& coro) 94 | { 95 | out.push_back(std::move(coro)); 96 | }); 97 | _queue.resize(to_leave); 98 | } 99 | } 100 | } 101 | 102 | unsigned processor::queue_size() 103 | { 104 | std::lock_guard lock(_queue_mutex); 105 | return _queue.size() + _executing; 106 | } 107 | 108 | void processor::block() 109 | { 110 | CORO_LOG("PROC=", this, " block"); 111 | CORO_PROF("processor", this, "block"); 112 | 113 | std::vector queue; 114 | { 115 | std::lock_guard lock(_queue_mutex); 116 | 117 | queue.reserve(_queue.size()); 118 | std::copy(_queue.begin(), _queue.end(), std::back_inserter(queue)); 119 | _queue.clear(); 120 | _blocked = true; 121 | } 122 | 123 | _scheduler.processor_blocked(this, queue); 124 | } 125 | 126 | void processor::unblock() 127 | { 128 | CORO_LOG("PROC=", this, " unblock"); 129 | CORO_PROF("processor", this, "unblock"); 130 | 131 | { 132 | std::lock_guard lock(_queue_mutex); 133 | _blocked = false; 134 | } 135 | _scheduler.processor_unblocked(this); 136 | } 137 | 138 | processor* processor::current_processor() 139 | { 140 | return __current_processor; 141 | } 142 | 143 | void processor::routine() 144 | { 145 | CORO_PROF("PROC=", this, "routine started"); 146 | CORO_LOG("PROC=", this, " routine started"); 147 | 148 | __current_processor = this; 149 | struct scope_exit { ~scope_exit() { __current_processor = nullptr; } } exit; 150 | 151 | for(;;) 152 | { 153 | // am I short on jobs? 154 | bool starved = false; 155 | { 156 | std::lock_guard lock(_queue_mutex); 157 | _executing = false; 158 | starved = _queue.empty(); 159 | } 160 | // call scheduler outside of critical section 161 | if(starved) 162 | _scheduler.processor_starved(this); // ask for more 163 | 164 | // take coro from queue 165 | coroutine_weak_ptr coro = nullptr; 166 | { 167 | std::lock_guard lock(_queue_mutex); 168 | 169 | _cv.wait( 170 | _queue_mutex, 171 | [this](){ return _stopped || !_queue.empty(); }); 172 | 173 | if (_queue.empty()) 174 | { 175 | assert(_stopped); 176 | CORO_LOG("PROC=", this, " : Stopped, and queue empty. Stopping"); 177 | return; 178 | } 179 | else 180 | { 181 | coro = _queue.front(); 182 | _queue.pop_front(); 183 | } 184 | _executing = true; 185 | } 186 | 187 | // execute 188 | CORO_LOG("PROC=", this, " : will run coro '", coro->name(), "'"); 189 | coro->run(); 190 | } 191 | 192 | CORO_PROF("processor", this, "routine finished"); 193 | } 194 | 195 | } // namespace coroutines 196 | -------------------------------------------------------------------------------- /coroutines/processor.hpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #ifndef COROUTINES_PROCESSOR_HPP 3 | #define COROUTINES_PROCESSOR_HPP 4 | 5 | #include "coroutines/coroutine.hpp" 6 | #include "coroutines/mutex.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace coroutines { 14 | 15 | class scheduler; 16 | 17 | class processor 18 | { 19 | public: 20 | processor(scheduler& sched); 21 | processor(const processor&) = delete; 22 | ~processor(); 23 | 24 | // adds work to the queue. Returns false if not successful, because the processor is shutting down or blocked 25 | bool enqueue(coroutine_weak_ptr coro); 26 | 27 | template 28 | bool enqueue(InputIterator first, InputIterator last); 29 | 30 | // variations that assume procesors availability 31 | void enqueue_or_die(coroutine_weak_ptr coro); 32 | 33 | template 34 | void enqueue_or_die(InputIterator first, InputIterator last); 35 | 36 | // shuts the processor down, returns true if no tasks in the queue and processor can be destroyed 37 | // if false returned, the processor will stop after exhaustingf the queue 38 | bool stop(); 39 | 40 | // will stop the processor only if it has empty queue and is not doigng anything 41 | // if false is returned, the processor will continue 42 | bool stop_if_idle(); 43 | 44 | // steals half of work 45 | void steal(std::vector& out); 46 | 47 | // number of tasks in the queue (including currently executed) 48 | unsigned queue_size(); 49 | 50 | // block/unblock 51 | void block(); 52 | void unblock(); 53 | 54 | static processor* current_processor(); 55 | 56 | private: 57 | 58 | void routine(); 59 | void wakeup(); 60 | 61 | scheduler& _scheduler; 62 | 63 | std::deque _queue; 64 | mutex _queue_mutex; 65 | 66 | bool _stopped = false; 67 | bool _blocked = false; 68 | std::condition_variable_any _cv; 69 | bool _executing = false; 70 | 71 | std::thread _thread; 72 | }; 73 | 74 | typedef std::unique_ptr processor_ptr; 75 | typedef processor* processor_weak_ptr; 76 | 77 | inline 78 | void processor::enqueue_or_die(coroutine_weak_ptr coro) 79 | { 80 | if (!enqueue(coro)) 81 | std::terminate(); 82 | } 83 | 84 | template 85 | void processor::enqueue_or_die(InputIterator first, InputIterator last) 86 | { 87 | if (!enqueue(first, last)) 88 | std::terminate(); 89 | } 90 | 91 | 92 | } // namespace coroutines 93 | 94 | #endif // COROUTINES_PROCESSOR_HPP 95 | -------------------------------------------------------------------------------- /coroutines/processor_container.cpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #include "coroutines/processor_container.hpp" 3 | 4 | #include 5 | 6 | namespace coroutines { 7 | 8 | processor_container::processor_container() 9 | { 10 | } 11 | 12 | unsigned processor_container::least_busy_index(unsigned min, unsigned max) const 13 | { 14 | unsigned min_index = min; 15 | unsigned min_tasks = std::numeric_limits::max(); 16 | for(unsigned i = min; i < max; i++) 17 | { 18 | unsigned qs = _container[i]->queue_size(); 19 | if (qs < min_tasks) 20 | { 21 | min_index = i; 22 | min_tasks = qs; 23 | } 24 | } 25 | 26 | return min_index; 27 | } 28 | 29 | unsigned processor_container::most_busy_index(unsigned min, unsigned max) const 30 | { 31 | unsigned max_index = min; 32 | unsigned max_tasks = 0; 33 | for(unsigned i = min; i < max; i++) 34 | { 35 | unsigned qs = _container[i]->queue_size(); 36 | if (qs > max_tasks) 37 | { 38 | max_index = i; 39 | max_tasks = qs; 40 | } 41 | } 42 | 43 | return max_index; 44 | } 45 | 46 | void processor_container::stop_all() 47 | { 48 | for(auto& p : _container) 49 | { 50 | p->stop(); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /coroutines/processor_container.hpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #ifndef COROUTINES_PROCESSOR_CONTAINER_HPP 3 | #define COROUTINES_PROCESSOR_CONTAINER_HPP 4 | 5 | #include "coroutines/processor.hpp" 6 | #include "coroutines/algorithm.hpp" 7 | 8 | #include 9 | 10 | namespace coroutines { 11 | 12 | // special purpose container for storing processors. 13 | // the container is divided into two areas - acticve and inactive. 14 | // active is the first max_active processors 15 | class processor_container 16 | { 17 | public: 18 | processor_container(); 19 | 20 | unsigned size() const { return _container.size(); } 21 | 22 | unsigned index_of(processor* pc) const 23 | { 24 | auto it = find_ptr(_container, pc); 25 | return std::distance(_container.begin(), it); 26 | } 27 | 28 | void emplace_back(scheduler& sched) { _container.push_back(processor_ptr(new processor(sched))); } 29 | 30 | // inserts at index 31 | void insert(unsigned index, scheduler& sched) 32 | { 33 | _container.insert(_container.begin() + index, processor_ptr(new processor(sched))); 34 | } 35 | 36 | // will wait for thread to join, be sure to stop the processor 37 | void pop_back() { _container.pop_back(); } 38 | 39 | processor& operator[](unsigned i) { return *_container[i]; } 40 | processor& back() { return *_container.back(); } 41 | 42 | // returns processor with least and most coros in queue 43 | 44 | unsigned least_busy_index(unsigned min, unsigned max) const; 45 | unsigned most_busy_index(unsigned min, unsigned max) const; 46 | 47 | void swap(unsigned a, unsigned b) { std::swap(_container[a], _container[b]); } 48 | 49 | void stop_all(); 50 | 51 | private: 52 | 53 | std::vector _container; 54 | }; 55 | 56 | } 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /coroutines/scheduler.cpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #include "coroutines/scheduler.hpp" 3 | #include "coroutines/algorithm.hpp" 4 | 5 | //#define CORO_LOGGING 6 | #include "coroutines/logging.hpp" 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace coroutines { 15 | 16 | scheduler::scheduler(unsigned active_processors) 17 | : _active_processors(active_processors) 18 | , _processors() 19 | , _processors_mutex("sched processors mutex") 20 | , _starved_processors_mutex("sched starved processors mutex") 21 | , _coroutines_mutex("sched coroutines mutex") 22 | , _global_queue_mutex("sched global q mutex") 23 | , _random_generator(std::random_device()()) 24 | { 25 | assert(active_processors > 0); 26 | 27 | // setup 28 | { 29 | std::lock_guard lock(_processors_mutex); 30 | for(unsigned i = 0; i < active_processors; i++) 31 | { 32 | _processors.emplace_back(*this); 33 | } 34 | } 35 | } 36 | 37 | scheduler::~scheduler() 38 | { 39 | wait(); 40 | { 41 | std::lock_guard lock(_processors_mutex); 42 | _processors.stop_all(); 43 | } 44 | CORO_LOG("SCHED: destroyed"); 45 | } 46 | 47 | void scheduler::debug_dump() 48 | { 49 | std::lock(_coroutines_mutex, _processors_mutex); 50 | 51 | std::cerr << "=========== scheduler debug dump ============" << std::endl; 52 | std::cerr << " active coroutines now: " << _coroutines.size() << std::endl; 53 | std::cerr << " max active coroutines seen: " << _max_active_coroutines << std::endl; 54 | std::cerr << " no of processors: " << _processors.size(); 55 | std::cerr << " no of blocked processors: " << _blocked_processors; 56 | 57 | std::cerr << std::endl; 58 | std::cerr << " Active coroutines:" << std::endl; 59 | for(auto& coro : _coroutines) 60 | { 61 | std::cerr << " * " << coro->name() << " : " << coro->last_checkpoint() << std::endl; 62 | } 63 | std::cerr << "=============================================" << std::endl; 64 | std::terminate(); 65 | } 66 | 67 | void scheduler::wait() 68 | { 69 | CORO_LOG("SCHED: waiting..."); 70 | 71 | std::unique_lock lock(_coroutines_mutex); 72 | _coro_cv.wait(lock, [this]() { return _coroutines.empty(); }); 73 | 74 | CORO_LOG("SCHED: wait over"); 75 | } 76 | 77 | void scheduler::coroutine_finished(coroutine* coro) 78 | { 79 | CORO_LOG("SCHED: coro=", coro, " finished"); 80 | 81 | std::lock_guard lock(_coroutines_mutex); 82 | auto it = find_ptr(_coroutines, coro); 83 | assert(it != _coroutines.end()); 84 | _coroutines.erase(it); 85 | 86 | if (_coroutines.empty()) 87 | { 88 | _coro_cv.notify_all(); 89 | } 90 | } 91 | 92 | void scheduler::processor_starved(processor* pc) 93 | { 94 | CORO_LOG("SCHED: processor ", pc, " starved"); 95 | 96 | // step 1 - try to feed him global q 97 | { 98 | std::lock_guard lock(_global_queue_mutex); 99 | 100 | if (!_global_queue.empty()) 101 | { 102 | CORO_LOG("SCHED: scheduleing ", _global_queue.size(), " coros from global queue"); 103 | pc->enqueue_or_die(_global_queue.begin(), _global_queue.end()); 104 | _global_queue.clear(); 105 | return; 106 | } 107 | } 108 | 109 | // step 2 - try to steal something 110 | { 111 | reader_guard lock(_processors_mutex); 112 | 113 | unsigned index = _processors.index_of(pc); 114 | if (index < _active_processors + _blocked_processors) 115 | { 116 | // try to steal 117 | unsigned most_busy = _processors.most_busy_index(0, _active_processors); 118 | std::vector stolen; 119 | _processors[most_busy].steal(stolen); 120 | // if stealing successful - reactivate the processor 121 | if (!stolen.empty()) 122 | { 123 | CORO_LOG("SCHED: stolen ", stolen.size(), " coros for proc=", pc, " from proc=", &_processors[most_busy]); 124 | pc->enqueue_or_die(stolen.begin(), stolen.end()); 125 | return; 126 | } 127 | } 128 | // else: I don't care, you are in exile 129 | else 130 | { 131 | return; 132 | } 133 | } 134 | 135 | // record as starved 136 | { 137 | std::lock_guard lock(_starved_processors_mutex); 138 | 139 | _starved_processors.push_back(pc); 140 | } 141 | } 142 | 143 | void scheduler::processor_blocked(processor_weak_ptr pc, std::vector& queue) 144 | { 145 | // move to blocked, schedule coroutines 146 | { 147 | // TODO use reader here 148 | boost::upgrade_lock lock(_processors_mutex); 149 | 150 | CORO_LOG("SCHED: proc=", pc, " blocked"); 151 | 152 | _blocked_processors++; 153 | 154 | if (_processors.size() < _active_processors + _blocked_processors) 155 | { 156 | boost::upgrade_to_unique_lock upgrade_lock(lock); 157 | _processors.emplace_back(*this); 158 | } 159 | } 160 | // the procesor will now continue in blocked state 161 | 162 | 163 | // schedule coroutines 164 | schedule(queue.begin(), queue.end()); 165 | } 166 | 167 | void scheduler::processor_unblocked(processor_weak_ptr pc) 168 | { 169 | // TODO use reader here, upgrade if needed 170 | boost::upgrade_lock lock(_processors_mutex); 171 | 172 | CORO_LOG("SCHED: proc=", pc, " unblocked"); 173 | 174 | assert(_blocked_processors > 0); 175 | _blocked_processors--; 176 | 177 | if (_processors.size() > _active_processors*3 + _blocked_processors) // if above high-water mark 178 | { 179 | std::lock_guard starved_lock(_starved_processors_mutex); 180 | boost::upgrade_to_unique_lock upgrade_lock(lock); 181 | 182 | while(_processors.size() > _active_processors*2 + _blocked_processors) // recduce to acceptable value 183 | { 184 | CORO_LOG("SCHED: processors: ", _processors.size(), ", blocked: ", _blocked_processors, ", cleaning up"); 185 | if (_processors.back().stop_if_idle()) 186 | { 187 | _starved_processors.erase( 188 | std::remove(_starved_processors.begin(), _starved_processors.end(), &_processors.back()), 189 | _starved_processors.end()); 190 | 191 | _processors.pop_back(); 192 | } 193 | else 194 | { 195 | break; // some task is running, we'll come for it the next time 196 | } 197 | } 198 | } 199 | } 200 | 201 | // returns uniform random number between 0 and _max_allowed_running_coros 202 | unsigned scheduler::random_index() 203 | { 204 | std::uniform_int_distribution dist(0, _active_processors+_blocked_processors-1); 205 | return dist(_random_generator); 206 | } 207 | 208 | 209 | void scheduler::schedule(coroutine_weak_ptr coro) 210 | { 211 | schedule(&coro, &coro + 1); 212 | } 213 | 214 | template 215 | void scheduler::schedule(InputIterator first, InputIterator last) 216 | { 217 | if (first == last) 218 | return; // that was easy :) 219 | 220 | CORO_LOG("SCHED: scheduling ", std::distance(first, last), " corountines. First: '", (*first)->name(), "'"); 221 | 222 | // step 1 - try adding to starved processor 223 | { 224 | std::lock_guard lock(_starved_processors_mutex); 225 | 226 | if (!_starved_processors.empty()) 227 | { 228 | CORO_LOG("SCHED: scheduling corountine, will add to starved processor"); 229 | processor_weak_ptr starved = _starved_processors.back(); 230 | _starved_processors.pop_back(); 231 | starved->enqueue_or_die(first, last); 232 | return; 233 | } 234 | } 235 | 236 | // step 2 - add to self 237 | if (processor::current_processor() && processor::current_processor()->enqueue(first, last)) 238 | { 239 | CORO_LOG("SCHED: scheduling corountine, added to self"); 240 | return; 241 | } 242 | 243 | // total failure, add to global queue? 244 | { 245 | std::lock_guard lock(_global_queue_mutex); 246 | 247 | CORO_LOG("SCHED: scheduling corountines, added to global queue"); 248 | _global_queue.insert(_global_queue.end(), first, last); 249 | } 250 | } 251 | 252 | template 253 | void scheduler::schedule::iterator>(std::vector::iterator, std::vector::iterator); 254 | 255 | void scheduler::go(coroutine_ptr&& coro) 256 | { 257 | CORO_LOG("SCHED: go '", coro->name(), "'"); 258 | coroutine_weak_ptr coro_weak = coro.get(); 259 | { 260 | std::lock_guard lock(_coroutines_mutex); 261 | 262 | _coroutines.emplace_back(std::move(coro)); 263 | _max_active_coroutines = std::max(_coroutines.size(), _max_active_coroutines); 264 | } 265 | 266 | schedule(coro_weak); 267 | } 268 | 269 | } // namespace coroutines 270 | -------------------------------------------------------------------------------- /coroutines/scheduler.hpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #ifndef COROUTINES_COROUTINE_SCHEDULER_HPP 3 | #define COROUTINES_COROUTINE_SCHEDULER_HPP 4 | 5 | #include "coroutines/channel.hpp" 6 | #include "coroutines/coroutine.hpp" 7 | #include "coroutines/processor.hpp" 8 | #include "coroutines/locking_channel.hpp" 9 | #include "coroutines/condition_variable.hpp" 10 | #include "coroutines/processor_container.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | namespace coroutines { 17 | 18 | class scheduler 19 | { 20 | public: 21 | // creates and sets no of max coroutines runnig in parallel 22 | scheduler(unsigned active_processors = std::thread::hardware_concurrency()); 23 | 24 | scheduler(const scheduler&) = delete; 25 | 26 | ~scheduler(); 27 | 28 | // launches corountine 29 | template 30 | void go(Callable&& fn, Args&&... args); 31 | 32 | // debug version, with coroutine's name 33 | template 34 | void go(std::string name, Callable&& fn, Args&&... args); 35 | 36 | // debug version, with coroutine's name 37 | template 38 | void go(const char* name, Callable&& fn, Args&&... args); 39 | 40 | // create channel 41 | template 42 | channel_pair make_channel(std::size_t capacity, const std::string& name) 43 | { 44 | return channel_pair::make(*this, capacity, name); 45 | } 46 | 47 | // wrties current status to stderr 48 | void debug_dump(); 49 | 50 | // wait for all coroutines to complete 51 | void wait(); 52 | 53 | // coroutine interface 54 | void coroutine_finished(coroutine* coro); 55 | 56 | /////// 57 | // processor's interface 58 | 59 | void processor_starved(processor* pr); 60 | void processor_blocked(processor_weak_ptr pr, std::vector& queue); 61 | void processor_unblocked(processor_weak_ptr pr); 62 | 63 | void schedule(coroutine_weak_ptr coro); 64 | 65 | template 66 | void schedule(InputIterator first, InputIterator last); 67 | 68 | 69 | private: 70 | 71 | void go(coroutine_ptr&& coro); 72 | 73 | unsigned random_index(); 74 | 75 | const unsigned _active_processors; 76 | unsigned _blocked_processors = 0; 77 | 78 | processor_container _processors; 79 | shared_mutex _processors_mutex; 80 | 81 | std::vector _starved_processors; 82 | mutex _starved_processors_mutex; 83 | 84 | std::vector _coroutines; 85 | mutex _coroutines_mutex; 86 | 87 | std::condition_variable_any _coro_cv; 88 | std::size_t _max_active_coroutines = 0; // stat counter 89 | 90 | std::vector _global_queue; 91 | mutex _global_queue_mutex; 92 | 93 | std::minstd_rand _random_generator; 94 | }; 95 | 96 | 97 | template 98 | void scheduler::go(Callable&& fn, Args&&... args) 99 | { 100 | this->go(std::string(), std::forward(fn), std::forward(args)...); 101 | } 102 | 103 | template 104 | void scheduler::go(std::string name, Callable&& fn, Args&&... args) 105 | { 106 | this->go(make_coroutine(*this, std::move(name), std::bind(std::forward(fn), std::forward(args)...))); 107 | } 108 | 109 | template 110 | void scheduler::go(const char* name, Callable&& fn, Args&&... args) 111 | { 112 | 113 | this->go(make_coroutine(*this, std::string(name), std::bind(std::forward(fn), std::forward(args)...))); 114 | } 115 | 116 | } // namespace coroutines 117 | 118 | #endif // COROUTINES_COROUTINE_SCHEDULER_HPP 119 | -------------------------------------------------------------------------------- /coroutines/spsc_queue.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #ifndef COROUTINES_QUEUE_H 4 | #define COROUTINES_QUEUE_H 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace coroutines { 13 | 14 | // lock-free, fixed size single-producer single-consumer queue. 15 | // Based on https://github.com/facebook/folly/blob/master/folly/ProducerConsumerQueue.h 16 | template 17 | class spsc_queue 18 | { 19 | public: 20 | 21 | // capacity must be at least 2, 1 item is always used as divider and not really usable 22 | spsc_queue(std::size_t capacity); 23 | 24 | spsc_queue(const spsc_queue&) = delete; 25 | 26 | ~spsc_queue(); 27 | 28 | // returns true if itemns was moved into queue, false if queue was full 29 | bool put(T& v); 30 | 31 | // returns false is queue was empty, true if the item was filled with item from queue 32 | bool get(T& v); 33 | 34 | bool empty() const; 35 | bool full() const; 36 | std::size_t size() const; // approx. size 37 | 38 | std::size_t capacity() const { return _capacity; } 39 | 40 | private: 41 | 42 | 43 | const std::size_t _capacity; 44 | T* const _data; 45 | std::atomic _rd; 46 | std::atomic _wr; 47 | }; 48 | 49 | template 50 | spsc_queue::spsc_queue(std::size_t capacity) 51 | : _capacity(capacity) 52 | , _data(static_cast(std::malloc(sizeof(T) * capacity))) 53 | , _rd(0) 54 | , _wr(0) 55 | { 56 | assert(capacity >= 2); 57 | if (!_data) 58 | { 59 | throw std::bad_alloc(); 60 | } 61 | } 62 | 63 | template 64 | spsc_queue::~spsc_queue() 65 | { 66 | // destroy anything that could still be in there 67 | int rd = _rd; 68 | int wr = _wr; 69 | while(rd != wr) 70 | { 71 | _data[rd].~T(); 72 | rd = (rd+1) % _capacity; 73 | } 74 | } 75 | 76 | template 77 | bool spsc_queue::empty() const 78 | { 79 | return _rd.load(std::memory_order_consume) = _wr.load(std::memory_order_consume); 80 | } 81 | 82 | template 83 | bool spsc_queue::full() const 84 | { 85 | auto wr_next = _wr.load(std::memory_order_consume) + 1; 86 | if (wr_next == _capacity) 87 | wr_next = 0; 88 | return wr_next == _rd.load(std::memory_order_consume); 89 | } 90 | 91 | template 92 | bool spsc_queue::put(T& v) 93 | { 94 | int wr_now = _wr.load(std::memory_order_relaxed); 95 | 96 | int wr_next = wr_now + 1; 97 | if (wr_next == _capacity) 98 | wr_next = 0; 99 | 100 | if (wr_next != _rd.load(std::memory_order_acquire)) 101 | { 102 | new(&_data[wr_now]) T(std::move(v)); 103 | _wr.store(wr_next, std::memory_order_release); 104 | 105 | return true; 106 | } 107 | return false; 108 | } 109 | 110 | template 111 | bool spsc_queue::get(T& b) 112 | { 113 | int rd_now = _rd.load(std::memory_order_relaxed); 114 | 115 | if (rd_now != _wr.load(std::memory_order_acquire)) 116 | { 117 | int rd_next = rd_now + 1; 118 | if (rd_next == _capacity) 119 | rd_next = 0; 120 | 121 | b = std::move(_data[rd_now]); 122 | _data[rd_now].~T(); 123 | _rd.store(rd_next, std::memory_order_release); 124 | 125 | return true; 126 | } 127 | 128 | return false; 129 | } 130 | 131 | template 132 | std::size_t spsc_queue::size() const 133 | { 134 | int s = _wr.load(std::memory_order_consume) - _rd.load(std::memory_order_consume); 135 | if (s < 0 ) 136 | s+= _capacity; 137 | return s; 138 | } 139 | 140 | } 141 | 142 | #endif 143 | -------------------------------------------------------------------------------- /coroutines_io/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package( Boost 1.54.0 COMPONENTS system) 2 | include_directories(${Boost_INCLUDE_DIRS}) 3 | 4 | add_library(coroutines_io STATIC 5 | buffer.hpp 6 | globals.cpp globals.hpp 7 | io_scheduler.cpp io_scheduler.hpp 8 | tcp_socket.cpp tcp_socket.hpp 9 | tcp_acceptor.cpp tcp_acceptor.hpp 10 | base_pollable.cpp base_pollable.hpp 11 | file.cpp file.hpp 12 | tcp_resolver.cpp tcp_resolver.hpp 13 | socket_streambuf.hpp 14 | 15 | detail/poller.cpp detail/poller.hpp 16 | ) 17 | 18 | target_link_libraries(coroutines_io 19 | 20 | coroutines 21 | ${Boost_LIBRARIES} 22 | ) 23 | -------------------------------------------------------------------------------- /coroutines_io/base_pollable.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #include "coroutines_io/base_pollable.hpp" 4 | 5 | #include "coroutines_io/io_scheduler.hpp" 6 | 7 | #include 8 | 9 | namespace coroutines { 10 | 11 | base_pollable::base_pollable(io_scheduler& srv) 12 | : _service(srv) 13 | { 14 | } 15 | 16 | base_pollable::base_pollable(base_pollable&& o) 17 | : _service(o._service) 18 | { 19 | std::swap(_fd, o._fd); 20 | std::swap(_reader, o._reader); 21 | std::swap(_writer, o._writer); 22 | } 23 | 24 | base_pollable::~base_pollable() 25 | { 26 | close(); 27 | } 28 | 29 | void base_pollable::close() 30 | { 31 | if (_fd != -1) 32 | { 33 | ::close(_fd); 34 | _fd = -1; 35 | } 36 | } 37 | 38 | void base_pollable::set_fd(int fd) 39 | { 40 | assert(_fd == -1); 41 | 42 | _fd = fd; 43 | 44 | auto pair = _service.get_scheduler().make_channel(1, "pollable notification channel"); 45 | _reader = std::move(pair.reader); 46 | _writer = std::move(pair.writer); 47 | 48 | } 49 | 50 | void base_pollable::wait_for_readable() 51 | { 52 | assert(_fd != -1); 53 | 54 | _service.wait_for_readable(_fd, _writer); 55 | std::error_code e = _reader.get(); 56 | if (e) 57 | { 58 | throw std::system_error(e, "wait_for_readable"); 59 | } 60 | } 61 | 62 | void base_pollable::wait_for_writable() 63 | { 64 | _service.wait_for_writable(_fd, _writer); 65 | std::error_code e = _reader.get(); 66 | if (e) 67 | { 68 | throw std::system_error(e, "wait_for_writable"); 69 | } 70 | 71 | } 72 | 73 | std::size_t base_pollable::read(char* buf, std::size_t how_much) 74 | { 75 | assert(is_open()); 76 | 77 | std::size_t total = 0; 78 | while(total < how_much) 79 | { 80 | ssize_t r = ::read(get_fd(), buf + total, how_much - total); 81 | if (r == 0) 82 | { 83 | return total; 84 | } 85 | else if (r < 0) 86 | { 87 | if (errno == EAGAIN || errno == EWOULDBLOCK) 88 | { 89 | wait_for_readable(); 90 | } 91 | else 92 | { 93 | throw_errno("read"); 94 | } 95 | } 96 | else 97 | { 98 | total += r; 99 | } 100 | } 101 | return total; 102 | } 103 | 104 | std::size_t base_pollable::read_some(char* buf, std::size_t how_much) 105 | { 106 | assert(is_open()); 107 | 108 | for(;;) 109 | { 110 | ssize_t r = ::read(get_fd(), buf, how_much); 111 | if (r < 0) 112 | { 113 | if (errno == EAGAIN || errno == EWOULDBLOCK) 114 | { 115 | wait_for_readable(); 116 | } 117 | else 118 | { 119 | throw_errno("read_some"); 120 | } 121 | } 122 | else 123 | { 124 | return r; 125 | } 126 | } 127 | } 128 | 129 | std::size_t base_pollable::read_unitl(char* buf, std::size_t how_much, const std::string& pattern) 130 | { 131 | assert(is_open()); 132 | 133 | std::size_t total = 0; 134 | while(total < how_much) 135 | { 136 | std::size_t r= read_some(buf+total, how_much-total); 137 | if (r == 0) 138 | { 139 | return total; 140 | } 141 | total += r; 142 | 143 | auto it = std::search(buf, buf+total, pattern.begin(), pattern.end()); 144 | if (it != buf+total) 145 | { 146 | return total; 147 | } 148 | } 149 | return total; 150 | } 151 | 152 | std::size_t base_pollable::write(const char* buf, std::size_t how_much) 153 | { 154 | assert(is_open()); 155 | 156 | std::size_t total = 0; 157 | while(total < how_much) 158 | { 159 | ssize_t r = ::write(get_fd(), buf + total, how_much - total); 160 | if (r == 0) 161 | { 162 | return total; 163 | } 164 | else if (r < 0) 165 | { 166 | if (errno == EAGAIN || errno == EWOULDBLOCK) 167 | { 168 | wait_for_writable(); 169 | } 170 | else 171 | { 172 | throw_errno("write"); 173 | } 174 | } 175 | else 176 | { 177 | total += r; 178 | } 179 | } 180 | return total; 181 | } 182 | 183 | } 184 | -------------------------------------------------------------------------------- /coroutines_io/base_pollable.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #ifndef COROUTINES_BASE_POLLABLE_HPP 3 | #define COROUTINES_BASE_POLLABLE_HPP 4 | 5 | #include "coroutines/channel.hpp" 6 | 7 | #include 8 | 9 | namespace coroutines { 10 | 11 | class io_scheduler; 12 | 13 | class base_pollable 14 | { 15 | public: 16 | base_pollable(io_scheduler& srv); 17 | base_pollable(const base_pollable&) = delete; 18 | base_pollable(base_pollable&& o); 19 | 20 | ~base_pollable(); 21 | 22 | void close(); 23 | 24 | // read all unless how_much or EOF 25 | std::size_t read(char* buf, std::size_t how_much); 26 | 27 | // reads whatever is available, blocks only if nothing's there 28 | std::size_t read_some(char* buf, std::size_t how_much); 29 | 30 | // reads until buffer is full, contains pattern or EOF 31 | std::size_t read_unitl(char* buf, std::size_t how_much,const std::string& pattern); 32 | 33 | // write all 34 | std::size_t write(const char* buf, std::size_t how_much); 35 | 36 | 37 | protected: 38 | 39 | void set_fd(int get_fd); 40 | 41 | void wait_for_readable(); 42 | void wait_for_writable(); 43 | 44 | int get_fd() const { return _fd; } 45 | bool is_open() const { return _fd != -1; } 46 | 47 | io_scheduler& get_service() { return _service; } 48 | 49 | private: 50 | 51 | int _fd = -1; 52 | 53 | io_scheduler& _service; 54 | 55 | channel_reader _reader; 56 | channel_writer _writer; 57 | }; 58 | 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /coroutines_io/buffer.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #ifndef COROUTINES_IO_BUFFER_HPP 3 | #define COROUTINES_IO_BUFFER_HPP 4 | 5 | #include 6 | #include 7 | 8 | namespace coroutines_io 9 | { 10 | 11 | // used to send blocks of data around. Movable, bot not copytable. 12 | class buffer 13 | { 14 | public: 15 | 16 | typedef char value_type; 17 | typedef char* iterator; 18 | typedef const char* const_iterator; 19 | 20 | // null buffer 21 | buffer() = default; 22 | 23 | // alocates buffer 24 | buffer(std::size_t capacity) 25 | : _capacity(capacity), _size(0), _data(new char[_capacity]) 26 | { 27 | } 28 | 29 | ~buffer() 30 | { 31 | } 32 | 33 | buffer(const buffer&) = delete; 34 | buffer(buffer&& o) noexcept 35 | { 36 | swap(o); 37 | } 38 | 39 | buffer& operator=(buffer&& o) 40 | { 41 | swap(o); 42 | return *this; 43 | } 44 | 45 | // iterators 46 | iterator begin() { return _data.get(); } 47 | iterator end() { return _data.get() + _size; } 48 | const_iterator begin() const { return _data.get(); } 49 | const_iterator end() const { return _data.get() + _size; } 50 | 51 | // size/capacity 52 | void set_size(std::size_t s) { _size = s; } 53 | std::size_t size() const { return _size; } 54 | std::size_t capacity() const { return _capacity; } 55 | 56 | bool is_null() const { return !_capacity; } 57 | 58 | // other 59 | void swap(buffer& o) noexcept 60 | { 61 | std::swap(_capacity, o._capacity); 62 | std::swap(_size, o._size); 63 | std::swap(_data, o._data); 64 | } 65 | 66 | private: 67 | 68 | std::size_t _capacity = 0; // buffer capacity 69 | std::size_t _size = 0; // amount of data in 70 | std::unique_ptr _data; 71 | }; 72 | 73 | } 74 | 75 | namespace std { 76 | 77 | template<> 78 | inline 79 | void swap(coroutines_io::buffer& a, coroutines_io::buffer& b) 80 | { 81 | a.swap(b); 82 | } 83 | 84 | } 85 | #endif 86 | -------------------------------------------------------------------------------- /coroutines_io/detail/poller.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #include "coroutines_io/detail/poller.hpp" 3 | #include "coroutines/algorithm.hpp" 4 | #include "coroutines_io/globals.hpp" 5 | 6 | //#define CORO_LOGGING 7 | #include "coroutines/logging.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | namespace coroutines { namespace detail { 18 | 19 | static const std::uint64_t EVENTFD_KEY = std::numeric_limits::max(); 20 | 21 | poller::poller() 22 | { 23 | _event_fd = ::eventfd(0, EFD_NONBLOCK); 24 | if (_event_fd < 0) 25 | throw_errno(); 26 | 27 | _epoll = ::epoll_create(10); 28 | if (_epoll < 0) 29 | throw_errno(); 30 | 31 | // add event to epoll 32 | epoll_event ev; 33 | ev.events = EPOLLIN; 34 | ev.data.u64 = EVENTFD_KEY; 35 | int r = ::epoll_ctl(_epoll, EPOLL_CTL_ADD, _event_fd, &ev); 36 | if (r < 0) 37 | throw_errno(); 38 | } 39 | 40 | poller::~poller() 41 | { 42 | ::close(_event_fd); 43 | ::close(_epoll); 44 | } 45 | 46 | void poller::add_fd(int fd, fd_events e, std::uint64_t key) 47 | { 48 | // add to epoll 49 | epoll_event ev; 50 | ev.events = to_epoll_events(e) | EPOLLERR | EPOLLHUP /*| EPOLLET*/; 51 | ev.data.u64 = key; 52 | 53 | int r = ::epoll_ctl(_epoll, EPOLL_CTL_ADD, fd, &ev); 54 | if (r < 0) 55 | throw_errno("poller::add_fd"); 56 | 57 | CORO_LOG("POLLER: fd ", fd, " added to epoll with flags ", ev.events, ", fd_Events=", int(e)); 58 | } 59 | 60 | void poller::remove_fd(int fd) 61 | { 62 | int r = ::epoll_ctl(_epoll, EPOLL_CTL_DEL, fd, nullptr); 63 | if (r < 0) 64 | throw_errno("poller::remove_fd"); 65 | } 66 | 67 | void poller::wait(std::vector& keys) 68 | { 69 | static const unsigned EPOLL_BUFFER = 256; 70 | epoll_event events[EPOLL_BUFFER]; 71 | 72 | sigset_t sigs; 73 | sigemptyset(&sigs); 74 | sigaddset(&sigs, SIGTRAP); 75 | 76 | int r = ::epoll_pwait(_epoll, events, EPOLL_BUFFER, -1, &sigs); 77 | 78 | CORO_LOG("POLLER: woken up with ", r, " events ready"); 79 | 80 | if (r < 0 && errno != EINTR) 81 | throw_errno(); 82 | 83 | for(int i = 0; i < r; i++) 84 | { 85 | std::uint64_t key = events[i].data.u64; 86 | if (key == EVENTFD_KEY) 87 | { 88 | // re-arm eventfd 89 | std::uint64_t v = 1; 90 | if (::read(_event_fd, &v, sizeof(v)) < 0 ) 91 | throw_errno(); 92 | } 93 | else 94 | { 95 | keys.push_back(key); 96 | } 97 | } 98 | } 99 | 100 | void poller::wake() 101 | { 102 | std::uint64_t v = 1; 103 | if (::write(_event_fd, &v, sizeof(v)) < 0 ) 104 | throw_errno(); 105 | } 106 | 107 | int poller::to_epoll_events(fd_events e) 108 | { 109 | int result = 0; 110 | 111 | if (e & FD_READABLE) result |= EPOLLIN; 112 | if (e & FD_WRITABLE) result |= EPOLLOUT; 113 | 114 | return result; 115 | } 116 | 117 | 118 | 119 | }} 120 | -------------------------------------------------------------------------------- /coroutines_io/detail/poller.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #ifndef COROUTINES_IO_DETAIL_POLLER_HPP 3 | #define COROUTINES_IO_DETAIL_POLLER_HPP 4 | 5 | #include "coroutines/channel.hpp" 6 | #include "coroutines_io/globals.hpp" 7 | 8 | #include 9 | #include 10 | 11 | namespace coroutines { 12 | 13 | namespace detail { 14 | 15 | 16 | // wrapper for epoll 17 | class poller 18 | { 19 | public: 20 | 21 | poller(); 22 | ~poller(); 23 | 24 | // not thread safe, has to be called between calls to wait(); 25 | void add_fd(int fd, fd_events e, std::uint64_t key); 26 | void remove_fd(int fd); 27 | 28 | // will block until one of the fd's bcomes active, ro wakeup() is called 29 | // filles 'keys' with activated descriptors 30 | void wait(std::vector& keys); 31 | 32 | // interrupts wait(). 33 | void wake(); 34 | 35 | private: 36 | 37 | static int to_epoll_events(fd_events e); 38 | 39 | int _event_fd = -1; 40 | int _epoll = -1; 41 | }; 42 | 43 | }} 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /coroutines_io/file.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #include "coroutines_io/file.hpp" 3 | 4 | #include "coroutines_io/globals.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace coroutines { 13 | 14 | file::file(io_scheduler& srv) 15 | : base_pollable(srv) 16 | { 17 | 18 | } 19 | 20 | file::file() 21 | : base_pollable(get_io_scheduler_check()) 22 | { 23 | } 24 | 25 | void file::open_for_reading(const std::string& path) 26 | { 27 | open(path, O_NONBLOCK|O_RDONLY); 28 | } 29 | 30 | void file::open_for_writing(const std::string& path) 31 | { 32 | open(path, O_NONBLOCK|O_WRONLY|O_CREAT|O_TRUNC); 33 | } 34 | 35 | void file::open(const std::string& path, int flags) 36 | { 37 | assert(!is_open()); 38 | 39 | int fd = ::open(path.c_str(), flags, 00666); 40 | if (fd < 0 ) 41 | { 42 | throw_errno("open"); 43 | } 44 | else 45 | { 46 | set_fd(fd); 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /coroutines_io/file.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #ifndef COROUTINES_FILE_HPP 3 | #define COROUTINES_FILE_HPP 4 | 5 | #include "coroutines_io/base_pollable.hpp" 6 | 7 | namespace coroutines { 8 | 9 | // disk file 10 | class file : public base_pollable 11 | { 12 | public: 13 | 14 | file(io_scheduler& srv); 15 | file(); // uses get_service_check() 16 | 17 | void open_for_reading(const std::string& path); 18 | void open_for_writing(const std::string& path); 19 | 20 | private: 21 | 22 | void open(const std::string& path, int flags); 23 | }; 24 | 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /coroutines_io/globals.cpp: -------------------------------------------------------------------------------- 1 | #include "globals.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace coroutines 7 | { 8 | 9 | static io_scheduler* __io_scheduler = nullptr; 10 | 11 | void set_io_scheduler(io_scheduler* s) 12 | { 13 | __io_scheduler = s; 14 | } 15 | 16 | io_scheduler* get_io_scheduler() 17 | { 18 | return __io_scheduler; 19 | } 20 | 21 | io_scheduler& get_io_scheduler_check() 22 | { 23 | assert(__io_scheduler); 24 | return *__io_scheduler; 25 | } 26 | 27 | void throw_errno() 28 | { 29 | throw std::system_error(errno, std::system_category()); 30 | } 31 | 32 | void throw_errno(const std::string& what) 33 | { 34 | throw std::system_error(errno, std::system_category(), what); 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /coroutines_io/globals.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #ifndef COROUTINES_IO_GLOBALS_HPP 3 | #define COROUTINES_IO_GLOBALS_HPP 4 | 5 | #include 6 | 7 | namespace coroutines 8 | { 9 | 10 | class io_scheduler; 11 | 12 | void set_io_scheduler(io_scheduler* s); 13 | io_scheduler* get_io_scheduler(); 14 | io_scheduler& get_io_scheduler_check(); // asserts io_scheduler != null 15 | 16 | void throw_errno(); 17 | void throw_errno(const std::string& what); 18 | 19 | enum fd_events 20 | { 21 | FD_READABLE = 0x01, 22 | FD_WRITABLE = 0x02 23 | }; 24 | 25 | 26 | } 27 | 28 | #endif 29 | 30 | -------------------------------------------------------------------------------- /coroutines_io/io_scheduler.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #include "coroutines_io/io_scheduler.hpp" 4 | #include "coroutines_io/globals.hpp" 5 | #include "coroutines/globals.hpp" 6 | 7 | #include "coroutines_io/detail/poller.hpp" 8 | 9 | //#define CORO_LOGGING 10 | #include "coroutines/logging.hpp" 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace coroutines { 18 | 19 | io_scheduler::io_scheduler(scheduler& sched) 20 | : _scheduler(sched) 21 | { 22 | } 23 | 24 | io_scheduler::~io_scheduler() 25 | { 26 | } 27 | 28 | void io_scheduler::wait_for_writable(int fd, const channel_writer& writer) 29 | { 30 | CORO_LOG("SERV: wait for writable fd=", fd); 31 | _command_writer.put(command{fd, FD_WRITABLE, writer}); 32 | _poller.wake(); 33 | } 34 | 35 | void io_scheduler::wait_for_readable(int fd, const channel_writer& writer) 36 | { 37 | CORO_LOG("SERV: wait for readable fd=", fd); 38 | _command_writer.put(command{fd, FD_READABLE, writer}); 39 | _poller.wake(); 40 | } 41 | 42 | void io_scheduler::start() 43 | { 44 | auto pair = _scheduler.make_channel(256, "io_scheduler command channel"); 45 | _command_writer = std::move(pair.writer); 46 | _command_reader = std::move(pair.reader); 47 | 48 | _scheduler.go("service loop", [this](){ loop(); }); 49 | } 50 | 51 | void io_scheduler::stop() 52 | { 53 | _command_writer.close(); 54 | } 55 | 56 | void io_scheduler::loop() 57 | { 58 | std::unordered_map commands; 59 | std::uint64_t counter = 0; 60 | std::vector keys; 61 | 62 | for(;;) 63 | { 64 | command cmd; 65 | 66 | // if no pending commands, block on reader 67 | if (commands.empty()) 68 | { 69 | CORO_LOG("SERV: blocking on commands"); 70 | try 71 | { 72 | cmd = _command_reader.get(); 73 | } 74 | catch(const channel_closed&) 75 | { 76 | CORO_LOG("SERV: blocking on commands interrupted"); 77 | throw; 78 | } 79 | _poller.add_fd(cmd.fd, cmd.events, counter); 80 | commands.insert(std::make_pair(counter++, cmd)); 81 | } 82 | 83 | // read all events 84 | while(_command_reader.try_get(cmd)) 85 | { 86 | _poller.add_fd(cmd.fd, cmd.events, counter); 87 | commands.insert(std::make_pair(counter++, cmd)); 88 | } 89 | 90 | CORO_LOG("SERV: polling, ", commands.size(), " sockets pending"); 91 | 92 | // poll! 93 | keys.clear(); 94 | block([&]() 95 | { 96 | _poller.wait(keys); 97 | }); 98 | 99 | CORO_LOG("SERV: ", keys.size(), " events ready"); 100 | 101 | // serve events 102 | for(std::uint64_t key : keys) 103 | { 104 | auto it = commands.find(key); 105 | assert(it != commands.end()); 106 | 107 | // get error 108 | std::error_code ec; 109 | int err = 0; 110 | socklen_t err_len = sizeof(err); 111 | int r = ::getsockopt(it->second.fd, SOL_SOCKET, SO_ERROR, &err, &err_len); 112 | if (r == 0) 113 | { 114 | ec = std::error_code(err, std::system_category()); 115 | } 116 | CORO_LOG("SERV: sending event on fd=", it->second.fd, " ec=", ec); 117 | 118 | it->second.writer.put(ec); 119 | commands.erase(it); 120 | _poller.remove_fd(it->second.fd); 121 | } 122 | 123 | } 124 | 125 | 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /coroutines_io/io_scheduler.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #ifndef COROUTINES_IO_SERVICE_HPP 3 | #define COROUTINES_IO_SERVICE_HPP 4 | 5 | #include "coroutines/scheduler.hpp" 6 | #include "coroutines_io/detail/poller.hpp" 7 | #include "coroutines_io/globals.hpp" 8 | 9 | #include 10 | 11 | namespace coroutines { 12 | 13 | class io_scheduler 14 | { 15 | public: 16 | 17 | io_scheduler(scheduler& sched); 18 | ~io_scheduler(); 19 | 20 | scheduler& get_scheduler() { return _scheduler; } 21 | 22 | // services provided 23 | void wait_for_writable(int fd, const channel_writer& writer); 24 | void wait_for_readable(int fd, const channel_writer& writer); 25 | 26 | void start(); 27 | void stop(); 28 | 29 | private: 30 | 31 | struct command 32 | { 33 | int fd; 34 | fd_events events; 35 | channel_writer writer; 36 | }; 37 | 38 | void loop(); 39 | scheduler& _scheduler; 40 | detail::poller _poller; 41 | channel_writer _command_writer; 42 | channel_reader _command_reader; 43 | }; 44 | 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /coroutines_io/socket_streambuf.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #ifndef COROUTINES_IO_SOCKET_STREAMBUF_HPP 3 | #define COROUTINES_IO_SOCKET_STREAMBUF_HPP 4 | 5 | #include "coroutines_io/base_pollable.hpp" 6 | 7 | #include 8 | #include 9 | 10 | namespace coroutines { 11 | 12 | // current status: 13 | // * no putback 14 | class socket_istreambuf : public std::streambuf 15 | { 16 | public: 17 | 18 | using std::streambuf::int_type; 19 | using std::streambuf::traits_type; 20 | 21 | explicit socket_istreambuf(base_pollable& sock) 22 | : _socket(sock) 23 | { 24 | setg(end(), end(), end()); 25 | } 26 | 27 | socket_istreambuf(const socket_istreambuf&) = delete; 28 | 29 | protected: 30 | 31 | virtual int_type underflow() override 32 | { 33 | if (gptr() < egptr()) 34 | { 35 | // std::cout << "STREAM: buffer not exhausetd, why are you bothering me?" << std::endl; 36 | return traits_type::to_int_type(*gptr()); 37 | } 38 | else 39 | { 40 | //std::cout << "STREAM: need to refill..." << std::endl; 41 | // need to load some data 42 | std::size_t n = _socket.read_some(begin(), BUFFER_SIZE); 43 | if (n == 0) 44 | { 45 | return traits_type::eof(); 46 | } 47 | else 48 | { 49 | //std::cout << "STREAM: " << n << " bytes added to buffer" << std::endl; 50 | setg(begin(), begin(), begin() + n); 51 | return traits_type::to_int_type(*gptr()); 52 | } 53 | } 54 | } 55 | 56 | private: 57 | 58 | static constexpr unsigned BUFFER_SIZE = 4096; 59 | 60 | typedef std::array buffer_type; 61 | 62 | const char* begin() const { return _buffer.data(); } 63 | const char* end() const { return _buffer.data() + BUFFER_SIZE; } 64 | 65 | char* begin() { return _buffer.data(); } 66 | char* end() { return _buffer.data() + BUFFER_SIZE; } 67 | 68 | buffer_type _buffer; 69 | base_pollable& _socket; 70 | }; 71 | 72 | 73 | class socket_ostreambuf: public std::streambuf 74 | { 75 | public: 76 | 77 | using std::streambuf::int_type; 78 | using std::streambuf::traits_type; 79 | 80 | explicit socket_ostreambuf(base_pollable& sock) 81 | : _socket(sock) 82 | { 83 | setp(begin(), end()); 84 | } 85 | 86 | socket_ostreambuf(const socket_istreambuf&) = delete; 87 | 88 | protected: 89 | 90 | virtual int_type overflow(int_type ch) override 91 | { 92 | _socket.write(begin(), pptr() - begin()); 93 | if (ch != traits_type::eof()) 94 | { 95 | _buffer[0] = traits_type::to_char_type(ch); 96 | setp(begin()+1, end()); 97 | } 98 | else 99 | { 100 | setp(begin(), end()); 101 | } 102 | return traits_type::to_int_type('a'); 103 | } 104 | 105 | virtual int sync() 106 | { 107 | overflow(traits_type::eof()); 108 | return 0; 109 | } 110 | 111 | private: 112 | 113 | static constexpr unsigned BUFFER_SIZE = 4096; 114 | 115 | typedef std::array buffer_type; 116 | 117 | const char* begin() const { return _buffer.data(); } 118 | const char* end() const { return _buffer.data() + BUFFER_SIZE; } 119 | 120 | char* begin() { return _buffer.data(); } 121 | char* end() { return _buffer.data() + BUFFER_SIZE; } 122 | 123 | buffer_type _buffer; 124 | base_pollable& _socket; 125 | }; 126 | 127 | } 128 | 129 | #endif 130 | -------------------------------------------------------------------------------- /coroutines_io/tcp_acceptor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #include "coroutines_io/tcp_acceptor.hpp" 3 | #include "coroutines_io/globals.hpp" 4 | #include "coroutines_io/io_scheduler.hpp" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace coroutines { 12 | 13 | tcp_acceptor::tcp_acceptor(io_scheduler& srv) 14 | : base_pollable(srv) 15 | { 16 | } 17 | 18 | tcp_acceptor::tcp_acceptor() 19 | : base_pollable(get_io_scheduler_check()) 20 | { 21 | } 22 | 23 | void tcp_acceptor::listen(const tcp_acceptor::endpoint_type& endpoint) 24 | { 25 | assert(!_listening); 26 | 27 | int af = endpoint.address().is_v4() ? AF_INET : AF_INET6; 28 | 29 | open(af); 30 | 31 | int cr = 0; 32 | if (af == AF_INET) 33 | { 34 | sockaddr_in addr; 35 | std::memset(&addr, 0, sizeof(addr)); 36 | 37 | addr.sin_family = af; 38 | addr.sin_addr.s_addr = htonl(endpoint.address().to_v4().to_ulong()); 39 | addr.sin_port = htons(endpoint.port()); 40 | cr = ::bind(get_fd(), (sockaddr*)&addr, sizeof(addr)); 41 | } 42 | else 43 | { 44 | assert(true); // not implemented 45 | } 46 | 47 | if (cr < 0) 48 | { 49 | throw_errno(); 50 | } 51 | 52 | cr = ::listen(get_fd(), 256); // compeltely arbitrary queue size 53 | if (cr < 0) 54 | throw_errno(); 55 | 56 | _listening = true; 57 | 58 | } 59 | 60 | tcp_socket tcp_acceptor::accept() 61 | { 62 | assert(_listening); 63 | 64 | sockaddr_in addr; 65 | socklen_t addr_len = sizeof(addr); 66 | for(;;) 67 | { 68 | int fd = ::accept4(get_fd(), (sockaddr*)&addr, &addr_len, SOCK_NONBLOCK); 69 | if (fd < 0 ) 70 | { 71 | if (errno == EWOULDBLOCK || errno == EAGAIN) 72 | { 73 | // std::cout << "ACCEPT: acceptor would block" << std::endl; 74 | wait_for_readable(); 75 | continue; 76 | } 77 | } 78 | else 79 | { 80 | boost::asio::ip::address_v4 a(ntohl(addr.sin_addr.s_addr)); 81 | 82 | return tcp_socket(get_service(), fd, endpoint_type(a, ntohs(addr.sin_port))); 83 | } 84 | }; 85 | } 86 | 87 | void tcp_acceptor::open(int address_family) 88 | { 89 | if (!is_open()) 90 | { 91 | int fd = ::socket( 92 | address_family, 93 | SOCK_STREAM | SOCK_NONBLOCK, 94 | 0); 95 | 96 | int one = 1; 97 | ::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); 98 | 99 | if (fd == -1) 100 | { 101 | throw_errno("open acceptor"); 102 | } 103 | else 104 | { 105 | set_fd(fd); 106 | } 107 | } 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /coroutines_io/tcp_acceptor.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #ifndef COROUTINES_TCP_ACCEPTOR_HPP 3 | #define COROUTINES_TCP_ACCEPTOR_HPP 4 | 5 | #include "coroutines/channel.hpp" 6 | 7 | #include "coroutines_io/tcp_socket.hpp" 8 | 9 | #include 10 | 11 | namespace coroutines { 12 | 13 | class io_scheduler; 14 | 15 | class tcp_acceptor : public base_pollable 16 | { 17 | public: 18 | typedef boost::asio::ip::tcp::endpoint endpoint_type; 19 | 20 | tcp_acceptor(coroutines::io_scheduler& srv); 21 | tcp_acceptor(); // uses get_io_scheduler_check() 22 | tcp_acceptor(const tcp_acceptor&) = delete; 23 | 24 | ~tcp_acceptor() = default; 25 | 26 | void listen(const endpoint_type& endpoint); 27 | 28 | tcp_socket accept(); 29 | 30 | private: 31 | 32 | void open(int af); 33 | 34 | bool _listening = false; 35 | }; 36 | 37 | } 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /coroutines_io/tcp_resolver.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #include "coroutines_io/tcp_resolver.hpp" 4 | #include "coroutines_io/io_scheduler.hpp" 5 | 6 | #include "coroutines/globals.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | 15 | namespace coroutines { 16 | 17 | void tcp_resolve(const std::string& hostname, const std::string& service, std::vector& out) 18 | { 19 | addrinfo hints; 20 | addrinfo* result = nullptr; 21 | std::memset(&hints, 0, sizeof(addrinfo)); 22 | 23 | block("address resolution started"); 24 | 25 | int r = getaddrinfo( 26 | hostname.c_str(), 27 | service.c_str(), 28 | &hints, 29 | &result); 30 | 31 | hints.ai_family = AF_INET; 32 | hints.ai_socktype = SOCK_STREAM; 33 | 34 | unblock("address resolution finished"); 35 | 36 | if (r != 0) 37 | { 38 | throw_errno("tcp_resolver::resolve"); 39 | } 40 | 41 | for(addrinfo* addr_info = result; addr_info; addr_info = addr_info->ai_next) 42 | { 43 | if(addr_info->ai_family == AF_INET) 44 | { 45 | const sockaddr_in* addr = reinterpret_cast(addr_info->ai_addr); 46 | boost::asio::ip::address_v4 a(ntohl(addr->sin_addr.s_addr)); 47 | 48 | out.emplace_back(a, ntohs(addr->sin_port)); 49 | } 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /coroutines_io/tcp_resolver.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #ifndef COROUTINES_TCP_RESOLVER_HPP 4 | #define COROUTINES_TCP_RESOLVER_HPP 5 | 6 | #include "coroutines_io/globals.hpp" 7 | #include "coroutines_io/tcp_socket.hpp" 8 | 9 | #include 10 | 11 | namespace coroutines { 12 | 13 | void tcp_resolve(const std::string& hostname, const std::string& io_scheduler, std::vector& out); 14 | 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /coroutines_io/tcp_socket.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #include "coroutines_io/tcp_socket.hpp" 4 | #include "coroutines_io/io_scheduler.hpp" 5 | #include "coroutines_io/globals.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | namespace coroutines { 15 | 16 | tcp_socket::tcp_socket(coroutines::io_scheduler& srv) 17 | : base_pollable(srv) 18 | { 19 | } 20 | 21 | tcp_socket::tcp_socket() 22 | : base_pollable(get_io_scheduler_check()) 23 | { 24 | } 25 | 26 | tcp_socket::tcp_socket(tcp_socket&& o) 27 | : base_pollable(std::move(o)) 28 | , _remote_endpoint(std::move(o._remote_endpoint)) 29 | { 30 | } 31 | 32 | tcp_socket::tcp_socket(io_scheduler& srv, int fd, const endpoint_type& remote_endpoint) 33 | : base_pollable(srv) 34 | , _remote_endpoint(remote_endpoint) 35 | { 36 | set_fd(fd); 37 | } 38 | 39 | 40 | void tcp_socket::connect(const tcp_socket::endpoint_type& endpoint) 41 | { 42 | 43 | int af = endpoint.address().is_v4() ? AF_INET : AF_INET6; 44 | 45 | open(af); 46 | 47 | int cr = 0; 48 | if (af == AF_INET) 49 | { 50 | sockaddr_in addr; 51 | std::memset(&addr, 0, sizeof(addr)); 52 | 53 | addr.sin_family = af; 54 | addr.sin_addr.s_addr = htonl(endpoint.address().to_v4().to_ulong()); 55 | addr.sin_port = htons(endpoint.port()); 56 | cr = ::connect(get_fd(), (sockaddr*)&addr, sizeof(addr)); 57 | } 58 | else 59 | { 60 | assert(true); // not implemented 61 | } 62 | 63 | if (cr == 0 ) 64 | { 65 | assert(false); 66 | } 67 | if (errno != EINPROGRESS) 68 | { 69 | throw_errno("connect"); 70 | } 71 | wait_for_writable(); 72 | _remote_endpoint = endpoint; 73 | } 74 | 75 | void tcp_socket::shutdown() 76 | { 77 | int r = ::shutdown(get_fd(), SHUT_RDWR); 78 | if (r < 0) 79 | throw_errno("shutdown"); 80 | } 81 | 82 | 83 | void tcp_socket::open(int address_family) 84 | { 85 | int fd = ::socket( 86 | address_family, 87 | SOCK_STREAM | SOCK_NONBLOCK, 88 | 0); 89 | 90 | if (fd == -1) 91 | { 92 | throw_errno(); 93 | } 94 | else 95 | { 96 | set_fd(fd); 97 | } 98 | } 99 | 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /coroutines_io/tcp_socket.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #ifndef COROUTINES_IO_TCP_SOCKET_HPP 3 | #define COROUTINES_IO_TCP_SOCKET_HPP 4 | 5 | #include "coroutines/channel.hpp" 6 | 7 | #include "coroutines_io/base_pollable.hpp" 8 | 9 | #include 10 | #include 11 | 12 | namespace coroutines { 13 | 14 | class io_scheduler; 15 | 16 | 17 | // implements boost:asio::SyncReadStream 18 | class tcp_socket : public base_pollable 19 | { 20 | public: 21 | 22 | typedef boost::asio::ip::tcp::endpoint endpoint_type; 23 | 24 | // then object uses service and must not outlive it 25 | tcp_socket(io_scheduler& srv); 26 | tcp_socket(); // uses get_io_scheduler_check() 27 | 28 | tcp_socket(const tcp_socket&) = delete; 29 | tcp_socket(tcp_socket&&); 30 | 31 | tcp_socket(io_scheduler& srv, int get_fd, const endpoint_type& remote_endpoint); 32 | 33 | ~tcp_socket() = default; 34 | 35 | void connect(const endpoint_type& endpoint); 36 | 37 | endpoint_type remote_endpoint() const { return _remote_endpoint; } 38 | 39 | void shutdown(); 40 | 41 | private: 42 | 43 | void open(int address_family); 44 | endpoint_type _remote_endpoint; 45 | 46 | }; 47 | 48 | } 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /http_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package( Boost 1.54.0 COMPONENTS system) 2 | include_directories(${Boost_INCLUDE_DIRS}) 3 | 4 | if(ENABLE_PROFILING) 5 | message(STATUS "Profiling enabled") 6 | add_definitions(-DCOROUTINES_PROFILING) 7 | endif() 8 | 9 | add_executable(http_server 10 | server.cpp 11 | client_connection.cpp client_connection.hpp 12 | http_response.hpp 13 | http_request.hpp 14 | 15 | ) 16 | 17 | 18 | target_link_libraries(http_server 19 | 20 | coroutines 21 | coroutines_io 22 | 23 | PocoNet 24 | 25 | ${Boost_LIBRARIES} 26 | ) 27 | -------------------------------------------------------------------------------- /http_test/client.cpp: -------------------------------------------------------------------------------- 1 | #include "coroutines_http/http_client.hpp" 2 | 3 | #include "coroutines_io/globals.hpp" 4 | #include "coroutines_io/service.hpp" 5 | 6 | #include "coroutines/scheduler.hpp" 7 | #include "coroutines/globals.hpp" 8 | 9 | #include // TODO remove 10 | 11 | #include 12 | 13 | using namespace coroutines; 14 | 15 | void client(const char* url) 16 | { 17 | try 18 | { 19 | http_client client; 20 | network::http::request req(url); 21 | network::http::response resp = client.get(req); 22 | 23 | std::string body; 24 | resp.get_body(body); 25 | std::cout << body << std::endl; 26 | } 27 | catch(const std::exception& e) 28 | { 29 | std::cerr << " Error: " << e.what() << std::endl; 30 | } 31 | } 32 | 33 | int main(int argc, char** argv) 34 | { 35 | if (argc <= 1) 36 | { 37 | std::cerr << "USAGE: client URL" << std::endl; 38 | return 2; 39 | } 40 | 41 | // scheduler sched; 42 | // service serv(sched); 43 | // set_scheduler(&sched); 44 | // set_service(&serv); 45 | 46 | // serv.start(); 47 | 48 | // go(client, argv[1]); 49 | 50 | // sched.wait(); 51 | 52 | using boost::asio::ip::tcp; 53 | 54 | boost::asio::io_service io_service; 55 | 56 | tcp::resolver resolver(io_service); 57 | tcp::resolver::query query(argv[1], "http"); 58 | tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); 59 | 60 | tcp::socket socket(io_service); 61 | boost::asio::connect(socket, endpoint_iterator); 62 | 63 | boost::asio::streambuf request; 64 | std::ostream request_stream(&request); 65 | request_stream << "GET " << argv[2] << " HTTP/1.0\r\n"; 66 | request_stream << "Host: " << argv[1] << "\r\n"; 67 | request_stream << "Accept: */*\r\n"; 68 | request_stream << "Connection: close\r\n\r\n"; 69 | 70 | // Send the request. 71 | boost::asio::write(socket, request); 72 | } 73 | -------------------------------------------------------------------------------- /http_test/client_connection.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #include "client_connection.hpp" 3 | #include "coroutines_io/socket_streambuf.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Poco/Net/NetException.h" 10 | 11 | client_connection::client_connection(tcp_socket&& s, handler_type&& handler) 12 | : _socket(std::move(s)) 13 | , _handler(std::move(handler)) 14 | { 15 | } 16 | 17 | // case-insensitive comparison 18 | template 19 | bool ci_equal(const StringA& a, const StringB& b) 20 | { 21 | return std::equal( 22 | std::begin(a), std::end(a), std::begin(b), 23 | [](char ac, char bc) { return (ac & 0x1f) == (bc & 0x1f); } 24 | ); 25 | } 26 | 27 | void client_connection::start() 28 | { 29 | //std::cout << "HTTP: conenction from: " << _socket.remote_endpoint() << std::endl; 30 | 31 | try 32 | { 33 | socket_istreambuf ibuf(_socket); 34 | socket_ostreambuf obuf(_socket); 35 | std::istream istream(&ibuf); 36 | std::ostream ostream(&obuf); 37 | 38 | for(;;) 39 | { 40 | http_request request(istream); 41 | try 42 | { 43 | //std::cout << "HTTP: reading header..." << std::endl; 44 | request.read_header(); 45 | //std::cout << "HTTP: header read OK" << std::endl; 46 | } 47 | catch(const Poco::Net::NoMessageException&) 48 | { 49 | //std::cout << "HTTP: no message" << std::endl; 50 | return; 51 | } 52 | 53 | http_response response(ostream); 54 | 55 | // honour HTTP 1.0 vs 1.1 and Connection: close 56 | bool keep_alive = false; 57 | if (request.getVersion() == http_response::HTTP_1_1 && ci_equal(request.get("Connection", ""), "close")) 58 | { 59 | keep_alive = true; 60 | } 61 | else if (ci_equal(request.get("Connection", ""), "keep-alive")) 62 | { 63 | keep_alive = true; 64 | } 65 | 66 | // set Date. This is ugly, this should be one-lines with std::put_time 67 | std::time_t now = std::time(nullptr); 68 | char date_buffer[64]; 69 | std::strftime(date_buffer, 64, "%a, %d %b %Y %T GMT", std::gmtime(&now)); 70 | response.add("Date", date_buffer); 71 | 72 | // set keep-alive 73 | if (keep_alive) 74 | { 75 | response.add("Connection", "Keep-Alive"); 76 | } 77 | 78 | _handler(request, response); 79 | 80 | if (!keep_alive) 81 | break; 82 | } 83 | } 84 | catch(const std::exception& e) 85 | { 86 | std::cerr << "exception in client: " << e.what() << std::endl; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /http_test/client_connection.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #ifndef CLIENT_CONNECTION_HPP 4 | #define CLIENT_CONNECTION_HPP 5 | 6 | #include "coroutines_io/tcp_socket.hpp" 7 | 8 | #include "http_request.hpp" 9 | #include "http_response.hpp" 10 | 11 | using namespace coroutines; 12 | 13 | class client_connection 14 | { 15 | public: 16 | 17 | typedef std::function handler_type; 18 | 19 | client_connection(tcp_socket&& s, handler_type&& handler); 20 | 21 | void start(); 22 | 23 | private: 24 | 25 | tcp_socket _socket; 26 | handler_type _handler; 27 | }; 28 | 29 | 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /http_test/http_request.cpp: -------------------------------------------------------------------------------- 1 | #include "http_request.hpp" 2 | 3 | -------------------------------------------------------------------------------- /http_test/http_request.hpp: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_REQUEST_HPP 2 | #define HTTP_REQUEST_HPP 3 | 4 | #include "Poco/Net/HTTPRequest.h" 5 | 6 | class http_request : public Poco::Net::HTTPRequest 7 | { 8 | public: 9 | http_request(std::istream& stream) 10 | : _stream(stream) 11 | { 12 | } 13 | 14 | void read_header() 15 | { 16 | read(_stream); 17 | } 18 | 19 | // stream for reading body 20 | std::istream& stream() { return _stream; } 21 | 22 | private: 23 | 24 | std::istream& _stream; 25 | }; 26 | 27 | #endif // HTTP_REQUEST_HPP 28 | -------------------------------------------------------------------------------- /http_test/http_response.cpp: -------------------------------------------------------------------------------- 1 | #include "http_response.hpp" 2 | 3 | -------------------------------------------------------------------------------- /http_test/http_response.hpp: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_RESPONSE_HPP 2 | #define HTTP_RESPONSE_HPP 3 | 4 | #include "Poco/Net/HTTPResponse.h" 5 | 6 | class http_response : public Poco::Net::HTTPResponse 7 | { 8 | public: 9 | http_response(std::ostream& stream) 10 | : _stream(stream) 11 | { } 12 | 13 | // header manipulation has no effect after this call 14 | std::ostream& stream() 15 | { 16 | ensure_header_send(); 17 | return _stream; 18 | } 19 | 20 | ~http_response() 21 | { 22 | ensure_header_send(); 23 | _stream.flush(); 24 | } 25 | 26 | private: 27 | 28 | void ensure_header_send() 29 | { 30 | if (!_header_written) 31 | { 32 | write(_stream); 33 | _header_written = true; 34 | } 35 | } 36 | 37 | std::ostream& _stream; 38 | bool _header_written = false; 39 | }; 40 | 41 | #endif // HTTP_REPLY_HPP 42 | -------------------------------------------------------------------------------- /http_test/server.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #include "coroutines/globals.hpp" 3 | #include "coroutines/scheduler.hpp" 4 | 5 | #include "profiling/profiling.hpp" 6 | 7 | #include "coroutines_io/globals.hpp" 8 | #include "coroutines_io/io_scheduler.hpp" 9 | #include "coroutines_io/tcp_acceptor.hpp" 10 | 11 | #include "client_connection.hpp" 12 | 13 | #include 14 | 15 | #include 16 | 17 | using namespace coroutines; 18 | using namespace boost::asio::ip; 19 | 20 | void handler(http_request const& req, http_response& res) 21 | { 22 | res.setStatus(Poco::Net::HTTPResponse::HTTP_OK); 23 | res.add("Content-Length", "14"); 24 | res.add("Content-Type", "text/plain"); 25 | 26 | res.stream() << "hello, world!\n"; 27 | } 28 | 29 | void start_client_connection(tcp_socket& sock) 30 | { 31 | client_connection c(std::move(sock), handler); 32 | c.start(); 33 | } 34 | 35 | void server() 36 | { 37 | try 38 | { 39 | tcp_acceptor acc; 40 | acc.listen(tcp::endpoint(address_v4::any(), 8080)); 41 | 42 | std::cout << "Server accepting connections" << std::endl; 43 | for(;;) 44 | { 45 | tcp_socket sock = acc.accept(); 46 | //std::cout << "HTTP: connection accepted" << std::endl; 47 | go("client connection", start_client_connection, std::move(sock)); 48 | } 49 | } 50 | catch(const std::exception& e) 51 | { 52 | std::cerr << "server error: " << e.what() << std::endl; 53 | } 54 | } 55 | 56 | void signal_handler(int) 57 | { 58 | CORO_PROF_DUMP(); 59 | std::terminate(); 60 | } 61 | 62 | int main(int argc, char** argv) 63 | { 64 | signal(SIGINT, signal_handler); 65 | 66 | scheduler sched(4); 67 | io_scheduler io_sched(sched); 68 | set_scheduler(&sched); 69 | set_io_scheduler(&io_sched); 70 | 71 | io_sched.start(); 72 | 73 | go("acceptor", server); 74 | 75 | sched.wait(); 76 | } 77 | -------------------------------------------------------------------------------- /profiling/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(${Boost_INCLUDE_DIRS}) 2 | 3 | add_library(profiling STATIC 4 | profiling.cpp profiling.hpp 5 | ) 6 | -------------------------------------------------------------------------------- /profiling/profiling.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #include "profiling/profiling.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | namespace profiling { 22 | 23 | static const unsigned DATA_SIZE = 64; 24 | 25 | static std::uint64_t __sys_tv_sec_base = 0; 26 | static std::uint64_t __clock_base; // first-ever tcs call 27 | static double __ticks_per_ns = 0.0; 28 | 29 | struct record 30 | { 31 | std::int64_t time; 32 | std::thread::id thread_id; 33 | const char* object_type; 34 | void* object_id; 35 | std::uint32_t ordinal = 0; 36 | const char* event; 37 | char data[DATA_SIZE]; 38 | 39 | void write(std::ostream& stream, std::uint64_t time_ns); 40 | }; 41 | 42 | void record::write(std::ostream& stream, std::uint64_t time_ns) 43 | { 44 | stream << time_ns << "," << time << "," << std::hash()(thread_id) << "," << object_type << "," << (std::uintptr_t)object_id << "," << ordinal << "," << event << "," << data << "\n"; 45 | } 46 | 47 | static unsigned BLOCK_SIZE = 1000; 48 | static const char* PROFILING_FILE_NAME = "profiling_data.csv"; 49 | 50 | struct per_thread_data 51 | { 52 | std::forward_list blocks; 53 | unsigned counter = 0; 54 | std::thread::id thread_id; 55 | // tsc<->sys time relation. time from both clocks taken at the same time 56 | std::uint64_t sys_ns; 57 | std::uint64_t tsc_ticks; 58 | }; 59 | 60 | // returns tsc, in ticks. Takes 30-40 ticks 61 | inline std::uint64_t get_tsc() 62 | { 63 | std::uint32_t low, high; 64 | asm volatile ( 65 | "rdtsc" 66 | : "=a" (low), "=d" (high)); 67 | return std::uint64_t(high) << 32 | low; 68 | } 69 | 70 | 71 | // returns system time, in ns (from some arbitrary point). Takes 2us 72 | static std::int64_t get_systime() 73 | { 74 | timespec ts; 75 | clock_gettime(CLOCK_MONOTONIC, &ts); 76 | 77 | return (std::int64_t(ts.tv_sec) - __sys_tv_sec_base)*1000000000 + ts.tv_nsec; 78 | } 79 | 80 | // returns ticks per nanosecond 81 | static double calibrate_clock(); 82 | 83 | class global_profiling_data 84 | { 85 | public: 86 | typedef std::forward_list block_list; // needs to be fast and simple 87 | typedef std::list block_list_list; // modified once per thread, we can have some sophistication 88 | 89 | global_profiling_data() 90 | { 91 | __clock_base = get_tsc(); 92 | __ticks_per_ns = calibrate_clock(); 93 | 94 | timespec ts; 95 | clock_gettime(CLOCK_MONOTONIC, &ts); 96 | __sys_tv_sec_base = ts.tv_sec; 97 | } 98 | 99 | // caled when thred instrumentation is created, in the thread 100 | per_thread_data* get_per_thread_data() 101 | { 102 | std::lock_guard lock(_block_list_mutex); 103 | _block_lists.emplace_front(); 104 | 105 | // stick this thread to a core 106 | stick_to_core(_core++); 107 | 108 | return &_block_lists.front(); 109 | } 110 | 111 | void dump(); 112 | 113 | ~global_profiling_data() 114 | { 115 | dump(); 116 | } 117 | 118 | std::int64_t sys_tv_sec_base; 119 | 120 | private: 121 | 122 | void stick_to_core(int core) 123 | { 124 | int num_cores = sysconf(_SC_NPROCESSORS_ONLN); 125 | int core_adj = core % num_cores; 126 | 127 | cpu_set_t cpuset; 128 | CPU_ZERO(&cpuset); 129 | CPU_SET(core_adj, &cpuset); 130 | 131 | pthread_t current_thread = pthread_self(); 132 | pthread_setaffinity_np(current_thread, sizeof(cpu_set_t), &cpuset); 133 | } 134 | 135 | 136 | block_list_list _block_lists; 137 | std::mutex _block_list_mutex; 138 | int _core = 0; 139 | }; 140 | 141 | static global_profiling_data __global_profiling_data; 142 | thread_local std::thread::id __thread_id; 143 | 144 | class profiling_data 145 | { 146 | public: 147 | 148 | profiling_data() 149 | { 150 | __thread_id = std::this_thread::get_id(); 151 | _data = __global_profiling_data.get_per_thread_data(); 152 | _data->thread_id = __thread_id; 153 | 154 | record* block = new record[BLOCK_SIZE]; 155 | _data->blocks.push_front(block); 156 | 157 | // tsc <-> sys relation in this thread 158 | std::int64_t sys1 = get_systime(); 159 | _data->tsc_ticks = get_tsc() - __clock_base; 160 | std::int64_t sys2 = get_systime(); 161 | _data->sys_ns = (sys1+sys2)/2; 162 | } 163 | 164 | ~profiling_data() 165 | { 166 | // this is a good place to put any code that should be executed when thread finishes 167 | } 168 | 169 | record* get_next() 170 | { 171 | if (_data->counter < BLOCK_SIZE) 172 | { 173 | return _data->blocks.front() + _data->counter++; 174 | } 175 | else 176 | { 177 | // need to allocate another one 178 | record* block = new record[BLOCK_SIZE]; 179 | block[0].thread_id = __thread_id; 180 | block[0].time = get_tsc() - __clock_base; 181 | block[0].object_type = "profiler"; 182 | block[0].object_id = this; 183 | block[0].event = "new block"; 184 | 185 | _data->blocks.push_front(block); 186 | _data->counter = 2; 187 | return block + 1; 188 | } 189 | } 190 | 191 | private: 192 | per_thread_data* _data; 193 | }; 194 | 195 | thread_local profiling_data __profiling_data; 196 | 197 | void profiling_event(const char* object_type, void* object_id, const char* event, std::uint32_t ordinal, const char* data) 198 | { 199 | // this is hotpath! 200 | 201 | record* r = __profiling_data.get_next(); 202 | 203 | r->thread_id = __thread_id; 204 | r->time = get_tsc() - __clock_base; 205 | r->object_type = object_type; 206 | r->object_id = object_id; 207 | r->ordinal = ordinal; 208 | r->event = event; 209 | if (data) 210 | std::strncpy(r->data, data, DATA_SIZE-1); 211 | else 212 | r->data[0] = 0; 213 | } 214 | 215 | void dump() 216 | { 217 | __global_profiling_data.dump(); 218 | } 219 | 220 | // tick->ns converter 221 | class tsc_to_ns_functor 222 | { 223 | public: 224 | 225 | // sys_ns and tcs are clock values taken at the same time 226 | tsc_to_ns_functor(double ticks_per_ns, std::int64_t calib_sys_ns, std::int64_t calib_tcs) 227 | { 228 | _ticks_per_ns = ticks_per_ns; 229 | _ns_shift = calib_sys_ns - calib_tcs/ticks_per_ns; 230 | } 231 | 232 | // returns time in ns 233 | std::int64_t operator()(std::int64_t tcs) const 234 | { 235 | return tcs/_ticks_per_ns + _ns_shift; 236 | } 237 | 238 | private: 239 | double _ticks_per_ns; 240 | std::int64_t _ns_shift; 241 | }; 242 | 243 | void global_profiling_data::dump() 244 | { 245 | std::cerr << "dumping profiling data to... " << PROFILING_FILE_NAME << std::endl; 246 | try 247 | { 248 | std::ofstream file(PROFILING_FILE_NAME, std::ios_base::out | std::ios_base::trunc); 249 | 250 | unsigned counter = 0; 251 | 252 | // store the earliest clock calibration event 253 | std::int64_t min_sys_ns = _block_lists.back().sys_ns; 254 | 255 | for(per_thread_data& thread_data : _block_lists) 256 | { 257 | 258 | tsc_to_ns_functor tcs_to_ns(__ticks_per_ns, thread_data.sys_ns - min_sys_ns, thread_data.tsc_ticks); 259 | 260 | unsigned records = thread_data.counter; // only valid for the first block 261 | for(record* block : thread_data.blocks) 262 | { 263 | // write records 264 | for(unsigned i = 0; i < records; i++) 265 | { 266 | block[i].write(file, tcs_to_ns(block[i].time)); 267 | counter ++; 268 | } 269 | // all subsequent blocks are fully utilized 270 | records = BLOCK_SIZE; 271 | } 272 | } 273 | 274 | file.close(); 275 | 276 | std::cerr << "dumping profiling data completed, " << counter << " records written" << std::endl; 277 | } 278 | catch(const std::exception& e) 279 | { 280 | std::cerr << "Error dumping profiling data: " << e.what() << std::endl; 281 | } 282 | } 283 | 284 | 285 | double calibrate_clock() 286 | { 287 | static const unsigned CYCLES = 11; 288 | static const unsigned REPETITIONS = 1000000; 289 | 290 | std::vector mplyers; 291 | mplyers.reserve(CYCLES); 292 | 293 | 294 | for(unsigned i = 0; i < CYCLES; i++) 295 | { 296 | std::uint64_t tsc_start = get_tsc(); 297 | auto clock_start = std::chrono::high_resolution_clock::now(); 298 | 299 | for(;;) 300 | { 301 | for(unsigned j = 0; j < REPETITIONS; j++); 302 | auto now = std::chrono::high_resolution_clock::now(); 303 | if ((now - clock_start) > std::chrono::microseconds(1000)) 304 | break; 305 | } 306 | 307 | std::uint64_t tsc_end = get_tsc(); 308 | auto clock_end = std::chrono::high_resolution_clock::now(); 309 | 310 | std::uint64_t tsc_diff = tsc_end - tsc_start; 311 | std::uint64_t nano_diff = (clock_end-clock_start)/std::chrono::nanoseconds(1); 312 | double tick_per_nano = double(tsc_diff) / nano_diff; 313 | 314 | mplyers.push_back(tick_per_nano); 315 | } 316 | 317 | // return median 318 | std::sort(mplyers.begin(), mplyers.end()); 319 | return mplyers[std::ceil(CYCLES/2.0)]; 320 | } 321 | 322 | 323 | 324 | 325 | } 326 | 327 | -------------------------------------------------------------------------------- /profiling/profiling.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #ifndef PROFILING_PROFILING_HPP 3 | #define PROFILING_PROFILING_HPP 4 | 5 | #include 6 | #include 7 | 8 | namespace profiling { 9 | 10 | void profiling_event(const char* object_type, void* object_id, const char* event, std::uint32_t ordinal, const char* data); 11 | inline void profiling_event(const char* object_type, void* object_id, const char* event, const char* data = nullptr) 12 | { 13 | profiling_event(object_type, object_id, event, 0, data); 14 | } 15 | 16 | void dump(); 17 | 18 | } 19 | 20 | #ifdef COROUTINES_PROFILING 21 | #define CORO_PROF profiling::profiling_event 22 | #define CORO_PROF_DUMP profiling::dump 23 | #define CORO_PROF_DECLARE_COUNTER(name) static std::atomic __coro_prof_counter ## name; 24 | #define CORO_PROF_COUNTER(name) __coro_prof_counter ## name ++; 25 | #else 26 | #define CORO_PROF(...); 27 | #define CORO_PROF_DUMP(); 28 | #define CORO_PROF_DECLARE_COUNTER(name); 29 | #define CORO_PROF_COUNTER(name) 30 | #endif 31 | 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /profiling_analyzer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(${Boost_INCLUDE_DIRS}) 2 | 3 | add_executable(profiling_analyzer 4 | main.cpp 5 | ) 6 | 7 | 8 | target_link_libraries(profiling_analyzer 9 | 10 | profiling_reader 11 | ) 12 | -------------------------------------------------------------------------------- /profiling_analyzer/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #include "profiling_reader/reader.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | // holds process state 10 | struct processor_state 11 | { 12 | unsigned tasks_run = 0; 13 | double routine_started = 0; 14 | double time_in_coroutines = 0; 15 | 16 | double time_first_coro_start = 0; 17 | double time_last_coro_start = 0; 18 | double time_last_coro_end = 0; 19 | }; 20 | 21 | void analyze(const profiling_reader::reader& reader) 22 | { 23 | std::unordered_map processors; 24 | 25 | std::cout << "analyzing..." << std::endl; 26 | 27 | reader.for_each_by_time([&processors](const profiling_reader::record_type& record) 28 | { 29 | if (record.object_type == "processor" && record.event == "routine started") 30 | { 31 | processor_state& ps = processors[record.thread_id]; 32 | ps.routine_started = record.time_ns; 33 | } 34 | 35 | if (record.object_type == "coroutine") 36 | { 37 | processor_state& ps = processors[record.thread_id]; 38 | 39 | if (record.event == "enter") 40 | { 41 | if (ps.time_first_coro_start == 0) 42 | ps.time_first_coro_start = record.time_ns; 43 | ps.time_last_coro_start = record.time_ns; 44 | ps.tasks_run++; 45 | } 46 | else if (record.event == "exit") 47 | { 48 | ps.time_last_coro_end = record.time_ns; 49 | ps.time_in_coroutines += (record.time_ns - ps.time_last_coro_start); 50 | } 51 | } 52 | }); 53 | 54 | // display some info 55 | for(auto& p : processors) 56 | { 57 | const processor_state& ps = p.second; 58 | 59 | if (ps.routine_started == 0) 60 | continue; 61 | 62 | std::cout << "Process info" << std::endl; 63 | std::cout << " * thread id: " << p.first << std::endl; 64 | std::cout << " * tasks run: " << ps.tasks_run << std::endl; 65 | std::cout << " * routine started: " << ps.routine_started << std::endl; 66 | std::cout << " * time in coros: " << ps.time_in_coroutines << " ns" << std::endl; 67 | std::cout << " * time from first to last coro: " << (ps.time_last_coro_end - ps.time_first_coro_start) << " ns" << std::endl; 68 | std::cout << std::endl; 69 | } 70 | } 71 | 72 | int main(int argc, char** argv) 73 | { 74 | if (argc < 2) 75 | { 76 | std::cerr << "USAGE: profiling_analyze PROFILING_FILE" << std::endl; 77 | return 1; 78 | } 79 | 80 | try 81 | { 82 | profiling_reader::reader reader(argv[1]); 83 | 84 | analyze(reader); 85 | 86 | } 87 | catch(const std::exception& e) 88 | { 89 | std::cerr << "Error : " << e.what() << std::endl; 90 | return 2; 91 | } 92 | 93 | return 0; 94 | } 95 | 96 | -------------------------------------------------------------------------------- /profiling_gui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Qt5Widgets REQUIRED) 2 | 3 | include_directories(${Boost_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS}) 4 | 5 | set(CMAKE_AUTOMOC ON) 6 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 7 | 8 | qt5_wrap_ui(UI_FILES mainwindow.ui) 9 | 10 | add_executable(profiling_gui 11 | ${UI_FILES} 12 | 13 | main.cpp 14 | mainwindow.cpp mainwindow.hpp 15 | flowdiagram.cpp flowdiagram.hpp 16 | flowdiagram_items.cpp flowdiagram_items.hpp 17 | horizontalview.cpp horizontalview.hpp 18 | coroutinesmodel.cpp coroutinesmodel.hpp 19 | globals.cpp globals.hpp 20 | ) 21 | 22 | 23 | target_link_libraries(profiling_gui 24 | 25 | profiling_reader 26 | ) 27 | 28 | qt5_use_modules(profiling_gui Widgets) 29 | -------------------------------------------------------------------------------- /profiling_gui/coroutinesmodel.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #include "profiling_gui/coroutinesmodel.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace profiling_gui { 10 | 11 | CoroutinesModel::CoroutinesModel(QObject *parent) : 12 | QAbstractListModel(parent), 13 | 14 | _selectionModel(this) 15 | { 16 | connect(&_selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), SLOT(onSelectionChanged(QModelIndex))); 17 | } 18 | 19 | void CoroutinesModel::append(const CoroutinesModel::Record& record) 20 | { 21 | beginInsertRows(QModelIndex(), _records.size(), _records.size()); 22 | 23 | _records.append(record); 24 | 25 | endInsertRows(); 26 | } 27 | 28 | void CoroutinesModel::clear() 29 | { 30 | beginResetModel(); 31 | _records.clear(); 32 | endResetModel(); 33 | } 34 | 35 | QVariant CoroutinesModel::data(const QModelIndex& index, int role) const 36 | { 37 | // no nested data 38 | if (index.parent().isValid()) 39 | { 40 | return QVariant(); 41 | } 42 | 43 | const Record& record = _records[index.row()]; 44 | 45 | if (index.column() == 0) 46 | { 47 | if (role == Qt::DisplayRole) 48 | return record.name; 49 | else if (role == Qt::DecorationRole) 50 | return iconFromColor(record.color); 51 | } 52 | if (index.column() == 1 && role == Qt::DisplayRole) 53 | { 54 | return nanosToString(record.timeExecuted); 55 | } 56 | 57 | return QVariant(); 58 | } 59 | 60 | void CoroutinesModel::sort(int column, Qt::SortOrder order) 61 | { 62 | emit layoutAboutToBeChanged(QList(), VerticalSortHint); 63 | 64 | if (column == 0) 65 | { 66 | qStableSort(_records.begin(), _records.end(), [order](const Record& a, const Record& b) 67 | { 68 | if (order == Qt::AscendingOrder) 69 | return a.name > b.name; 70 | else 71 | return a.name < b.name; 72 | }); 73 | } 74 | else if (column == 1) 75 | { 76 | qStableSort(_records.begin(), _records.end(), [order](const Record& a, const Record& b) 77 | { 78 | if (order == Qt::AscendingOrder) 79 | return a.timeExecuted > b.timeExecuted; 80 | else 81 | return a.timeExecuted < b.timeExecuted; 82 | }); 83 | } 84 | 85 | emit layoutChanged(QList(), VerticalSortHint); 86 | } 87 | 88 | QVariant CoroutinesModel::headerData(int section, Qt::Orientation orientation, int role) const 89 | { 90 | if (orientation == Qt::Horizontal && role == Qt::DisplayRole) 91 | { 92 | switch(section) 93 | { 94 | case 0: 95 | return "Name"; 96 | case 1: 97 | return "Time"; 98 | default: 99 | ; 100 | } 101 | } 102 | 103 | return QVariant(); 104 | } 105 | 106 | void CoroutinesModel::onCoroutineSelected(quintptr id) 107 | { 108 | auto it = std::find_if(_records.begin(), _records.end(), [id](const Record& r) { return r.id == id; }); 109 | int row = std::distance(_records.begin(), it); 110 | 111 | blockSignals(true); 112 | _selectionModel.setCurrentIndex(index(row), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); 113 | blockSignals(false); 114 | } 115 | 116 | void CoroutinesModel::onSelectionChanged(QModelIndex index) 117 | { 118 | emit coroSelected(_records[index.row()].id); 119 | } 120 | 121 | QPixmap CoroutinesModel::iconFromColor(QColor color) 122 | { 123 | static const int ICON_SIZE = 16; 124 | QPixmap icon(ICON_SIZE, ICON_SIZE); 125 | icon.fill(Qt::transparent); 126 | QPainter painter(&icon); 127 | 128 | painter.setPen(QPen(Qt::black, 1)); 129 | painter.setBrush(color); 130 | painter.drawRect(1, 1, ICON_SIZE-2, ICON_SIZE-2); 131 | 132 | return icon; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /profiling_gui/coroutinesmodel.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #ifndef PROFILING_GUI_COROUTINES_MODEL_HPP 4 | #define PROFILING_GUI_COROUTINES_MODEL_HPP 5 | 6 | #include "profiling_gui/globals.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace profiling_gui { 13 | 14 | class CoroutinesModel : public QAbstractListModel 15 | { 16 | Q_OBJECT 17 | public: 18 | explicit CoroutinesModel(QObject *parent = 0); 19 | 20 | struct Record 21 | { 22 | quintptr id; 23 | QString name; 24 | QColor color; 25 | double timeExecuted; 26 | }; 27 | 28 | void append(const Record& record); 29 | void clear(); 30 | 31 | virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override 32 | { 33 | if (!parent.isValid()) 34 | return _records.size(); 35 | else 36 | return 0; 37 | } 38 | 39 | virtual int columnCount(const QModelIndex &parent) const override 40 | { 41 | if (!parent.isValid()) 42 | return 2; 43 | else 44 | return 0; 45 | } 46 | 47 | virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; 48 | virtual void sort(int column, Qt::SortOrder order) override; 49 | virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; 50 | 51 | QItemSelectionModel* selectionModel() { return &_selectionModel; } 52 | 53 | public slots: 54 | 55 | void onCoroutineSelected(quintptr id); 56 | 57 | signals: 58 | 59 | void coroSelected(quintptr id); 60 | 61 | private slots: 62 | 63 | void onSelectionChanged(QModelIndex index); 64 | 65 | private: 66 | 67 | static QPixmap iconFromColor(QColor color); 68 | 69 | QVector _records; 70 | 71 | QItemSelectionModel _selectionModel; 72 | }; 73 | 74 | } 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /profiling_gui/flowdiagram.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #ifndef PROFILING_GUI_FLOWDIAGRAM_HPP 4 | #define PROFILING_GUI_FLOWDIAGRAM_HPP 5 | 6 | #include "profiling_gui/coroutinesmodel.hpp" 7 | 8 | #include "profiling_reader/reader.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | namespace profiling_gui { 18 | 19 | // Flow diagram builder 20 | class FlowDiagram : public QObject 21 | { 22 | Q_OBJECT 23 | public: 24 | 25 | explicit FlowDiagram(QObject *parent = nullptr); 26 | 27 | void loadFile(const QString& path, QGraphicsScene* scene, CoroutinesModel& coroutinesModel); 28 | 29 | private: 30 | 31 | struct ThreadData 32 | { 33 | double y; 34 | std::uint64_t minTime; 35 | std::uint64_t maxTime; 36 | std::uint64_t lastBlock = 0; 37 | }; 38 | 39 | struct CoroutineData 40 | { 41 | QString name; 42 | QColor color; 43 | 44 | QMap enters; 45 | QPointF lastEvent; 46 | QList items; 47 | double totalTime = 0; 48 | }; 49 | 50 | struct SpinlockData 51 | { 52 | QString name; 53 | QColor color; 54 | 55 | // per thread 56 | QHash lastSpinningBeginTime; 57 | QHash lastLockedTime; 58 | }; 59 | 60 | // first param is time in ns adjusted 61 | void onRecord(const profiling_reader::record_type& record); // master record dipatcher 62 | 63 | void onCoroutineRecord(const profiling_reader::record_type& record, const ThreadData& thread); 64 | void onProcessorRecord(const profiling_reader::record_type& record, ThreadData& thread); 65 | void onSpinlockRecord(const profiling_reader::record_type& record, ThreadData& thread); 66 | void onMonitorRecord(const profiling_reader::record_type& record, ThreadData& thread); 67 | 68 | QColor randomColor(int baseV = 172); 69 | 70 | QGraphicsScene* _scene; 71 | QMap _threads; 72 | QMap _coroutines; 73 | QMap< std::uintptr_t, SpinlockData> _spinlocks; 74 | 75 | std::minstd_rand0 _random_generator; 76 | }; 77 | 78 | } 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /profiling_gui/flowdiagram_items.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #include "profiling_gui/flowdiagram_items.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace profiling_gui { 11 | 12 | SelectableRectangle::SelectableRectangle(double l, double t, double w, double h) : QGraphicsRectItem(l, t, w, h) 13 | { 14 | setZValue(1.0); 15 | setFlag(ItemIsSelectable); 16 | } 17 | 18 | void SelectableRectangle::paint(QPainter* painter, const QStyleOptionGraphicsItem * option, QWidget * widget) 19 | { 20 | QPen p(Qt::black); 21 | p.setCosmetic(true); 22 | if (isSelected()) 23 | { 24 | p.setWidth(3); 25 | } 26 | 27 | painter->setPen(p); 28 | painter->setBrush(brush()); 29 | 30 | painter->drawRect(boundingRect()); 31 | 32 | } 33 | 34 | 35 | void SelectableRectangle::mousePressEvent(QGraphicsSceneMouseEvent* event) 36 | { 37 | if (parentItem()) 38 | { 39 | if (!isSelected()) 40 | { 41 | scene()->clearSelection(); 42 | parentItem()->setSelected(true); 43 | } 44 | } 45 | else 46 | { 47 | event->ignore(); 48 | } 49 | } 50 | 51 | void SelectableRectangle::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) 52 | { 53 | event->ignore(); 54 | } 55 | 56 | SelectableLine::SelectableLine(double x1, double y1, double x2, double y2) : QGraphicsLineItem(x1, y1, x2, y2) 57 | { 58 | setZValue(0.0); 59 | setAcceptedMouseButtons(0); 60 | setFlag(ItemIsSelectable); 61 | } 62 | 63 | void SelectableLine::paint(QPainter* painter, const QStyleOptionGraphicsItem * option, QWidget * widget) 64 | { 65 | QPen p; 66 | QColor c = pen().color(); 67 | p.setCosmetic(true); 68 | if (isSelected()) 69 | { 70 | c.setAlpha(176); 71 | p.setWidth(2); 72 | } 73 | else 74 | { 75 | p.setWidth(1); 76 | c.setAlpha(128); 77 | } 78 | p.setColor(c); 79 | 80 | painter->setPen(p); 81 | 82 | painter->drawLine(line()); 83 | } 84 | 85 | CoroutineGroup::CoroutineGroup(quintptr id, QGraphicsItem* parent) 86 | : QGraphicsObject(parent) 87 | , _id(id) 88 | { 89 | setFlag(ItemIsSelectable); 90 | } 91 | 92 | void CoroutineGroup::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) 93 | { 94 | // invisible 95 | } 96 | 97 | QRectF CoroutineGroup::boundingRect() const 98 | { 99 | return QRectF(); 100 | } 101 | 102 | void CoroutineGroup::onCoroutineSelected(quintptr id) 103 | { 104 | if (id == _id) 105 | { 106 | scene()->clearSelection(); 107 | blockSignals(true); 108 | setSelected(true); 109 | blockSignals(false); 110 | } 111 | } 112 | 113 | QVariant CoroutineGroup::itemChange(GraphicsItemChange change, const QVariant& value) 114 | { 115 | if (change == ItemSelectedChange) 116 | { 117 | if (value.toBool()) 118 | { 119 | emit coroSelected(_id); 120 | for(QGraphicsItem* item : childItems()) 121 | { 122 | item->setSelected(true); 123 | } 124 | } 125 | return value; 126 | } 127 | 128 | return QGraphicsObject::itemChange(change, value); 129 | } 130 | 131 | SelectableSymbol::SelectableSymbol(const QPointF& pos, SHAPE shape, const QColor color, double size) 132 | : _pos(pos) 133 | , _color(color) 134 | , _shape(shape) 135 | , _size(size) 136 | { 137 | setFlag(ItemIsSelectable); 138 | setFlag(ItemIgnoresTransformations); 139 | setPos(pos); 140 | 141 | QTransform t; 142 | t.scale(_size/2, _size/2); 143 | setTransform(t); 144 | } 145 | 146 | void SelectableSymbol::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) 147 | { 148 | QPen p(Qt::black); 149 | p.setCosmetic(true); 150 | if (isSelected()) 151 | { 152 | p.setWidth(2); 153 | } 154 | 155 | painter->setPen(p); 156 | painter->setBrush(_color); 157 | 158 | switch (_shape) { 159 | case SHAPE_CIRCLE: 160 | painter->drawEllipse(QPointF(0, 0), 1.0, 1.0); 161 | break; 162 | 163 | case SHAPE_TRIANGLE_LEFT: 164 | painter->drawPolygon(QPolygonF() << QPointF(0.5, 0.86) << QPointF(-1.0, 0) << QPointF(0.5, -0.86)); 165 | break; 166 | 167 | case SHAPE_TRIANGLE_RIGHT: 168 | painter->drawPolygon(QPolygonF() << QPointF(-0.5, 0.86) << QPointF(1.0, 0) << QPointF(-0.5, -0.86)); 169 | break; 170 | 171 | default: 172 | ; 173 | } 174 | } 175 | 176 | QRectF SelectableSymbol::boundingRect() const 177 | { 178 | return QRectF(-1, -1, 2, 2); 179 | } 180 | 181 | 182 | } 183 | -------------------------------------------------------------------------------- /profiling_gui/flowdiagram_items.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #ifndef PROFILING_GUI_FLOWDIAGRAM_ITEMS_HPP 4 | #define PROFILING_GUI_FLOWDIAGRAM_ITEMS_HPP 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace profiling_gui { 11 | 12 | // set of custom grpahics items used on flow diagram 13 | 14 | // selectable rectagular element 15 | class SelectableRectangle : public QGraphicsRectItem 16 | { 17 | public: 18 | 19 | SelectableRectangle(double l, double t, double w, double h); 20 | 21 | virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override; 22 | 23 | protected: 24 | 25 | virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) override; 26 | virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override; 27 | }; 28 | 29 | // simple symbol 30 | class SelectableSymbol : public QGraphicsItem 31 | { 32 | public: 33 | 34 | enum SHAPE 35 | { 36 | SHAPE_CIRCLE, 37 | SHAPE_TRIANGLE_LEFT, 38 | SHAPE_TRIANGLE_RIGHT, 39 | }; 40 | 41 | SelectableSymbol(const QPointF& pos, SHAPE shape, const QColor color, double size); 42 | 43 | virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override; 44 | virtual QRectF boundingRect() const override; 45 | 46 | private: 47 | 48 | QPointF _pos; 49 | QColor _color; 50 | SHAPE _shape; 51 | double _size; 52 | }; 53 | 54 | 55 | // non-clickable, selctable line 56 | class SelectableLine : public QGraphicsLineItem 57 | { 58 | public: 59 | SelectableLine(double x1, double y1, double x2, double y2); 60 | 61 | virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem * option, QWidget * widget = nullptr) override; 62 | }; 63 | 64 | // invisible item grouping all items belonging to one coroutine 65 | class CoroutineGroup : public QGraphicsObject 66 | { 67 | Q_OBJECT 68 | public: 69 | 70 | CoroutineGroup(quintptr id, QGraphicsItem* parent = nullptr); 71 | 72 | virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem * option, QWidget * widget) override; 73 | virtual QRectF boundingRect() const override; 74 | 75 | public slots: 76 | 77 | void onCoroutineSelected(quintptr id); 78 | 79 | signals: 80 | 81 | void coroSelected(quintptr id); 82 | 83 | protected: 84 | 85 | virtual QVariant itemChange(GraphicsItemChange change, const QVariant& value) override; 86 | 87 | private: 88 | 89 | quintptr _id; 90 | }; 91 | 92 | } 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /profiling_gui/globals.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #include "profiling_gui/globals.hpp" 4 | 5 | namespace profiling_gui { 6 | 7 | QString nanosToString(double ns) 8 | { 9 | static const char* suffixes[] = { "ns", "µs", "ms", "s" }; 10 | 11 | double value = ns; 12 | auto it = std::begin(suffixes); 13 | for(; it != std::end(suffixes)-1 && value > 1000.0; it++) 14 | value /= 1000; 15 | 16 | return QString::number(value, 'f', 2) + *it; 17 | } 18 | 19 | 20 | } // namespace profiling_gui 21 | -------------------------------------------------------------------------------- /profiling_gui/globals.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #ifndef PROFILING_GUI_GLOBALS_HPP 4 | #define PROFILING_GUI_GLOBALS_HPP 5 | 6 | #include 7 | 8 | namespace profiling_gui { 9 | 10 | QString nanosToString(double ns); 11 | 12 | } 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /profiling_gui/horizontalview.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #include "profiling_gui/horizontalview.hpp" 4 | 5 | #include 6 | #include 7 | 8 | namespace profiling_gui { 9 | 10 | static const int DRAG_THRESHOLD = 10; // in px 11 | 12 | HorizontalView::HorizontalView(QWidget *parent) : 13 | QGraphicsView(parent) 14 | { 15 | setRenderHint(QPainter::Antialiasing); 16 | setCursor(Qt::CrossCursor); 17 | } 18 | 19 | void HorizontalView::showAll() 20 | { 21 | QGraphicsScene* s = scene(); 22 | if (s) 23 | { 24 | QRectF sceneRect = s->sceneRect(); 25 | _viewStart = sceneRect.left(); 26 | _viewEnd = sceneRect.right(); 27 | updateTransformation(); 28 | } 29 | } 30 | 31 | void HorizontalView::paintEvent(QPaintEvent* event) 32 | { 33 | QGraphicsView::paintEvent(event); 34 | 35 | if (_dragging) 36 | { 37 | QPainter painter(viewport()); 38 | 39 | QRectF r(_dragStart, 0, _dragEnd-_dragStart, height()); 40 | 41 | QPen p(Qt::blue, 1); 42 | p.setCosmetic(true); 43 | QColor c(Qt::cyan); 44 | c.setAlpha(32); 45 | 46 | painter.setPen(Qt::NoPen); 47 | painter.setBrush(c); 48 | painter.drawRect(r); 49 | 50 | QLine l1(_dragStart, 0, _dragStart, height()); 51 | QLine l2(_dragEnd, 0, _dragEnd, height()); 52 | 53 | painter.setPen(Qt::black); 54 | painter.drawLine(l1); 55 | painter.drawLine(l2); 56 | } 57 | } 58 | 59 | void HorizontalView::resizeEvent(QResizeEvent* event) 60 | { 61 | updateTransformation(); 62 | QGraphicsView::resizeEvent(event); 63 | } 64 | 65 | void HorizontalView::wheelEvent(QWheelEvent* event) 66 | { 67 | int d = event->angleDelta().y(); 68 | double mousePos = mapToScene(event->pos()).x(); 69 | double viewStart = mapToScene(QPoint(0, 0)).x(); 70 | double viewEnd = mapToScene(QPoint(width(), 0)).x(); 71 | 72 | if (d > 0) 73 | { 74 | // zoom in 75 | _viewStart = viewStart + (mousePos-viewStart)/3.0; 76 | _viewEnd = viewEnd - (viewEnd-mousePos)/3.0; 77 | updateTransformation(); 78 | } 79 | else if (d < 0) 80 | { 81 | // zoom out 82 | QRectF sceneRect = scene()->sceneRect(); 83 | _viewStart = qMax(viewStart- (mousePos-viewStart)/2.0, sceneRect.left()); 84 | _viewEnd = qMin(viewEnd + (viewEnd-mousePos)/3.0, sceneRect.right()); 85 | updateTransformation(); 86 | } 87 | } 88 | 89 | void HorizontalView::mousePressEvent(QMouseEvent* event) 90 | { 91 | QGraphicsView::mousePressEvent(event); 92 | 93 | if (event->button() == Qt::LeftButton && !event->isAccepted()) 94 | { 95 | _dragging = true; 96 | _dragStart = event->pos().x(); 97 | _dragEnd = _dragStart; 98 | } 99 | } 100 | 101 | void HorizontalView::mouseReleaseEvent(QMouseEvent* event) 102 | { 103 | if (_dragging) 104 | { 105 | _dragging = false; 106 | viewport()->update(); 107 | emit rangeHighlighted(0); 108 | } 109 | else 110 | { 111 | QGraphicsView::mouseReleaseEvent(event); 112 | } 113 | } 114 | 115 | void HorizontalView::mouseMoveEvent(QMouseEvent* event) 116 | { 117 | if (_dragging) 118 | { 119 | if (qAbs(_dragEnd - _dragStart) > 5) 120 | { 121 | viewport()->update(); 122 | 123 | QPointF startPoint = mapToScene(_dragStart, 0); 124 | QPointF endPoint = mapToScene(_dragEnd, 0); 125 | 126 | unsigned ns = qAbs(endPoint.x() - startPoint.x()); 127 | emit rangeHighlighted(ns); 128 | } 129 | _dragEnd = event->pos().x(); 130 | } 131 | } 132 | 133 | void HorizontalView::updateTransformation() 134 | { 135 | QRectF sceneRect = scene()->sceneRect(); 136 | QRectF visibleRect(_viewStart, sceneRect.top(), _viewEnd - _viewStart, sceneRect.height()); 137 | fitInView(visibleRect); 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /profiling_gui/horizontalview.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #ifndef PROFILING_GUI_HORIZONTALVIEW_HPP 4 | #define PROFILING_GUI_HORIZONTALVIEW_HPP 5 | 6 | #include 7 | 8 | namespace profiling_gui { 9 | 10 | class HorizontalView : public QGraphicsView 11 | { 12 | Q_OBJECT 13 | public: 14 | explicit HorizontalView(QWidget *parent = 0); 15 | 16 | public slots: 17 | 18 | void showAll(); 19 | 20 | signals: 21 | 22 | void rangeHighlighted(unsigned ns); 23 | 24 | protected: 25 | 26 | virtual void paintEvent(QPaintEvent *event) override; 27 | 28 | virtual void resizeEvent(QResizeEvent* event) override; 29 | virtual void wheelEvent(QWheelEvent* event) override; 30 | 31 | virtual void mousePressEvent(QMouseEvent* event) override; 32 | virtual void mouseReleaseEvent(QMouseEvent* event) override; 33 | virtual void mouseMoveEvent(QMouseEvent* event) override; 34 | 35 | private: 36 | 37 | void updateTransformation(); 38 | 39 | double _viewStart; 40 | double _viewEnd; 41 | 42 | bool _dragging = false; 43 | int _dragStart; 44 | int _dragEnd; 45 | 46 | }; 47 | 48 | } // namespace profiling_gui 49 | 50 | #endif // PROFILING_GUI_HORIZONTALVIEW_HPP 51 | -------------------------------------------------------------------------------- /profiling_gui/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "profiling_gui/mainwindow.hpp" 4 | 5 | int main(int argc, char** argv) 6 | { 7 | QApplication app(argc, argv); 8 | 9 | auto window = new profiling_gui::MainWindow(); 10 | window->show(); 11 | 12 | if (argc > 1) 13 | { 14 | QString path = argv[1]; 15 | window->loadFile(path); 16 | } 17 | 18 | return app.exec(); 19 | } 20 | -------------------------------------------------------------------------------- /profiling_gui/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #include "profiling_gui/mainwindow.hpp" 4 | #include "profiling_gui/flowdiagram.hpp" 5 | #include "profiling_gui/globals.hpp" 6 | 7 | #include "ui_mainwindow.h" 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | namespace profiling_gui { 15 | 16 | static const int MAX_MRD = 5; 17 | 18 | MainWindow::MainWindow(QWidget *parent) 19 | : 20 | QMainWindow(parent), 21 | _ui(new Ui::MainWindow) 22 | { 23 | _ui->setupUi(this); 24 | _ui->mainView->setScene(&_scene); 25 | 26 | _ui->coroutineView->setModel(&_coroutinesModel); 27 | _ui->coroutineView->setSelectionModel(_coroutinesModel.selectionModel()); 28 | 29 | connect(_ui->actionExit, SIGNAL(triggered()), QApplication::instance(), SLOT(quit())); 30 | // connect(_ui->actionOpen, SIGNAL(triggered()), SLOT(openFileDialog())); 31 | 32 | connect(_ui->mainView, SIGNAL(rangeHighlighted(uint)), SLOT(timeRangeHighlighted(uint))); 33 | 34 | loadSettings(); 35 | } 36 | 37 | MainWindow::~MainWindow() 38 | { 39 | delete _ui; 40 | } 41 | 42 | void MainWindow::loadFile(const QString& path) 43 | { 44 | FlowDiagram builder; 45 | _scene.clear(); 46 | _coroutinesModel.clear(); 47 | builder.loadFile(path, &_scene, _coroutinesModel); 48 | 49 | _ui->mainView->showAll(); 50 | _ui->coroutineView->resizeColumnToContents(0); 51 | 52 | // after successful load, add to mrd 53 | QString absolutePath; 54 | if (QDir::isAbsolutePath(path)) 55 | absolutePath = path; 56 | else 57 | absolutePath = QDir::current().absoluteFilePath(QDir::cleanPath(path)); 58 | _mrd.removeAll(absolutePath); 59 | _mrd.push_front(absolutePath); 60 | while (_mrd.size() > MAX_MRD) 61 | _mrd.pop_back(); 62 | setMrd(_mrd); 63 | } 64 | 65 | void MainWindow::on_actionOpen_triggered() 66 | { 67 | QString fileName = QFileDialog::getOpenFileName(this, "Open profiling dump file"); 68 | if (!fileName.isNull()) 69 | { 70 | loadFile(fileName); 71 | } 72 | } 73 | 74 | void MainWindow::timeRangeHighlighted(unsigned ns) 75 | { 76 | if (ns > 0) 77 | statusBar()->showMessage(QString("range: %1").arg(nanosToString(ns))); 78 | else 79 | statusBar()->clearMessage(); 80 | } 81 | 82 | void MainWindow::closeEvent(QCloseEvent *event) 83 | { 84 | QMainWindow::closeEvent(event); 85 | writeSettings(); 86 | } 87 | 88 | void MainWindow::loadSettings() 89 | { 90 | QSettings settings("coroutines", "profiling_gui"); 91 | 92 | // mrd list 93 | _mrd = settings.value("mrd").toStringList(); 94 | setMrd(_mrd); 95 | } 96 | 97 | void MainWindow::writeSettings() 98 | { 99 | QSettings settings("coroutines", "profiling_gui"); 100 | 101 | settings.setValue("mrd", _mrd); 102 | } 103 | 104 | void MainWindow::setMrd(const QStringList& mrd) 105 | { 106 | // clear previous actions 107 | for(QAction* a : _mrdActions) 108 | { 109 | delete a; 110 | } 111 | _mrdActions.clear(); 112 | 113 | if (!mrd.empty()) 114 | { 115 | _mrdActions.append(_ui->menuFile->insertSeparator(_ui->actionExit)); 116 | 117 | for(const QString& file : mrd) 118 | { 119 | QAction* action = new QAction(file, _ui->menuFile); 120 | _ui->menuFile->insertAction(_ui->actionExit, action); 121 | 122 | connect(action, &QAction::triggered, [this, file]() { this->loadFile(file); }); 123 | _mrdActions.append(action); 124 | } 125 | 126 | _mrdActions.append(_ui->menuFile->insertSeparator(_ui->actionExit)); 127 | } 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /profiling_gui/mainwindow.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #ifndef PROFILING_GUI_MAINWINDOW_HPP 4 | #define PROFILING_GUI_MAINWINDOW_HPP 5 | 6 | #include "profiling_gui/coroutinesmodel.hpp" 7 | 8 | #include 9 | #include 10 | 11 | namespace profiling_gui { 12 | 13 | namespace Ui { 14 | class MainWindow; 15 | } 16 | 17 | class MainWindow : public QMainWindow 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit MainWindow(QWidget *parent = nullptr); 23 | ~MainWindow(); 24 | 25 | public slots: 26 | 27 | void loadFile(const QString& path); 28 | 29 | private slots: 30 | 31 | void on_actionOpen_triggered(); 32 | void timeRangeHighlighted(unsigned ns); 33 | 34 | protected: 35 | 36 | virtual void closeEvent(QCloseEvent* event) override; 37 | 38 | private: 39 | 40 | void loadSettings(); 41 | void writeSettings(); 42 | 43 | // adds most-recent-document list to menu 44 | void setMrd(const QStringList& mrd); 45 | 46 | Ui::MainWindow *_ui; 47 | QGraphicsScene _scene; 48 | 49 | CoroutinesModel _coroutinesModel; 50 | 51 | QStringList _mrd; 52 | QList _mrdActions; 53 | }; 54 | 55 | } 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /profiling_gui/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | profiling_gui::MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 887 10 | 595 11 | 12 | 13 | 14 | Profiling visualizer 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 0 27 | 0 28 | 887 29 | 22 30 | 31 | 32 | 33 | 34 | File 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | toolBar 46 | 47 | 48 | TopToolBarArea 49 | 50 | 51 | false 52 | 53 | 54 | 55 | 56 | 2 57 | 58 | 59 | 60 | 61 | 62 | 63 | true 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Open 73 | 74 | 75 | Open prifiling data 76 | 77 | 78 | Ctrl+O 79 | 80 | 81 | 82 | 83 | Exit 84 | 85 | 86 | Ctrl+X 87 | 88 | 89 | 90 | 91 | 92 | profiling_gui::HorizontalView 93 | QGraphicsView 94 |
profiling_gui/horizontalview.hpp
95 |
96 |
97 | 98 | 99 |
100 | -------------------------------------------------------------------------------- /profiling_reader/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(${Boost_INCLUDE_DIRS}) 2 | 3 | add_library(profiling_reader STATIC 4 | reader.cpp reader.hpp 5 | ) 6 | -------------------------------------------------------------------------------- /profiling_reader/reader.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #include "profiling_reader/reader.hpp" 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace profiling_reader { 9 | 10 | template 11 | void read_until(std::ifstream& file, T& o, char delim) 12 | { 13 | static const unsigned bufsize = 128; 14 | char buf[bufsize]; 15 | file.getline(buf, bufsize, delim); 16 | o = boost::lexical_cast(buf); 17 | } 18 | 19 | reader::reader(const std::string& file_name) 20 | { 21 | std::ifstream file(file_name); 22 | file.exceptions(std::ios_base::eofbit); 23 | 24 | try 25 | { 26 | while(!file.eof()) 27 | { 28 | record_type record; 29 | 30 | read_until(file, record.time_ns, ','); 31 | read_until(file, record.ticks, ','); 32 | read_until(file, record.thread_id, ','); 33 | read_until(file, record.object_type, ','); 34 | read_until(file, record.object_id, ','); 35 | read_until(file, record.ordinal, ','); 36 | read_until(file, record.event, ','); 37 | read_until(file, record.data, '\n'); 38 | 39 | _by_time.insert(std::make_pair(record.time_ns, record)); 40 | } 41 | } 42 | catch(const std::ios_base::failure&) 43 | { 44 | } 45 | } 46 | 47 | } // namespace profiling 48 | -------------------------------------------------------------------------------- /profiling_reader/reader.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #ifndef PROFILING_READER_HPP 4 | #define PROFILING_READER_HPP 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace profiling_reader { 13 | 14 | struct record_type 15 | { 16 | std::int64_t time_ns; 17 | std::int64_t ticks; 18 | std::size_t thread_id; 19 | std::string object_type; 20 | std::uintptr_t object_id; 21 | std::uint32_t ordinal; 22 | std::string event; 23 | std::string data; 24 | }; 25 | 26 | class reader 27 | { 28 | public: 29 | reader(const std::string& file_name); 30 | 31 | // visits all records in chronological order 32 | template 33 | void for_each_by_time(Callable c) const 34 | { 35 | for(auto& p : _by_time) 36 | { 37 | c(p.second); 38 | } 39 | } 40 | 41 | private: 42 | 43 | // index by time. key is time_ns 44 | std::multimap _by_time; 45 | }; 46 | 47 | } // namespace profiling 48 | 49 | #endif // PROFILING_READER_HPP 50 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package( Boost 1.54.0 COMPONENTS unit_test_framework) 2 | 3 | add_definitions(-DBOOST_TEST_DYN_LINK) 4 | 5 | add_executable(test 6 | main.cpp 7 | fixtures.cpp fixtures.hpp 8 | 9 | generator_tests.cpp generator_tests.hpp 10 | 11 | channel_tests.cpp 12 | scheduler_tests.cpp 13 | mutex_tests.cpp 14 | ) 15 | 16 | target_link_libraries(test 17 | ${Boost_LIBRARIES} 18 | 19 | coroutines 20 | ) 21 | -------------------------------------------------------------------------------- /test/channel_tests.cpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #include "coroutines/globals.hpp" 3 | 4 | #include "test/fixtures.hpp" 5 | 6 | #include 7 | 8 | namespace coroutines { namespace tests { 9 | 10 | // simple reader-wrtier test, wrtier closes before reader finishes 11 | // expected: reader will read all data, and then throw channel_closed 12 | BOOST_FIXTURE_TEST_CASE(test_reading_after_close, fixture) 13 | { 14 | 15 | // create channel 16 | channel_pair pair = make_channel(10); 17 | 18 | int last_written = -1; 19 | int last_read = -1; 20 | 21 | // writer coroutine 22 | go(std::string("reading_after_close writer"), [&last_written](channel_writer& writer) 23 | { 24 | for(int i = 0; i < 5; i++) 25 | { 26 | writer.put(i); 27 | last_written = i; 28 | } 29 | }, std::move(pair.writer)); 30 | 31 | // reader coroutine 32 | go(std::string("reading_after_close reader"), [&last_read](channel_reader& reader) 33 | { 34 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 35 | for(;;) 36 | { 37 | try 38 | { 39 | last_read = reader.get(); 40 | } 41 | catch(const channel_closed&) 42 | { 43 | break; 44 | } 45 | } 46 | }, std::move(pair.reader)); 47 | 48 | wait_for_completion(); 49 | 50 | BOOST_CHECK_EQUAL(last_written, 4); 51 | BOOST_CHECK_EQUAL(last_read, 4); 52 | } 53 | 54 | // Reader blocking: reader should block until wrtier writes 55 | BOOST_FIXTURE_TEST_CASE(test_reader_blocking, fixture) 56 | { 57 | // create channel 58 | channel_pair pair = make_channel(10); 59 | 60 | bool reader_finished = false; 61 | go(std::string("test_reader_blocking reader"), [&reader_finished](channel_reader& r) 62 | { 63 | r.get(); 64 | reader_finished = true; 65 | }, std::move(pair.reader)); 66 | 67 | BOOST_CHECK_EQUAL(reader_finished, false); 68 | 69 | bool writer_finished = false; 70 | go(std::string("test_reader_blocking writer"), [&writer_finished](channel_writer& w) 71 | { 72 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 73 | w.put(7); 74 | writer_finished = true; 75 | 76 | }, std::move(pair.writer)); 77 | 78 | wait_for_completion(); 79 | 80 | BOOST_CHECK_EQUAL(reader_finished, true); 81 | BOOST_CHECK_EQUAL(writer_finished, true); 82 | } 83 | 84 | // test - writer.put() should exit with exception if reader closes channel 85 | BOOST_FIXTURE_TEST_CASE(test_writer_exit_when_closed, fixture) 86 | { 87 | // create channel 88 | channel_pair pair = make_channel(1); 89 | 90 | 91 | go(std::string("test_writer_exit_when_closed reader"), [](channel_reader& r) 92 | { 93 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 94 | // do nothing, close the chanel on exit 95 | }, std::move(pair.reader)); 96 | 97 | bool writer_threw = false; 98 | go(std::string("test_writer_exit_when_closed writer"), [&writer_threw](channel_writer& w) 99 | { 100 | try 101 | { 102 | w.put(1); 103 | w.put(2); // this will block 104 | } 105 | catch(const channel_closed&) 106 | { 107 | writer_threw = true; 108 | } 109 | 110 | }, std::move(pair.writer)); 111 | 112 | wait_for_completion(); 113 | 114 | BOOST_CHECK_EQUAL(writer_threw, true); 115 | } 116 | 117 | // send more items than channels capacity 118 | BOOST_FIXTURE_TEST_CASE(test_large_transfer, fixture) 119 | { 120 | // create channel 121 | channel_pair pair = make_channel(10); 122 | 123 | int last_written = -1; 124 | int last_read = -1; 125 | static const int MESSAGES = 10000; 126 | 127 | // writer coroutine 128 | go(std::string("large_transfer writer"), [&last_written](channel_writer& writer) 129 | { 130 | for(int i = 0; i < MESSAGES; i++) 131 | { 132 | writer.put(i); 133 | last_written = i; 134 | if ((i % 37) == 0) 135 | { 136 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 137 | BOOST_TEST_MESSAGE("long write progress: " << i << "/" << MESSAGES); 138 | } 139 | } 140 | }, std::move(pair.writer)); 141 | 142 | // reader coroutine 143 | go(std::string("large_transfer reader"), [&last_read](channel_reader& reader) 144 | { 145 | for(int i = 0;;i++) 146 | { 147 | try 148 | { 149 | last_read = reader.get(); 150 | if ((i % 53) == 0) 151 | { 152 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 153 | BOOST_TEST_MESSAGE("long read progress: " << i << "/" << MESSAGES); 154 | } 155 | } 156 | catch(const channel_closed&) 157 | { 158 | break; 159 | } 160 | } 161 | }, std::move(pair.reader)); 162 | 163 | wait_for_completion(); 164 | 165 | BOOST_CHECK_EQUAL(last_written, MESSAGES-1); 166 | BOOST_CHECK_EQUAL(last_read, MESSAGES-1); 167 | } 168 | 169 | BOOST_FIXTURE_TEST_CASE(test_multiple_readers, fixture) 170 | { 171 | static const int MSGS = 10000; 172 | static const int READERS = 100; 173 | std::atomic received(0); 174 | std::atomic closed(0); 175 | 176 | channel_pair pair = make_channel(10); 177 | 178 | go("test_multiple_readers writer", [](channel_writer& writer) 179 | { 180 | for(int i = 0; i < MSGS; i++) 181 | { 182 | writer.put(i); 183 | } 184 | }, std::move(pair.writer)); 185 | 186 | for(int i = 0; i < READERS; i++) 187 | { 188 | go("test_multiple_readers reader", [&received, &closed](channel_reader reader) 189 | { 190 | try 191 | { 192 | for(;;) 193 | { 194 | reader.get(); 195 | received++; 196 | } 197 | } 198 | catch(const channel_closed&) 199 | { 200 | closed++; 201 | throw; 202 | } 203 | 204 | }, pair.reader); 205 | } 206 | 207 | wait_for_completion(); 208 | 209 | BOOST_CHECK_EQUAL(closed, READERS); 210 | BOOST_CHECK_EQUAL(received, MSGS); 211 | } 212 | 213 | BOOST_FIXTURE_TEST_CASE(test_multiple_writers, fixture) 214 | { 215 | static const int MSGS_PER_WRITER = 100; 216 | static const int WRITERS = 100; 217 | 218 | std::atomic received(0); 219 | 220 | channel_pair pair = make_channel(10); 221 | 222 | for(int i = 0; i < WRITERS; i++) 223 | { 224 | go( 225 | boost::str(boost::format("test_multiple_readers writer %d") % i), 226 | [](channel_writer writer) 227 | { 228 | for(int i = 0; i < MSGS_PER_WRITER; i++) 229 | { 230 | writer.put(i); 231 | } 232 | }, pair.writer); 233 | } 234 | pair.writer.close(); 235 | 236 | go("test_multiple_readers reader", [&received](channel_reader& reader) 237 | { 238 | for(;;) 239 | { 240 | reader.get(); 241 | received++; 242 | } 243 | 244 | }, std::move(pair.reader)); 245 | 246 | wait_for_completion(); 247 | 248 | BOOST_CHECK_EQUAL(received, WRITERS*MSGS_PER_WRITER); 249 | } 250 | 251 | BOOST_FIXTURE_TEST_CASE(test_non_blocking_read, fixture) 252 | { 253 | auto pair = make_channel(10); 254 | int read = 0; 255 | bool completed = false; 256 | 257 | go([&]() 258 | { 259 | pair.writer.put(1.1); 260 | pair.writer.put(2.2); 261 | pair.writer.put(3.3); 262 | 263 | double x; 264 | while(pair.reader.try_get(x)) 265 | read++; 266 | completed = true; 267 | }); 268 | 269 | wait_for_completion(); 270 | 271 | BOOST_CHECK_EQUAL(read, 3); 272 | BOOST_CHECK_EQUAL(completed, true); 273 | } 274 | 275 | }} 276 | -------------------------------------------------------------------------------- /test/clang_asan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | mkdir -p .asan 4 | clang++ -g -O1 -fsanitize=address -fno-omit-frame-pointer -std=c++11 -o .asan/coroutines main.cpp -L/usr/local/boost_1_54/lib -Wl,-Bstatic -lboost_context -Wl,-Bdynamic 5 | cd .asan 6 | ./coroutines 2> log 7 | /home/maciek/dev/llvm/llvm/projects/compiler-rt/lib/asan/scripts/asan_symbolize.py / < log | c++filt 8 | -------------------------------------------------------------------------------- /test/fibonacci.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | def fibonacci(): 4 | last = 1 5 | current = 1 6 | while(True): 7 | yield current 8 | nxt = last + current 9 | last = current 10 | current = nxt 11 | 12 | 13 | N = 10 14 | print('Two fibonacci sequences generated in parallel:') 15 | generator1 = fibonacci() 16 | print('seq #1: %d' % generator1.next()) 17 | print('seq #1: %d' % generator1.next()) 18 | 19 | generator2 = fibonacci() 20 | for i in range(N): 21 | print('seq #1: %d' % generator1.next()) 22 | print('seq #2: %d' % generator2.next()) 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/fixtures.cpp: -------------------------------------------------------------------------------- 1 | #include "test/fixtures.hpp" 2 | 3 | namespace coroutines { namespace tests { 4 | 5 | // any static asserts here 6 | 7 | } } 8 | -------------------------------------------------------------------------------- /test/fixtures.hpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #ifndef COROUTINES_TESTS_FIXTURES_HPP 3 | #define COROUTINES_TESTS_FIXTURES_HPP 4 | 5 | #include "coroutines/globals.hpp" 6 | 7 | namespace coroutines { namespace tests { 8 | 9 | // simple fixture creating a 4-thread scheduler 10 | class fixture 11 | { 12 | public: 13 | 14 | fixture() 15 | : _sched(4) 16 | { 17 | set_scheduler(&_sched); 18 | } 19 | 20 | ~fixture() 21 | { 22 | _sched.wait(); 23 | set_scheduler(nullptr); 24 | } 25 | 26 | protected: 27 | 28 | void wait_for_completion() 29 | { 30 | _sched.wait(); 31 | } 32 | 33 | private: 34 | 35 | scheduler _sched; 36 | }; 37 | 38 | } } 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /test/generator_tests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #include "generator_tests.hpp" 4 | #include "coroutines/generator.hpp" 5 | 6 | #include 7 | #include 8 | 9 | namespace coroutines { namespace tests { 10 | 11 | template // now we can choose in which flavour do we want our fibonacci numbers 12 | void fibonacci(const typename corountines::generator::yield_function_type& yield) 13 | { 14 | NumericType last = 1; 15 | NumericType current = 1; 16 | for(;;) 17 | { 18 | yield(current); 19 | NumericType nxt = last + current; 20 | last = current; 21 | current = nxt; 22 | } 23 | } 24 | 25 | void fibonacci() 26 | { 27 | const int N = 10; 28 | std::cout << "Two fibonacci sequences generated in parallel::" << std::endl; 29 | std::cout << std::setprecision(3) << std::fixed; // to make floating point number distinguishable 30 | corountines::generator generator1(fibonacci); 31 | std::cout << "seq #1: " << generator1.next() << std::endl; 32 | std::cout << "seq #1: " << generator1.next() << std::endl; 33 | 34 | corountines::generator generator2(fibonacci); 35 | for(int i = 0; i < N; i++) 36 | { 37 | std::cout << "seq #1: " << generator1.next() << std::endl; 38 | std::cout << "seq #2: " << generator2.next() << std::endl; 39 | } 40 | } 41 | 42 | }} 43 | 44 | 45 | -------------------------------------------------------------------------------- /test/generator_tests.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #ifndef COROUTINES_GENERATOR_TESTS_HPP 4 | #define COROUTINES_GENERATOR_TESTS_HPP 5 | 6 | namespace coroutines { namespace tests { 7 | 8 | extern void fibonacci(); 9 | 10 | }} 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | 3 | #include "coroutines/globals.hpp" 4 | 5 | #define BOOST_TEST_MODULE coroutines_test 6 | #include 7 | 8 | #include 9 | 10 | static void signal_handler(int) 11 | { 12 | coroutines::scheduler * sched = coroutines::get_scheduler(); 13 | if (sched) 14 | { 15 | sched->debug_dump(); 16 | } 17 | } 18 | 19 | // not really a test case, just a hook to indtal sigbnal handler 20 | BOOST_AUTO_TEST_CASE(singal_instller) 21 | { 22 | signal(SIGINT, signal_handler); 23 | } 24 | -------------------------------------------------------------------------------- /test/mutex_tests.cpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #include "coroutines/mutex.hpp" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace coroutines { namespace tests { 16 | 17 | // torture test 18 | 19 | template 20 | void writer(ContainerType& data, rw_spinlock& mutex) 21 | { 22 | std::lock_guard lock(mutex); 23 | 24 | static std::minstd_rand generator; 25 | static std::uniform_int_distribution distribution(-100, 100); 26 | 27 | int sum = 0; 28 | for(int i = 0; i < data.size() - 1; i++) 29 | { 30 | data[i] = distribution(generator); 31 | sum += data[i]; 32 | } 33 | data.back() = -sum; 34 | } 35 | 36 | template 37 | void reader(ContainerType& data, rw_spinlock& mutex) 38 | { 39 | reader_guard lock(mutex); 40 | 41 | int sum = std::accumulate(data.begin(), data.end(), 0); 42 | BOOST_REQUIRE_EQUAL(sum, 0); 43 | } 44 | 45 | BOOST_AUTO_TEST_CASE(rw_spinlock_torture) 46 | { 47 | static const int data_size = 100000; 48 | static const int thread_num = 10; 49 | static const int cycles_per_thread = 100; 50 | static const int writers_per_cycle = 1; 51 | static const int readers_per_cycle = 10; 52 | 53 | std::vector data(data_size, 0); 54 | 55 | rw_spinlock mutex; 56 | 57 | // init the data 58 | writer(data, mutex); 59 | 60 | std::cout << "rw_spinlock torture-test, wait..." << std::endl; 61 | 62 | std::atomic writers_run(0); 63 | std::atomic readers_run(0); 64 | 65 | std::vector threads; 66 | for(int i = 0; i < thread_num; i++) 67 | { 68 | threads.emplace_back([&]() 69 | { 70 | for(int c = 0; c < cycles_per_thread; c++) 71 | { 72 | for(int r = 0; r < readers_per_cycle; r++) 73 | { 74 | reader(data, mutex); 75 | readers_run++; 76 | } 77 | for(int w = 0; w < writers_per_cycle; w++) 78 | { 79 | writer(data, mutex); 80 | writers_run++; 81 | } 82 | } 83 | }); 84 | } 85 | 86 | for(std::thread& t : threads) 87 | { 88 | t.join(); 89 | } 90 | 91 | BOOST_CHECK_EQUAL(writers_run, thread_num * cycles_per_thread * writers_per_cycle); 92 | BOOST_CHECK_EQUAL(readers_run, thread_num * cycles_per_thread * readers_per_cycle); 93 | 94 | std::cout << "rw_spinlock torture-test finished" << std::endl; 95 | } 96 | 97 | // this test checks whether the rw spinlock is actually a reader-writer 98 | BOOST_AUTO_TEST_CASE(rw_spinlock_test) 99 | { 100 | static const int concurrent_writers = 4; 101 | static const int concurrent_readers = 10; 102 | static const auto delay = std::chrono::milliseconds(100); 103 | 104 | rw_spinlock mutex; 105 | std::vector threads; 106 | 107 | std::cout << "rw_spinlock test..." << std::endl; 108 | auto start = std::chrono::high_resolution_clock::now(); 109 | 110 | // start the readers first - the total time should be ~= delay 111 | for(int i = 0; i < concurrent_readers; i++) 112 | { 113 | threads.emplace_back([&]() 114 | { 115 | reader_guard lock(mutex); 116 | std::this_thread::sleep_for(delay); 117 | }); 118 | } 119 | 120 | // expected total time: delay*concurrent_writers 121 | for(int i = 0; i < concurrent_writers; i++) 122 | { 123 | threads.emplace_back([&]() 124 | { 125 | std::lock_guard lock(mutex); 126 | std::this_thread::sleep_for(delay); 127 | }); 128 | } 129 | 130 | for(std::thread& thread : threads) 131 | { 132 | thread.join(); 133 | } 134 | 135 | auto end = std::chrono::high_resolution_clock::now(); 136 | 137 | int total_ms = (end-start) / std::chrono::milliseconds(1); 138 | int expected_ms = (concurrent_writers+1)*delay / std::chrono::milliseconds(1); 139 | int tolerance = delay / std::chrono::milliseconds(1); 140 | 141 | std::cout << " > total time: " << total_ms << " ms" << std::endl; 142 | std::cout << " > expected time: " << expected_ms << " ms" << std::endl; 143 | 144 | BOOST_CHECK_CLOSE(float(total_ms), float(expected_ms), 100*float(tolerance)/expected_ms); 145 | } 146 | 147 | 148 | }} 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /test/scheduler_tests.cpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #include "coroutines/globals.hpp" 3 | 4 | #include "test/fixtures.hpp" 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace coroutines { namespace tests { 11 | 12 | 13 | BOOST_FIXTURE_TEST_CASE(test_nestet_coros, fixture) 14 | { 15 | channel_pair pair1 = make_channel(10); 16 | 17 | go(std::string("test_nestet_coros reader1"), [](channel_reader& reader) 18 | { 19 | int res = reader.get(); 20 | BOOST_CHECK_EQUAL(res, 1); 21 | }, std::move(pair1.reader)); 22 | 23 | go(std::string("test_nestet_coros reader2"), [](channel_writer& writer) 24 | { 25 | channel_pair pair2 = make_channel(10); 26 | 27 | 28 | go(std::string("test_nestet_coros nested"), [](channel_writer& w1, channel_writer& w2) 29 | { 30 | w1.put(1); 31 | w2.put(3); 32 | }, std::move(writer), std::move(pair2.writer)); 33 | 34 | int res = pair2.reader.get(); 35 | BOOST_CHECK_EQUAL(res, 3); 36 | 37 | }, std::move(pair1.writer)); 38 | } 39 | 40 | BOOST_FIXTURE_TEST_CASE(test_muchos_coros, fixture) 41 | { 42 | const int NUM = 1000; 43 | const int MSGS = 10000; 44 | std::atomic received(0); 45 | std::atomic sent(0); 46 | std::atomic coros(0); 47 | for(int i = 0; i < NUM; i++) 48 | { 49 | channel_pair pair = make_channel(10); 50 | 51 | go(std::string("test_muchos_coros reader"), [&received, &coros](channel_reader& r) 52 | { 53 | coros++; 54 | for(int i = 0; i < MSGS; i++) 55 | { 56 | try 57 | { 58 | r.get(); 59 | received++; 60 | } 61 | catch(const channel_closed&) 62 | { 63 | std::cout << "channel closed after readig only " << i << " msgs" << std::endl; 64 | throw; 65 | } 66 | } 67 | }, std::move(pair.reader)); 68 | 69 | go(std::string("test_muchos_coros writer"), [&sent, &coros](channel_writer& w) 70 | { 71 | coros++; 72 | for(int i = 0; i < MSGS; i++) 73 | { 74 | try 75 | { 76 | w.put(i); 77 | sent++; 78 | } 79 | catch(const channel_closed&) 80 | { 81 | std::cout << "channel closed after writing only " << i << " msgs" << std::endl; 82 | throw; 83 | } 84 | } 85 | }, std::move(pair.writer)); 86 | } 87 | 88 | wait_for_completion(); 89 | 90 | BOOST_CHECK_EQUAL(coros, NUM*2); 91 | BOOST_CHECK_EQUAL(received, NUM*MSGS); 92 | BOOST_CHECK_EQUAL(sent, NUM*MSGS); 93 | } 94 | 95 | static void nonblocking_coro(std::atomic& counter, int spawns) 96 | { 97 | if (spawns > 0) 98 | { 99 | counter++; 100 | std::this_thread::sleep_for(std::chrono::milliseconds(4)); // this will be called 100 times on 4 threads, we need to cover 1000ms they should saturate all 4 threads for at least 1000ms, so we need 2000 ms total 101 | go("nonblocking nested", nonblocking_coro, std::ref(counter), spawns-1); 102 | } 103 | } 104 | 105 | BOOST_FIXTURE_TEST_CASE(test_blocking_coros, fixture) 106 | { 107 | std::cout << " > this test should take approx. one second" << std::endl; 108 | 109 | std::atomic nonblocking(0); 110 | std::atomic blocking(0); 111 | 112 | const int SERIES = 10; 113 | const int NON_BLOCKING_PER_SER = 10; 114 | const int NON_BLOCKING_SPAWNS = 10; 115 | 116 | auto start = std::chrono::high_resolution_clock::now(); 117 | 118 | for(int s = 0; s < SERIES; s++) 119 | { 120 | // start on that will block 121 | go("test_blocking_coros blocking", [&blocking]() 122 | { 123 | blocking++; 124 | block(); 125 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 126 | unblock(); 127 | 128 | blocking++; 129 | block(); 130 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 131 | unblock(); 132 | 133 | blocking++; 134 | }); 135 | 136 | // start some non-blocking coroutines 137 | for(int i = 0; i < NON_BLOCKING_PER_SER; i++) 138 | { 139 | go("test_blocking_coros nonblocking", [&nonblocking]() 140 | { 141 | nonblocking_coro(nonblocking, NON_BLOCKING_SPAWNS); 142 | }); 143 | } 144 | 145 | // the entire test should block for bit more than 1 second 146 | } 147 | 148 | wait_for_completion(); 149 | 150 | auto end = std::chrono::high_resolution_clock::now(); 151 | std::cout << " > actual time: " << (end-start)/std::chrono::milliseconds(1) << " ms" << std::endl; 152 | std::cout << " > total busy-block time per thread: " << nonblocking*4/4 << " ms" << std::endl; 153 | 154 | BOOST_CHECK_EQUAL(nonblocking, SERIES*NON_BLOCKING_PER_SER*NON_BLOCKING_SPAWNS); 155 | BOOST_CHECK_EQUAL(blocking, SERIES*3); 156 | } 157 | 158 | ///////////// 159 | // tree traversal 160 | 161 | struct node 162 | { 163 | double value; 164 | node* left = nullptr; 165 | node* right = nullptr; 166 | }; 167 | 168 | static node* build_tree(unsigned levels) 169 | { 170 | static double x = 7; 171 | 172 | if (levels == 0 ) 173 | return nullptr; 174 | 175 | node* n = new node(); 176 | n->value = levels * x; 177 | n->left = build_tree(levels - 1); 178 | n->right = build_tree(levels - 1); 179 | 180 | x = x*3.14; 181 | 182 | return n; 183 | } 184 | 185 | static double sum_tree(node* n) 186 | { 187 | if (n) 188 | { 189 | return n->value + sum_tree(n->left) + sum_tree(n->right); 190 | } 191 | else 192 | { 193 | return 0.0; 194 | } 195 | } 196 | 197 | static void paraller_sum_sub(node* tree, channel_writer& out) 198 | { 199 | out.put(sum_tree(tree)); 200 | } 201 | 202 | static double paraller_sum(node* tree) 203 | { 204 | if (tree) 205 | { 206 | auto pair = make_channel(2); 207 | go(paraller_sum_sub, tree->left, pair.writer); 208 | go(paraller_sum_sub, tree->right, pair.writer); 209 | 210 | return pair.reader.get() + pair.reader.get(); 211 | } 212 | else 213 | { 214 | return 0; 215 | } 216 | 217 | } 218 | 219 | BOOST_FIXTURE_TEST_CASE(tree_traverse_test, fixture) 220 | { 221 | node* tree = build_tree(20); 222 | 223 | auto start = std::chrono::high_resolution_clock::now(); 224 | double sum = sum_tree(tree); 225 | auto end = std::chrono::high_resolution_clock::now(); 226 | std::chrono::high_resolution_clock::duration single = end - start; 227 | std::chrono::high_resolution_clock::duration parallel; 228 | 229 | double psum = 0.0; 230 | go([tree, ¶llel, &psum]() 231 | { 232 | auto start = std::chrono::high_resolution_clock::now(); 233 | psum = paraller_sum(tree); 234 | auto end = std::chrono::high_resolution_clock::now(); 235 | parallel = end-start; 236 | }); 237 | 238 | wait_for_completion(); 239 | 240 | BOOST_CHECK_EQUAL(sum, psum); 241 | 242 | std::cout << "single thread duration: " << single / std::chrono::milliseconds(1) << " ms " << std::endl; 243 | std::cout << "parallel duration: " << parallel / std::chrono::milliseconds(1) << " ms " << std::endl; 244 | 245 | BOOST_CHECK_EQUAL(sum, psum); 246 | } 247 | 248 | 249 | }} 250 | 251 | -------------------------------------------------------------------------------- /test_io/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(test_io 2 | main.cpp 3 | ) 4 | 5 | target_link_libraries(test_io 6 | coroutines 7 | coroutines_io 8 | ) 9 | -------------------------------------------------------------------------------- /test_io/main.cpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | 3 | #include "coroutines/globals.hpp" 4 | 5 | #include "coroutines_io/globals.hpp" 6 | #include "coroutines_io/io_scheduler.hpp" 7 | #include "coroutines_io/tcp_socket.hpp" 8 | #include "coroutines_io/tcp_acceptor.hpp" 9 | 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | using namespace coroutines; 17 | 18 | template 19 | void _TEST_EQUAL(const T1& a, const T2& b, long line, const char* msg) 20 | { 21 | if (a != b) 22 | { 23 | std::cout << "Line " << line << " : " << msg << " failed (" << a << " != " << b << ")" << std::endl; 24 | assert(false); 25 | } 26 | } 27 | #define TEST_EQUAL(a, b) _TEST_EQUAL(a, b, __LINE__, #a "==" #b) 28 | #define RUN_TEST(test_name) std::cout << ">>> Starting test: " << #test_name << std::endl; test_name(); 29 | 30 | void test_connect() 31 | { 32 | scheduler sched(4); 33 | io_scheduler io_sched(sched); 34 | set_scheduler(&sched); 35 | set_io_scheduler(&io_sched); 36 | io_sched.start(); 37 | 38 | auto pair = make_channel(1); 39 | 40 | go("test_connect acceptor", [&pair]() 41 | { 42 | try 43 | { 44 | tcp_acceptor acceptor; 45 | 46 | acceptor.listen(boost::asio::ip::tcp::endpoint( 47 | boost::asio::ip::address_v4::from_string("0.0.0.0"), 48 | 22445)); 49 | 50 | pair.writer.put(0); 51 | 52 | std::this_thread::sleep_for(std::chrono::seconds(1)); 53 | std::cout << "accepting..." << std::endl; 54 | tcp_socket s = acceptor.accept(); 55 | std::cout << "accepted" << std::endl; 56 | 57 | static const int BUFSIZE = 64; 58 | char buf[BUFSIZE]; 59 | 60 | std::cout << "receiving...." << std::endl; 61 | std::size_t received = s.read(buf, BUFSIZE); 62 | 63 | std::string rstr(buf, received); 64 | std::cout << "received: " << rstr << std::endl; 65 | } 66 | catch(const std::exception& e) 67 | { 68 | std::cout << "acceptor error: " << e.what() << std::endl; 69 | } 70 | 71 | }); 72 | 73 | go("test_connect connector", [&pair]() 74 | { 75 | try 76 | { 77 | tcp_socket s; 78 | 79 | // wait for acceptor 80 | pair.reader.get(); 81 | 82 | std::cout << "connecting..." << std::endl; 83 | s.connect(boost::asio::ip::tcp::endpoint( 84 | boost::asio::ip::address_v4::from_string("127.0.0.1"), 85 | 22445)); 86 | std::cout << "connected" << std::endl; 87 | 88 | std::this_thread::sleep_for(std::chrono::seconds(1)); 89 | 90 | std::cout << "sending hello..." << std::endl; 91 | 92 | static const std::string hello = "hello"; 93 | std::size_t sent = s.write(hello.c_str(), hello.length()); 94 | 95 | std::cout << "sent " << sent << " bytes" << std::endl; 96 | } 97 | catch(const std::exception& e) 98 | { 99 | std::cout << "connection error: " << e.what() << std::endl; 100 | } 101 | }); 102 | 103 | sched.wait(); 104 | io_sched.stop(); 105 | sched.wait(); 106 | set_io_scheduler(nullptr); 107 | set_scheduler(nullptr); 108 | } 109 | 110 | int main(int , char** ) 111 | { 112 | RUN_TEST(test_connect); 113 | 114 | std::cout << "test completed" << std::endl; 115 | } 116 | 117 | -------------------------------------------------------------------------------- /torture/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package( Boost 1.38.0 COMPONENTS filesystem system) 2 | include_directories(${Boost_INCLUDE_DIRS}) 3 | 4 | add_executable(torture 5 | main.cpp 6 | parallel.cpp parallel.hpp 7 | buffer.hpp 8 | lzma_decompress.hpp lzma_decompress.cpp 9 | ) 10 | 11 | target_link_libraries(torture 12 | coroutines 13 | 14 | ${Boost_LIBRARIES} 15 | lzma 16 | ) 17 | -------------------------------------------------------------------------------- /torture/buffer.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #ifndef TORTURE_BUFFER_HPP 3 | #define TORTURE_BUFFER_HPP 4 | 5 | #include 6 | #include 7 | 8 | namespace torture 9 | { 10 | 11 | // used to send blocks of data around. Movable, bot not copytable 12 | class buffer 13 | { 14 | public: 15 | 16 | typedef char value_type; 17 | typedef char* iterator; 18 | typedef const char* const_iterator; 19 | 20 | // null buffer 21 | buffer() = default; 22 | 23 | // alocated buffer 24 | buffer(std::size_t capacity) 25 | : _capacity(capacity), _size(0), _data(new char[_capacity]) 26 | { 27 | } 28 | 29 | ~buffer() 30 | { 31 | } 32 | 33 | buffer(const buffer&) = delete; 34 | buffer(buffer&& o) noexcept 35 | { 36 | swap(o); 37 | } 38 | 39 | buffer& operator=(buffer&& o) 40 | { 41 | swap(o); 42 | return *this; 43 | } 44 | 45 | // iterators 46 | iterator begin() { return _data.get(); } 47 | iterator end() { return _data.get() + _size; } 48 | const_iterator begin() const { return _data.get(); } 49 | const_iterator end() const { return _data.get() + _size; } 50 | 51 | // size/capacity 52 | void set_size(std::size_t s) { _size = s; } 53 | std::size_t size() const { return _size; } 54 | std::size_t capacity() const { return _capacity; } 55 | 56 | bool is_null() const { return !_capacity; } 57 | 58 | // other 59 | void swap(buffer& o) noexcept 60 | { 61 | std::swap(_capacity, o._capacity); 62 | std::swap(_size, o._size); 63 | std::swap(_data, o._data); 64 | } 65 | 66 | private: 67 | 68 | std::size_t _capacity = 0; // buffer capacity 69 | std::size_t _size = 0; // amount of data in 70 | std::unique_ptr _data; 71 | }; 72 | 73 | } 74 | 75 | namespace std { 76 | 77 | template<> 78 | inline 79 | void swap(torture::buffer& a, torture::buffer& b) 80 | { 81 | a.swap(b); 82 | } 83 | 84 | } 85 | #endif 86 | -------------------------------------------------------------------------------- /torture/lzma_decompress.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | 3 | #include "lzma_decompress.hpp" 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace torture { 10 | 11 | void lzma_decompress( 12 | buffer_reader& compressed, 13 | buffer_writer& compressed_return, 14 | 15 | buffer_reader& decompressed_return, 16 | buffer_writer& decopressed 17 | ) 18 | { 19 | lzma_stream stream = LZMA_STREAM_INIT; 20 | lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED); 21 | if (ret != LZMA_OK) 22 | { 23 | throw std::runtime_error("lzma initialization failed"); 24 | } 25 | 26 | buffer inbuf; 27 | buffer outbuf = decompressed_return.get(); // get allocated buffer from writer 28 | 29 | stream.next_in = nullptr; 30 | stream.avail_in = 0; 31 | stream.next_out = (unsigned char*)outbuf.begin(); 32 | stream.avail_out = outbuf.capacity(); 33 | 34 | 35 | while(ret == LZMA_OK) 36 | { 37 | lzma_action action = LZMA_RUN; 38 | 39 | // read more data, if input buffer empty 40 | if(stream.avail_in == 0) 41 | { 42 | // return previous used buffer 43 | if (!inbuf.is_null()) 44 | compressed_return.put_nothrow(std::move(inbuf)); 45 | try 46 | { 47 | // read one 48 | inbuf = compressed.get(); 49 | stream.next_in = (unsigned char*)inbuf.begin(); 50 | stream.avail_in = inbuf.size(); 51 | } 52 | catch(const coroutines::channel_closed&) 53 | { 54 | action = LZMA_FINISH; 55 | } 56 | } 57 | 58 | // decompress 59 | ret = lzma_code(&stream, action); 60 | if (stream.avail_out == 0 || ret == LZMA_STREAM_END) 61 | { 62 | outbuf.set_size(stream.next_out - (unsigned char*)outbuf.begin()); 63 | // send the buffer, receive an empty one 64 | decopressed.put(std::move(outbuf)); 65 | 66 | if (ret != LZMA_STREAM_END) 67 | { 68 | outbuf = decompressed_return.get(); 69 | stream.next_out = (unsigned char*)outbuf.begin(); 70 | stream.avail_out = outbuf.capacity(); 71 | } 72 | } 73 | 74 | } 75 | 76 | lzma_end(&stream); 77 | 78 | if (ret != LZMA_STREAM_END) 79 | { 80 | std::cerr << "lzma decoding error" << std::endl; 81 | } 82 | // exit will close all channels 83 | } 84 | 85 | 86 | } 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /torture/lzma_decompress.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Maciej Gajewski 2 | #ifndef TORTURE_LZMA_DECOMPRESS_HPP 3 | #define TORTURE_LZMA_DECOMPRESS_HPP 4 | 5 | #include "buffer.hpp" 6 | 7 | #include "coroutines/channel.hpp" 8 | 9 | namespace torture { 10 | 11 | typedef coroutines::channel_reader buffer_reader; 12 | typedef coroutines::channel_writer buffer_writer; 13 | 14 | // function that decopresses LZMA stream. 15 | // Operation: 16 | // Receives buffers with compressed data via compressed until channel closes, returns bufers trough compressed_return. 17 | // Receives buffers for decompressed data from decompressed_return, sends compressed back trough decopressed, closes chanel when done. 18 | void lzma_decompress( 19 | buffer_reader& compressed, 20 | buffer_writer& compressed_return, 21 | 22 | buffer_reader& decompressed_return, 23 | buffer_writer& decopressed 24 | ); 25 | 26 | } 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /torture/main.cpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | 3 | #include "parallel.hpp" 4 | 5 | #include 6 | 7 | // torture test: 8 | // invocation: torture DIR 9 | // how it works: it opens all files in the direcotry with .xz suffix, decompresses them and puts them into DIR/out with suffix removed 10 | int main(int argc, char** argv) 11 | { 12 | if (argc < 3) 13 | { 14 | std::cerr << "Error: directory missing. Invocation: torture INPUTDIR OUTPUTDIR" << std::endl; 15 | return 2; 16 | } 17 | 18 | torture::parallel(argv[1], argv[2]); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /torture/parallel.cpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | 3 | #include "coroutines/globals.hpp" 4 | 5 | #include "parallel.hpp" 6 | #include "lzma_decompress.hpp" 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | 16 | 17 | using namespace coroutines; 18 | namespace bfs = boost::filesystem; 19 | 20 | namespace torture { 21 | 22 | static const unsigned BUFFERS = 8; 23 | static const unsigned BUFFER_SIZE = 100*1024; 24 | 25 | class file 26 | { 27 | public: 28 | 29 | file(const char* path, const char* mode) 30 | { 31 | _f = ::fopen(path, mode); 32 | if (!_f) 33 | throw std::runtime_error("Unable to open file"); 34 | } 35 | 36 | ~file() 37 | { 38 | ::fclose(_f); 39 | } 40 | 41 | std::size_t read(void* buf, std::size_t max) 42 | { 43 | block(); 44 | std::size_t r = ::fread(buf, 1, max, _f); 45 | unblock(); 46 | return r; 47 | } 48 | 49 | std::size_t write(void* buf, std::size_t size) 50 | { 51 | block(); 52 | std::size_t r = ::fwrite(buf, size, 1, _f); 53 | unblock(); 54 | return r; 55 | } 56 | 57 | private: 58 | 59 | FILE* _f = nullptr; 60 | }; 61 | 62 | void process_file(const bfs::path& in_path, const bfs::path& out_path); 63 | void write_output(buffer_reader& decompressed, buffer_writer& decompressed_return, const bfs::path& output_file); 64 | void read_input(buffer_writer& compressed, buffer_reader& compressed_return, const bfs::path& input_file); 65 | 66 | void signal_handler(int) 67 | { 68 | scheduler * sched = get_scheduler(); 69 | if (sched) 70 | { 71 | sched->debug_dump(); 72 | } 73 | } 74 | 75 | // Main entry point 76 | void parallel(const char* in, const char* out) 77 | { 78 | // install signal handler 79 | signal(SIGINT, signal_handler); 80 | 81 | scheduler sched(4 /*threads*/); // Go: runtime.MAXPROC(4) 82 | set_scheduler(&sched); 83 | 84 | try 85 | { 86 | bfs::path input_dir(in); 87 | bfs::path output_dir(out); 88 | 89 | 90 | for(bfs::directory_iterator it(input_dir); it != bfs::directory_iterator(); ++it) 91 | { 92 | if (it->path().extension() == ".xz" && it->status().type() == bfs::regular_file) 93 | { 94 | bfs::path output_path = output_dir / it->path().filename().stem(); 95 | go(std::string("process_file ") + it->path().string(), process_file, it->path(), output_path); 96 | } 97 | 98 | } 99 | 100 | } 101 | catch(const std::exception& e) 102 | { 103 | std::cerr << "Error :" << e.what() << std::endl; 104 | } 105 | 106 | sched.wait(); 107 | set_scheduler(nullptr); 108 | } 109 | 110 | void process_file(const bfs::path& input_file, const bfs::path& output_file) 111 | { 112 | channel_pair compressed = make_channel(BUFFERS, "compressed"); 113 | channel_pair decompressed = make_channel(BUFFERS, "decompressed"); 114 | channel_pair compressed_return = make_channel(BUFFERS, "compressed_return"); 115 | channel_pair decompressed_return = make_channel(BUFFERS, "decompressed_return"); 116 | 117 | 118 | // start writer 119 | go(std::string("write_output ") + output_file.string(), 120 | write_output, 121 | decompressed.reader, decompressed_return.writer, output_file); 122 | 123 | // start decompressor 124 | go(std::string("lzma_decompress ") + input_file.string(), 125 | lzma_decompress, 126 | compressed.reader, compressed_return.writer, 127 | decompressed_return.reader, decompressed.writer); 128 | 129 | // read (in this coroutine) 130 | read_input(compressed.writer, compressed_return.reader, input_file); 131 | } 132 | 133 | void read_input(buffer_writer& compressed, buffer_reader& compressed_return, const bfs::path& input_file) 134 | { 135 | try 136 | { 137 | file f(input_file.string().c_str(), "rb"); 138 | 139 | unsigned counter = 0; 140 | for(;;) 141 | { 142 | buffer b; 143 | if (counter++ < BUFFERS) 144 | b = buffer(BUFFER_SIZE); 145 | else 146 | b = compressed_return.get(); // get spent buffer from decoder 147 | std::size_t r = f.read(b.begin(), b.capacity()); 148 | if (r == 0) 149 | break; // this will close the channel 150 | else 151 | { 152 | b.set_size(r); 153 | compressed.put(std::move(b)); 154 | } 155 | } 156 | } 157 | catch(const std::exception& e) 158 | { 159 | std::cerr << "Error reading file " << input_file << " : " << e.what() << std::endl; 160 | } 161 | } 162 | 163 | 164 | void write_output(buffer_reader& decompressed, buffer_writer& decompressed_return, const bfs::path& output_file) 165 | { 166 | try 167 | { 168 | // open file 169 | file f(output_file.string().c_str(), "wb"); 170 | 171 | // fill the queue with allocated buffers 172 | for(unsigned i = 0; i < BUFFERS; i++) 173 | { 174 | decompressed_return.put(buffer(BUFFER_SIZE)); 175 | } 176 | 177 | for(;;) 178 | { 179 | buffer b = decompressed.get(); 180 | f.write(b.begin(), b.size()); 181 | decompressed_return.put_nothrow(std::move(b)); // return buffer to decoder 182 | } 183 | } 184 | catch(const channel_closed&) // this exception is expected when channels are closed 185 | { 186 | } 187 | catch(const std::exception& e) 188 | { 189 | std::cerr << "Error writing to output file " << output_file << " : " << e.what() << std::endl; 190 | } 191 | } 192 | 193 | } 194 | -------------------------------------------------------------------------------- /torture/parallel.hpp: -------------------------------------------------------------------------------- 1 | // (c) 2013 Maciej Gajewski, 2 | #ifndef TORTURE_PARALLEL_HPP 3 | #define TORTURE_PARALLEL_HPP 4 | 5 | namespace torture 6 | { 7 | 8 | // main entry point to parallel implementation 9 | void parallel(const char* dir, const char* out); 10 | 11 | } 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /torture/serial.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | INDIR=$1 4 | OUTDIR=$2 5 | 6 | for i in $INDIR/*.xz 7 | do 8 | o=$OUTDIR/`basename $i .xz` 9 | echo $i "->" $o 10 | time xzcat $i > $o 11 | done 12 | --------------------------------------------------------------------------------