├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── archive.sh ├── common ├── callback-queue.cc ├── callback-queue.h ├── log-text.cc ├── log-text.h ├── log-values.cc └── log-values.h ├── demo ├── README.md ├── logging.cc ├── logging.h ├── main.cc ├── test.cc ├── tracing.cc └── tracing.h ├── example ├── async.cc ├── connection.cc ├── function.cc ├── inherit.cc ├── limit.cc ├── manual.cc ├── mock.cc ├── multi.cc ├── paths.cc ├── simple.cc ├── speed.cc ├── threaded.cc ├── throttle.cc └── trace.cc ├── include ├── thread-capture.h └── thread-crosser.h ├── src └── thread-crosser.cc └── test ├── readme-test.cc ├── thread-capture-test.cc └── thread-crosser-test.cc /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | project(capture-thread) 3 | 4 | include_directories( 5 | ${CMAKE_SOURCE_DIR}/include 6 | ${CMAKE_SOURCE_DIR}/common) 7 | 8 | set(CMAKE_CXX_FLAGS "-Wall -pedantic -std=c++11 -O2 -g -pthread") 9 | 10 | IF(NOT USE_PREFIX) 11 | SET(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}") 12 | ENDIF() 13 | 14 | SET(INSTALL_INCLUDEDIR "include") 15 | SET(INSTALL_LIBDIR "lib") 16 | 17 | SET(INSTALL_PATH "${CMAKE_INSTALL_PREFIX}") 18 | SET(INCLUDE_PATH "${INSTALL_PATH}/${INSTALL_INCLUDEDIR}") 19 | SET(LIBRARY_PATH "${INSTALL_PATH}/${INSTALL_LIBDIR}") 20 | 21 | INCLUDE(FindThreads) 22 | FIND_LIBRARY(PTHREAD_LIBRARY 23 | NAMES pthread 24 | QUIET 25 | ONLY_CMAKE_FIND_ROOT_PATH) 26 | IF(NOT PTHREAD_LIBRARY) 27 | SET(PTHREAD_LIBRARY "") 28 | ENDIF() 29 | 30 | add_library(capture-thread STATIC 31 | src/thread-crosser.cc) 32 | 33 | add_executable(async 34 | example/async.cc) 35 | target_link_libraries(async 36 | capture-thread 37 | ${PTHREAD_LIBRARY}) 38 | 39 | add_executable(connection 40 | example/connection.cc) 41 | target_link_libraries(connection 42 | capture-thread) 43 | 44 | add_executable(function 45 | example/function.cc) 46 | target_link_libraries(function 47 | capture-thread) 48 | 49 | add_executable(inherit 50 | example/inherit.cc) 51 | target_link_libraries(inherit 52 | capture-thread) 53 | 54 | add_executable(limit 55 | example/limit.cc) 56 | target_link_libraries( 57 | limit 58 | capture-thread) 59 | 60 | add_executable(manual 61 | example/manual.cc) 62 | target_link_libraries(manual 63 | capture-thread 64 | ${PTHREAD_LIBRARY}) 65 | 66 | add_executable(mock 67 | example/mock.cc) 68 | target_link_libraries(mock 69 | capture-thread) 70 | 71 | add_executable(multi 72 | example/multi.cc) 73 | target_link_libraries(multi 74 | capture-thread 75 | ${PTHREAD_LIBRARY}) 76 | 77 | add_executable(paths 78 | example/paths.cc) 79 | target_link_libraries( 80 | paths 81 | capture-thread) 82 | 83 | add_executable(simple 84 | example/simple.cc) 85 | target_link_libraries( 86 | simple 87 | capture-thread) 88 | 89 | add_executable(speed 90 | example/speed.cc) 91 | target_link_libraries( 92 | speed 93 | capture-thread) 94 | 95 | add_executable(threaded 96 | example/threaded.cc) 97 | target_link_libraries( 98 | threaded 99 | capture-thread 100 | ${PTHREAD_LIBRARY}) 101 | 102 | add_executable(throttle 103 | example/throttle.cc) 104 | target_link_libraries( 105 | throttle 106 | capture-thread 107 | ${PTHREAD_LIBRARY}) 108 | 109 | add_executable(trace 110 | example/trace.cc) 111 | target_link_libraries( 112 | trace 113 | capture-thread 114 | ${PTHREAD_LIBRARY}) 115 | 116 | add_executable(demo-main 117 | demo/main.cc 118 | demo/logging.cc 119 | demo/tracing.cc 120 | common/callback-queue.cc) 121 | target_link_libraries(demo-main 122 | capture-thread 123 | ${PTHREAD_LIBRARY}) 124 | 125 | add_executable(readme-test 126 | test/readme-test.cc) 127 | target_link_libraries(readme-test 128 | capture-thread 129 | ${PTHREAD_LIBRARY}) 130 | 131 | INSTALL(TARGETS 132 | capture-thread 133 | DESTINATION ${INSTALL_LIBDIR}) 134 | 135 | INSTALL(FILES 136 | include/thread-capture.h 137 | include/thread-crosser.h 138 | DESTINATION ${INSTALL_INCLUDEDIR}) 139 | 140 | find_package(GTest) 141 | if(GTEST_LIBRARIES) 142 | 143 | include_directories(${GTEST_INCLUDE_DIRS}) 144 | 145 | add_executable(thread-capture-test 146 | test/thread-capture-test.cc 147 | common/log-text.cc 148 | common/log-values.cc 149 | common/callback-queue.cc) 150 | target_link_libraries(thread-capture-test 151 | gtest gmock gtest_main 152 | capture-thread 153 | ${PTHREAD_LIBRARY}) 154 | 155 | add_executable(thread-crosser-test 156 | test/thread-crosser-test.cc 157 | common/log-text.cc 158 | common/log-values.cc 159 | common/callback-queue.cc) 160 | target_link_libraries(thread-crosser-test 161 | gtest gmock gtest_main 162 | capture-thread 163 | ${PTHREAD_LIBRARY}) 164 | 165 | add_executable(demo-test 166 | demo/test.cc 167 | demo/logging.cc 168 | demo/tracing.cc 169 | common/callback-queue.cc) 170 | target_link_libraries(demo-test 171 | gtest gmock gtest_main 172 | capture-thread 173 | ${PTHREAD_LIBRARY}) 174 | 175 | endif() 176 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Capture Thread Library][google/capture-thread] 2 | 3 | Framework for loggers, tracers, and mockers in multithreaded C++ programs. 4 | 5 | *(This is not an official Google product.)* 6 | 7 | ## Motivation 8 | 9 | When developing C++ projects, [instrumentation][instrumentation] is frequently 10 | used to collect information from the system, inject information into the system, 11 | or both. The role of this information within the system rarely lines up with the 12 | actual structure of the project. 13 | 14 | For example: 15 | 16 | - *Loggers* will collect information in a wide range of contexts within the 17 | code, and thus the logic connecting the log points to the logger will not 18 | match the structure of the project. 19 | 20 | - *Tracers* make available information that describes how call execution 21 | arrived at a certain point. In some cases this information can be passed 22 | along with the data being processed, but in most cases this is obtrusive and 23 | does not scale well. 24 | 25 | - *Mockers* replace default idioms (e.g., opening files) with alternate 26 | behavior for the purposes of testing. These actions generally occur deep 27 | within the code, and could not otherwise be swapped out for testing without 28 | leaking those details in the API. 29 | 30 | This library is designed to handle all of these situations with minimal 31 | intrusion into your project, and without leaking details in your API. 32 | 33 | ## Summary 34 | 35 | The **Capture Thread Library** is designed around the concept of 36 | thread-locality, which allows the sharing of static variables *only within the 37 | current thread*. Canonical static variables, on the other hand, are problematic 38 | due to ownership and thread-safety issues. 39 | 40 | This library establishes the following idiom *(using logging as an example)*: 41 | 42 | 1. The instrumentation is 100% passive unless it is explicitly enabled. *(For 43 | example, logging points that by default just print to `std::cerr`.)* 44 | 45 | 2. Instrumentation is enabled in the current thread by *instantiating* an 46 | implementation, and only remains enabled until that instance goes out of 47 | scope. *(For example, an implementation that captures logged lines while 48 | it's in scope.)* 49 | 50 | 3. While enabled, the instrumentation transparently alters the behavior of 51 | logic deep within the code that would otherwise use default behavior. *(For 52 | example, the log-capture instance redirects messages to a `std::list` only 53 | while it's in scope.)* 54 | 55 | 4. Instrumentation can be shared across threads in *explicitly-specified* 56 | points in the code, based entirely on how you compartmentalize your logic. 57 | *(For example, independent of the instrumentation, you logically split 58 | processing of a query into multiple threads, and want the instrumentation to 59 | treat it as a single process.)* 60 | 61 | ## Key Design Points 62 | 63 | The **Capture Thread Library** has several design points that make it efficient 64 | and reliable: 65 | 66 | - All data structures are immutable and contain no dynamic allocation. 67 | - All library logic is thread-safe without threads blocking each other. 68 | - The enabling and disabling of instrumentation is strictly scope-driven, 69 | making it impossible to have bad pointers when used correctly. 70 | - The work required to share instrumentation between threads *does not* depend 71 | on the number of types of instrumentation used. (For example, sharing a 72 | logger and a tracer is the same amount of work as just sharing a logger.) 73 | - Instrumentation classes derived (by you) from this library *cannot* be 74 | moved, copied, or dynamically allocated, ensuring that scoping rules are 75 | strictly enforced. 76 | - The library is designed so that instrumentation data structures and calls do 77 | not need to be visible in your project's API headers, making them low-risk 78 | to add, modify, or remove. 79 | - All of the library code is thoroughly unit-tested. 80 | 81 | ## Caveats 82 | 83 | In some cases, it might not be appropriate (or possible) to use the **Capture 84 | Thread Library**: 85 | 86 | - By design, this library is meant to help you *circumvent* the structure of 87 | your program, specifically so that you don't need to modify your design to 88 | facilitate instrumentation. Although you could technically structure the 89 | business logic of your program around this library, doing so would likely 90 | lead to difficult-to-follow code and baffling latent bugs. 91 | 92 | - If you want to share instrumentation between threads, you *must* be able to 93 | pass either a `std::function` or a pointer from the source thread to the 94 | destination thread. This is because those semantics are a part of your 95 | project's design, and thus cannot be automatically inferred by the library. 96 | 97 | - Since this library is scope-driven, you *cannot* share instrumentation 98 | outside of the scope it was created in. For example: 99 | 100 | ```c++ 101 | void f() { MyLogger capture_messages; } 102 | void g() { MyLogger::Log("g was called"); } 103 | 104 | void Execute() { 105 | f(); 106 | g(); 107 | } 108 | ``` 109 | 110 | In this example, the instrumentation is created in `f`, but goes out of 111 | scope before `g` is called. In this case, `g` just uses the default behavior 112 | for `MyLogger::Log`. 113 | 114 | This is actually dangerous if you return a wrapped function in a way that 115 | changes the scope: 116 | 117 | ```c++ 118 | // Fine, because no wrapping is done. 119 | std::function f() { 120 | MyLogger capture_messages; 121 | return [] { MyLogger::Log("f was called"); }; 122 | } 123 | 124 | // Fine, because no instrumentation goes out of scope. 125 | std::function g() { 126 | return ThreadCrosser::WrapCall([] { MyLogger::Log("g was called"); }); 127 | } 128 | 129 | // DANGER! capture_messages goes out of scope, invalidating the function. 130 | std::function h() { 131 | MyLogger capture_messages; 132 | return ThreadCrosser::WrapCall([] { MyLogger::Log("h was called"); }); 133 | } 134 | 135 | void Execute() { 136 | f()(); // Fine. 137 | g()(); // Fine. 138 | h()(); // SIGSEGV! 139 | 140 | // Fine. g captures capture_messages, but capture_messages doesn't go out of 141 | // scope until the worker thread is joined. 142 | MyLogger capture_messages; 143 | std::thread worker(g()); 144 | worker.join(); 145 | } 146 | ``` 147 | 148 | ## Quick Start 149 | 150 | Instrumenting a project has four steps. These assume that your project is 151 | already functional, and is just lacking instrumentation. 152 | 153 | 1. Create an instrumentation class to contain the state to be shared. 154 | 2. Instrument your project with logging points, tracing points, or mocking 155 | substitutions, depending on which you implemented. 156 | 3. Where control is passed between threads, e.g., creating a thread or passing 157 | a callback between threads, use the logic in `ThreadCrosser` to ensure that 158 | the instrumentation crosses threads. 159 | 4. As needed, instantiate the instrumentation class(es) to enable the 160 | instrumentation within a specific scope. 161 | 162 | Complexity estimates below are estimates of how much additional work will be 163 | necessary as your project grows. 164 | 165 | ### Step 1: Instrumentation Class [`O(1)`] 166 | 167 | The instrumentation class(es) will generally be written once and then left 168 | alone. They might also be general enough for use in multiple projects. 169 | 170 | ```c++ 171 | #include 172 | #include 173 | #include 174 | #include 175 | #include "thread-capture.h" 176 | 177 | // This class provides the instrumentation logic, both at the point the 178 | // instrumentation is used (e.g., logging points) and where it is enabled (e.g., 179 | // log-capture points.) Note that instances of ThreadCapture cannot be moved, 180 | // copied, or dynamically allocated. 181 | class Logger : public capture_thread::ThreadCapture { 182 | public: 183 | Logger() : cross_and_capture_to_(this) {} 184 | 185 | // The static API is used at the instrumentation points. It will often be a 186 | // no-op if no instrumentation is in scope. 187 | static void Log(const std::string& line) { 188 | // GetCurrent() provides the instrumentation currently in scope, and is 189 | // always thread-safe and repeatable. The implementation of the 190 | // instrumentation must be explicitly made thread-safe, however. 191 | if (GetCurrent()) { 192 | GetCurrent()->LogLine(line); 193 | } else { 194 | std::cerr << "Not captured: \"" << line << "\"" << std::endl; 195 | } 196 | } 197 | 198 | // The non-static public API allows the creator of the instrumentation object 199 | // to access its contents. This is only necessary when the instrumentation is 200 | // gathering information, as opposed to propagating information. 201 | std::list GetLines() { 202 | std::lock_guard lock(lock_); 203 | return lines_; 204 | } 205 | 206 | private: 207 | // The private implementation applies to the instrumentation only when it's in 208 | // scope. This does not need to exactly mirror the static API, and in fact 209 | // only needs to differentiate between default and override behaviors. 210 | void LogLine(const std::string& line) { 211 | std::lock_guard lock(lock_); 212 | lines_.emplace_back(line); 213 | } 214 | 215 | std::mutex lock_; 216 | std::list lines_; 217 | // Add an AutoThreadCrosser to ensure that scoping is handled correctly. If 218 | // you absolutely don't want the instrumentation crossing threads, use 219 | // ScopedCapture instead. Always initialize with `this`. 220 | const AutoThreadCrosser cross_and_capture_to_; 221 | }; 222 | ``` 223 | 224 | ### Step 2: Instrument the Code [`O(n)`] 225 | 226 | Instrumenting the code with your new instrumentation class will generally 227 | consist of one-line additions throughout the code. There will often be a large 228 | number of instrumentation points in the code. 229 | 230 | ```c++ 231 | // #include the header for your instrumentation class. 232 | 233 | // This function already exists in your code, and performs some sort of work for 234 | // which you want to use the instrumentation. 235 | void MyExistingFunction() { 236 | // Add calls to the static API where you need access to the instrumentation. 237 | Logger::Log("MyExistingFunction called"); 238 | } 239 | ``` 240 | 241 | ### Step 3: Cross Threads [`O(log n)`] 242 | 243 | Crossing threads is necessary when the process you are tracking splits work 244 | among multiple threads. The complexity here depends on both what you consider a 245 | single task (e.g., processing a query) and how that work is split among threads. 246 | 247 | ```c++ 248 | #include 249 | #include "thread-crosser.h" 250 | 251 | // (You don't need to #include the header for your instrumentation class here.) 252 | 253 | // This function already exists in your code, and parallelizes some 254 | // functionality that needs to use the instrumentation, but doesn't need to use 255 | // the instrumentation itself. 256 | void ParallelizeWork() { 257 | // Previously, the code just created a thread. 258 | // std::thread worker(&MyExistingFunction); 259 | 260 | // To pass along the instrumentation, wrap the thread with WrapCall. Also use 261 | // WrapCall when passing work to a worker thread, e.g., a thread pool. 262 | std::thread worker( 263 | capture_thread::ThreadCrosser::WrapCall(&MyExistingFunction)); 264 | worker.join(); 265 | } 266 | ``` 267 | 268 | ### Step 4: Enable Instrumentation [`O(1)`] 269 | 270 | Your instrumentation must have default behavior that makes sense when the 271 | instrumentation is not enabled. Instrumentation can only be enabled by 272 | instantiating the implementation, and will only be available until that instance 273 | goes out of scope. There should be *very few* instantiation points (i.e., 274 | usually just one per instrumentation type) in your code. 275 | 276 | ```c++ 277 | // #include the header for your instrumentation class. 278 | 279 | int main() { 280 | // If no instrumentation is in scope, the default behavior of the static API 281 | // is used where instrumentation calls are made. In this case, this will just 282 | // print the line to std::cerr. 283 | ParallelizeWork(); 284 | 285 | // To make the instrumentation available within a given scope, just 286 | // instantiate your class. The framework will take care of the rest. 287 | Logger logger; 288 | 289 | // Since a Logger is in scope, the line will be captured to that instance, 290 | // rather than the default behavior of printing to std::cerr. 291 | ParallelizeWork(); 292 | 293 | // In this case the instrumentation captures data, which is now available in 294 | // the local instance. 295 | for (const std::string& line : logger.GetLines()) { 296 | std::cerr << "The logger captured: \"" << line << "\"" << std::endl; 297 | } 298 | } 299 | ``` 300 | 301 | The instantiation point will depend on the semantics you are going for. For 302 | example, if you are mocking, you might only instantiate the instrumentation in 303 | unit tests, and use the default behavior in the released code. 304 | 305 | ## Contributing 306 | 307 | See [`CONTRIBUTING.md`](CONTRIBUTING.md) for guidelines. All contributions must 308 | follow the Google C++ style guide at 309 | https://google.github.io/styleguide/cppguide.html. **Contributions should be 310 | made to the [`current`][current] branch, which will periodically be merged with 311 | [`master`][master] after a more thorough review.** 312 | 313 | [google/capture-thread]: https://github.com/google/capture-thread 314 | [master]: https://github.com/google/capture-thread/tree/master 315 | [current]: https://github.com/google/capture-thread/tree/current 316 | [instrumentation]: https://en.wikipedia.org/wiki/Instrumentation_(computer_programming) 317 | -------------------------------------------------------------------------------- /archive.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(dirname "$0")" || exit 4 | 5 | date=${1-now} 6 | 7 | filename="$(basename $(pwd))-$(date +%Y%m%d -d "$date").tar.gz" 8 | 9 | git ls-files | tar -cvvzf "$filename" -T - 10 | -------------------------------------------------------------------------------- /common/callback-queue.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #include "callback-queue.h" 20 | 21 | namespace capture_thread { 22 | namespace testing { 23 | 24 | void CallbackQueue::Push(std::function callback) { 25 | std::lock_guard lock(queue_lock_); 26 | if (!terminated_) { 27 | queue_.push(std::move(callback)); 28 | condition_.notify_all(); 29 | } 30 | } 31 | 32 | bool CallbackQueue::PopAndCall() { 33 | std::unique_lock lock(queue_lock_); 34 | while (!terminated_ && (!active_ || queue_.empty())) { 35 | condition_.wait(lock); 36 | } 37 | if (terminated_) { 38 | return false; 39 | } else { 40 | const auto callback = queue_.front(); 41 | ++pending_; 42 | queue_.pop(); 43 | lock.unlock(); 44 | if (callback) { 45 | callback(); 46 | } 47 | lock.lock(); 48 | --pending_; 49 | condition_.notify_all(); 50 | return true; 51 | } 52 | } 53 | 54 | void CallbackQueue::WaitUntilEmpty() { 55 | std::unique_lock lock(queue_lock_); 56 | while (!terminated_ && (!queue_.empty() || pending_ > 0)) { 57 | condition_.wait(lock); 58 | } 59 | } 60 | 61 | void CallbackQueue::Terminate() { 62 | std::lock_guard lock(queue_lock_); 63 | terminated_ = true; 64 | condition_.notify_all(); 65 | } 66 | 67 | void CallbackQueue::Activate() { 68 | std::lock_guard lock(queue_lock_); 69 | active_ = true; 70 | condition_.notify_all(); 71 | } 72 | 73 | } // namespace testing 74 | } // namespace capture_thread 75 | -------------------------------------------------------------------------------- /common/callback-queue.h: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #ifndef CALLBACK_QUEUE_H_ 20 | #define CALLBACK_QUEUE_H_ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace capture_thread { 28 | namespace testing { 29 | 30 | // Queues and executes callbacks. 31 | class CallbackQueue { 32 | public: 33 | // If active is false, constructs the queue in a paused state. Use Activate() 34 | // to start the queue. 35 | CallbackQueue(bool active = true) : active_(active) {} 36 | 37 | void Push(std::function callback); 38 | 39 | // Blocks for a callback to execute, then pops and executes it. Does not block 40 | // other callers while executing the callback. Returns false if the queue has 41 | // been terminated. 42 | bool PopAndCall(); 43 | 44 | void WaitUntilEmpty(); 45 | void Activate(); 46 | 47 | // Informs all callers to stop using the queue. No further callbacks will be 48 | // executed, even if the queue is non-empty. Makes Push a no-op. 49 | void Terminate(); 50 | 51 | private: 52 | std::mutex queue_lock_; 53 | std::condition_variable condition_; 54 | int pending_ = 0; 55 | bool terminated_ = false; 56 | bool active_; 57 | std::queue> queue_; 58 | }; 59 | 60 | } // namespace testing 61 | } // namespace capture_thread 62 | 63 | #endif // CALLBACK_QUEUE_H_ 64 | -------------------------------------------------------------------------------- /common/log-text.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #include "log-text.h" 20 | 21 | namespace capture_thread { 22 | namespace testing { 23 | 24 | // static 25 | void LogText::Log(std::string line) { 26 | if (GetCurrent()) { 27 | GetCurrent()->LogLine(std::move(line)); 28 | } 29 | } 30 | 31 | std::list LogTextMultiThread::GetLines() { 32 | std::lock_guard lock(data_lock_); 33 | return lines_; 34 | } 35 | 36 | void LogTextMultiThread::LogLine(std::string line) { 37 | std::lock_guard lock(data_lock_); 38 | lines_.emplace_back(std::move(line)); 39 | } 40 | 41 | } // namespace testing 42 | } // namespace capture_thread 43 | -------------------------------------------------------------------------------- /common/log-text.h: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #ifndef LOG_TEXT_H_ 20 | #define LOG_TEXT_H_ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include "thread-capture.h" 27 | #include "thread-crosser.h" 28 | 29 | namespace capture_thread { 30 | namespace testing { 31 | 32 | // Captures text log entries. 33 | class LogText : public ThreadCapture { 34 | public: 35 | static void Log(std::string line); 36 | 37 | // Allows callers to manually cross threads. 38 | using ThreadCapture::ThreadBridge; 39 | using ThreadCapture::CrossThreads; 40 | 41 | protected: 42 | LogText() = default; 43 | virtual ~LogText() = default; 44 | 45 | virtual void LogLine(std::string line) = 0; 46 | }; 47 | 48 | // Captures text log entries, without automatic thread crossing. 49 | class LogTextSingleThread : public LogText { 50 | public: 51 | LogTextSingleThread() : capture_to_(this) {} 52 | 53 | const std::list& GetLines() { return lines_; } 54 | 55 | private: 56 | void LogLine(std::string line) override { 57 | lines_.emplace_back(std::move(line)); 58 | } 59 | 60 | std::list lines_; 61 | const ScopedCapture capture_to_; 62 | }; 63 | 64 | // Captures text log entries, with automatic thread crossing. 65 | class LogTextMultiThread : public LogText { 66 | public: 67 | LogTextMultiThread() : cross_and_capture_to_(this) {} 68 | 69 | std::list GetLines(); 70 | 71 | private: 72 | void LogLine(std::string line) override; 73 | 74 | std::mutex data_lock_; 75 | std::list lines_; 76 | const AutoThreadCrosser cross_and_capture_to_; 77 | }; 78 | 79 | } // namespace testing 80 | } // namespace capture_thread 81 | 82 | #endif // LOG_TEXT_H_ 83 | -------------------------------------------------------------------------------- /common/log-values.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #include "log-values.h" 20 | 21 | namespace capture_thread { 22 | namespace testing { 23 | 24 | // static 25 | void LogValues::Count(int count) { 26 | if (GetCurrent()) { 27 | GetCurrent()->LogCount(count); 28 | } 29 | } 30 | 31 | std::list LogValuesMultiThread::GetCounts() { 32 | std::lock_guard lock(data_lock_); 33 | return counts_; 34 | } 35 | 36 | void LogValuesMultiThread::LogCount(int count) { 37 | std::lock_guard lock(data_lock_); 38 | counts_.emplace_back(count); 39 | } 40 | 41 | } // namespace testing 42 | } // namespace capture_thread 43 | -------------------------------------------------------------------------------- /common/log-values.h: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #ifndef LOG_VALUES_H_ 20 | #define LOG_VALUES_H_ 21 | 22 | #include 23 | #include 24 | 25 | #include "thread-capture.h" 26 | #include "thread-crosser.h" 27 | 28 | namespace capture_thread { 29 | namespace testing { 30 | 31 | // Captures numerical log entries. 32 | class LogValues : public ThreadCapture { 33 | public: 34 | static void Count(int count); 35 | 36 | protected: 37 | LogValues() = default; 38 | virtual ~LogValues() = default; 39 | 40 | virtual void LogCount(int count) = 0; 41 | }; 42 | 43 | // Captures numerical log entries, without automatic thread crossing. 44 | class LogValuesSingleThread : public LogValues { 45 | public: 46 | LogValuesSingleThread() : capture_to_(this) {} 47 | 48 | const std::list& GetCounts() { return counts_; } 49 | 50 | private: 51 | void LogCount(int count) { counts_.emplace_back(count); } 52 | 53 | std::list counts_; 54 | const ScopedCapture capture_to_; 55 | }; 56 | 57 | // Captures numerical log entries, with automatic thread crossing. 58 | class LogValuesMultiThread : public LogValues { 59 | public: 60 | LogValuesMultiThread() : cross_and_capture_to_(this) {} 61 | 62 | std::list GetCounts(); 63 | 64 | private: 65 | void LogCount(int count); 66 | 67 | std::mutex data_lock_; 68 | std::list counts_; 69 | const AutoThreadCrosser cross_and_capture_to_; 70 | }; 71 | 72 | } // namespace testing 73 | } // namespace capture_thread 74 | 75 | #endif // LOG_VALUES_H_ 76 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # Logging Demo 2 | 3 | This directory contains a small demonstration of a logging system that makes use 4 | of user-specified scope tags for tracing. For testing purposes, it also contains 5 | logic to capture logged content. See [`main.cc`](main.cc) for the main program 6 | and [`test.cc`](test.cc) for the corresponding unit test. 7 | -------------------------------------------------------------------------------- /demo/logging.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #include 20 | 21 | #include "logging.h" 22 | #include "tracing.h" 23 | 24 | namespace demo { 25 | 26 | Logging::LogLine::LogLine() : capture_(GetCurrent()) { 27 | const std::string context = Tracing::GetContext(); 28 | if (!context.empty()) { 29 | *this << context << ": "; 30 | } else { 31 | *this << "(unknown context): "; 32 | } 33 | } 34 | 35 | Logging::LogLine::~LogLine() { 36 | *this << '\n'; 37 | if (capture_) { 38 | capture_->AppendLine(output_.str()); 39 | } else { 40 | DefaultAppendLine(output_.str()); 41 | } 42 | } 43 | 44 | // static 45 | void Logging::DefaultAppendLine(const std::string& line) { std::cerr << line; } 46 | 47 | std::list CaptureLogging::CopyLines() { 48 | std::lock_guard lock(data_lock_); 49 | return lines_; 50 | } 51 | 52 | void CaptureLogging::AppendLine(const std::string& line) { 53 | { 54 | std::lock_guard lock(data_lock_); 55 | lines_.emplace_back(line); 56 | } 57 | DefaultAppendLine(line); 58 | } 59 | 60 | } // namespace demo 61 | -------------------------------------------------------------------------------- /demo/logging.h: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #ifndef LOGGING_H_ 20 | #define LOGGING_H_ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "thread-capture.h" 28 | 29 | namespace demo { 30 | 31 | // Provides a text-logging mechanism. By default sends data to stderr. Use 32 | // CaptureLogging to capture logged data. 33 | class Logging : public capture_thread::ThreadCapture { 34 | public: 35 | // Formats and logs a line. Operates as a std::ostream. For example: 36 | // 37 | // Logging::LogLine() << "Log message."; 38 | class LogLine { 39 | public: 40 | LogLine(); 41 | ~LogLine(); 42 | 43 | template 44 | LogLine& operator<<(Type value) { 45 | static_cast(output_) << value; 46 | return *this; 47 | } 48 | 49 | private: 50 | Logging* const capture_; 51 | std::ostringstream output_; 52 | }; 53 | 54 | protected: 55 | Logging() = default; 56 | virtual ~Logging() = default; 57 | 58 | virtual void AppendLine(const std::string& line) = 0; 59 | 60 | static void DefaultAppendLine(const std::string& line); 61 | }; 62 | 63 | // Captures lines logged with Logging while in scope. 64 | class CaptureLogging : public Logging { 65 | public: 66 | CaptureLogging() : cross_and_capture_to_(this) {} 67 | 68 | // Returns a copy of all lines captured. 69 | std::list CopyLines(); 70 | 71 | protected: 72 | void AppendLine(const std::string& line) override; 73 | 74 | private: 75 | std::mutex data_lock_; 76 | std::list lines_; 77 | const AutoThreadCrosser cross_and_capture_to_; 78 | }; 79 | 80 | } // namespace demo 81 | 82 | #endif // LOGGING_H_ 83 | -------------------------------------------------------------------------------- /demo/main.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "callback-queue.h" 27 | #include "logging.h" 28 | #include "tracing.h" 29 | 30 | using capture_thread::ThreadCrosser; 31 | using capture_thread::testing::CallbackQueue; 32 | using demo::CaptureLogging; 33 | using demo::Formatter; 34 | using demo::Logging; 35 | using demo::Tracing; 36 | 37 | namespace { 38 | 39 | // A unit of computation that can be parallelized. 40 | void Compute(int value) { 41 | Tracing context(__func__); 42 | Logging::LogLine() << "Computing " << value; 43 | std::this_thread::sleep_for(std::chrono::milliseconds(value)); 44 | } 45 | 46 | // A worker thread that executes whatever is in the queue. 47 | void QueueThread(int index, CallbackQueue* queue) { 48 | assert(queue); 49 | Tracing context((Formatter() << __func__ << '[' << index << ']').String()); 50 | Logging::LogLine() << "Thread starting"; 51 | while (queue->PopAndCall()) { 52 | } 53 | Logging::LogLine() << "Thread stopping"; 54 | } 55 | 56 | // Worker threads don't need to be wrapped with ThreadCrosser::WrapCall if they 57 | // are just executing callbacks from a queue, but it can be helpful, e.g., for 58 | // tracing purposes. 59 | std::unique_ptr NewThread(std::function callback) { 60 | return std::unique_ptr( 61 | new std::thread(ThreadCrosser::WrapCall(std::move(callback)))); 62 | } 63 | 64 | } // namespace 65 | 66 | int main() { 67 | Tracing context(__func__); 68 | 69 | // Queue for passing work from the main thread to the worker threads. Created 70 | // in a paused state. 71 | CallbackQueue queue(false /*active*/); 72 | 73 | for (int i = 0; i < 10; ++i) { 74 | // One callback per unit of work that can be parallelized. 75 | queue.Push(ThreadCrosser::WrapCall(std::bind(&Compute, i))); 76 | } 77 | 78 | std::list> threads; 79 | for (int i = 0; i < 3; ++i) { 80 | // An arbitrary number of threads. 81 | threads.emplace_back(NewThread(std::bind(&QueueThread, i, &queue))); 82 | } 83 | 84 | // Perform the computations. 85 | 86 | queue.Activate(); 87 | queue.WaitUntilEmpty(); 88 | queue.Terminate(); 89 | 90 | for (const auto& thread : threads) { 91 | thread->join(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /demo/test.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include "callback-queue.h" 25 | #include "logging.h" 26 | #include "tracing.h" 27 | 28 | using capture_thread::ThreadCrosser; 29 | using capture_thread::testing::CallbackQueue; 30 | using testing::ElementsAre; 31 | 32 | namespace demo { 33 | 34 | TEST(DemoTest, IntegrationTest) { 35 | CaptureLogging logger; 36 | Tracing context("test"); 37 | CallbackQueue queue; 38 | 39 | for (int i = 0; i < 3; ++i) { 40 | queue.Push(ThreadCrosser::WrapCall([i] { 41 | Tracing context("thread"); 42 | Logging::LogLine() << "call " << i; 43 | })); 44 | } 45 | 46 | std::thread worker(ThreadCrosser::WrapCall([&queue] { 47 | Tracing context("worker"); 48 | Logging::LogLine() << "start"; 49 | while (queue.PopAndCall()) { 50 | } 51 | Logging::LogLine() << "stop"; 52 | })); 53 | 54 | queue.WaitUntilEmpty(); 55 | queue.Terminate(); 56 | 57 | worker.join(); 58 | 59 | EXPECT_THAT(logger.CopyLines(), 60 | ElementsAre("test:worker: start\n", "test:thread: call 0\n", 61 | "test:thread: call 1\n", "test:thread: call 2\n", 62 | "test:worker: stop\n")); 63 | } 64 | 65 | } // namespace demo 66 | 67 | int main(int argc, char *argv[]) { 68 | testing::InitGoogleTest(&argc, argv); 69 | return RUN_ALL_TESTS(); 70 | } 71 | -------------------------------------------------------------------------------- /demo/tracing.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #include 20 | #include 21 | 22 | #include "tracing.h" 23 | 24 | namespace demo { 25 | 26 | // static 27 | std::string Tracing::GetContext() { 28 | Formatter formatter; 29 | ReverseTrace(GetCurrent(), &formatter); 30 | return formatter.String(); 31 | } 32 | 33 | // static 34 | void Tracing::ReverseTrace(const Tracing* tracer, Formatter* formatter) { 35 | assert(formatter); 36 | if (tracer) { 37 | const auto previous = tracer->cross_and_capture_to_.Previous(); 38 | ReverseTrace(previous, formatter); 39 | if (previous) { 40 | *formatter << ":"; 41 | } 42 | *formatter << tracer->name(); 43 | } 44 | } 45 | 46 | } // namespace demo 47 | -------------------------------------------------------------------------------- /demo/tracing.h: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #ifndef TRACING_H_ 20 | #define TRACING_H_ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "thread-capture.h" 28 | 29 | namespace demo { 30 | 31 | // Formats a string much like std::ostringstream, but returns a type that can 32 | // be directly converted to a string. For example: 33 | // 34 | // std::string message = (Formatter() << "number: " << 1).String(); 35 | class Formatter { 36 | public: 37 | std::string String() const { return output_.str(); } 38 | 39 | template 40 | Formatter& operator<<(Type value) { 41 | static_cast(output_) << value; 42 | return *this; 43 | } 44 | 45 | private: 46 | std::ostringstream output_; 47 | }; 48 | 49 | // Adds a named tracing scope while the object is in scope. 50 | class Tracing : public capture_thread::ThreadCapture { 51 | public: 52 | explicit Tracing(std::string name) 53 | : name_(std::move(name)), cross_and_capture_to_(this) {} 54 | 55 | // Returns the current context as a ":"-joined concatenation of the names of 56 | // the current Tracing objects in scope. For example: 57 | // 58 | // Tracing scope1("scope1"); 59 | // Tracing scope2("scope2"); 60 | // std::cerr << Tracing::GetContext(); // "scope1:scope2" 61 | static std::string GetContext(); 62 | 63 | private: 64 | const std::string& name() const { return name_; } 65 | 66 | static void ReverseTrace(const Tracing* tracer, Formatter* formatter); 67 | 68 | const std::string name_; 69 | const AutoThreadCrosser cross_and_capture_to_; 70 | }; 71 | 72 | } // namespace demo 73 | 74 | #endif // TRACING_H_ 75 | -------------------------------------------------------------------------------- /example/async.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // This example demonstrates how you might implement asynchronous reporting, 20 | // e.g., in cases where the reporting process is too costly to be done 21 | // synchronously. The callers of the reporting API aren't blocked by the sending 22 | // of the report, which is handled separately by a dedicated reporting thread. 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "thread-capture.h" 37 | 38 | using capture_thread::ThreadCapture; 39 | 40 | // Captures reports for external reporting. Something like this might be used to 41 | // report usage, access, or performance information to an external resource, 42 | // e.g., remote storage or an auditing server. 43 | class Reporter : public ThreadCapture { 44 | public: 45 | // Arbitrary content to report, which would generally be more structured. 46 | using Report = std::list; 47 | 48 | // Sends the report, if a reporter is in scope; otherwise does nothing. Note 49 | // that noting about this class requires reporting to be asynchronous. 50 | static void Send(Report report) { 51 | if (GetCurrent()) { 52 | GetCurrent()->SendReport(std::move(report)); 53 | } 54 | } 55 | 56 | protected: 57 | Reporter() = default; 58 | virtual ~Reporter() = default; 59 | 60 | // Actually performs the reporting. 61 | virtual void SendReport(Report report) = 0; 62 | }; 63 | 64 | // Captures reports, but writes them asynchronously to avoid blocking. 65 | class ReportAsync : public Reporter { 66 | public: 67 | ReportAsync() : cross_and_capture_to_(this) {} 68 | 69 | ~ReportAsync() { 70 | { 71 | std::lock_guard lock(queue_lock_); 72 | terminated_ = true; 73 | queue_wait_.notify_all(); 74 | } 75 | if (reporter_thread_) { 76 | std::cerr << "Waiting for reporter thread to finish..." << std::endl; 77 | reporter_thread_->join(); 78 | std::cerr << "Reporter thread finished." << std::endl; 79 | } 80 | } 81 | 82 | protected: 83 | void SendReport(Report report) override { 84 | std::lock_guard lock(queue_lock_); 85 | if (!terminated_) { 86 | if (!reporter_thread_) { 87 | StartThread(); 88 | } 89 | // This assumes that the rate at which SendReport is called does not 90 | // exceed the rate at which reports can be written, on average. Since that 91 | // isn't always a safe assumption, it might be helpful to limit the queue 92 | // size and either wait or drop reports if that limit is exceeded here. 93 | queue_.push(std::move(report)); 94 | queue_wait_.notify_all(); 95 | } 96 | } 97 | 98 | private: 99 | // The thread is started lazily. 100 | void StartThread() { 101 | reporter_thread_.reset( 102 | new std::thread(std::bind(&ReportAsync::ReporterThread, this))); 103 | } 104 | 105 | // Monitors the queue, and bulk-writes new entries whenever possible. 106 | void ReporterThread() { 107 | bool terminated = false; 108 | std::queue working_queue; 109 | while (!terminated) { 110 | std::unique_lock lock(queue_lock_); 111 | // Wait for new entries to report. 112 | while (!(terminated = terminated_) && queue_.empty()) { 113 | queue_wait_.wait(lock); 114 | } 115 | assert(working_queue.empty()); 116 | // Grab them all at once so that the lock can be released, thereby 117 | // minimizing the amount of time callers are blocked. 118 | working_queue.swap(queue_); 119 | lock.unlock(); 120 | // Process the entries. 121 | while (!working_queue.empty()) { 122 | WriteToStorage(working_queue.front()); 123 | working_queue.pop(); 124 | } 125 | } 126 | } 127 | 128 | void WriteToStorage(const Report& report) { 129 | // Simulates an expensive write operation. 130 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 131 | for (const auto& entry : report) { 132 | std::cout << entry << std::endl; 133 | } 134 | } 135 | 136 | bool terminated_ = false; 137 | std::mutex queue_lock_; 138 | std::condition_variable queue_wait_; 139 | std::queue queue_; 140 | std::unique_ptr reporter_thread_; 141 | const AutoThreadCrosser cross_and_capture_to_; 142 | }; 143 | 144 | // Simulates a service that process external requests. 145 | class DataService { 146 | public: 147 | DataService() { std::cerr << "Starting DataService." << std::endl; } 148 | 149 | ~DataService() { std::cerr << "Stopping DataService." << std::endl; } 150 | 151 | // An arbitrary latency-sensitive operation that should not be blocked by the 152 | // reporting process. 153 | void AccessSomeResources(int resource_number) { 154 | Reporter::Report report; 155 | std::ostringstream formatted; 156 | formatted << "resource accessed: " << resource_number; 157 | report.emplace_back(formatted.str()); 158 | Reporter::Send(std::move(report)); 159 | } 160 | }; 161 | 162 | int main() { 163 | // Enable reporting globally. 164 | ReportAsync reporter; 165 | 166 | DataService service; 167 | for (int i = 0; i < 10; ++i) { 168 | // Simulates a latency-sensitive request to the service. 169 | service.AccessSomeResources(i); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /example/connection.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // This example demonstrates sharing a client connection (e.g., a socket) within 20 | // the thread so that it doesn't need to be passed around to all functions that 21 | // require access to it. Keep in mind that this design pattern could be 22 | // problematic if multiple threads need to access the client. (This essentially 23 | // turns the current thread into a singleton.) 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include "thread-capture.h" 30 | 31 | using capture_thread::ThreadCapture; 32 | 33 | // Serves as a singleton client connection within the thread. The default 34 | // behavior is to act as a disconnected client. 35 | class ClientConnection : public ThreadCapture { 36 | public: 37 | static bool IsActive() { 38 | if (GetCurrent()) { 39 | return GetCurrent()->CheckConnection(); 40 | } else { 41 | return false; 42 | } 43 | } 44 | 45 | static bool Send(const std::string& message) { 46 | if (GetCurrent()) { 47 | return GetCurrent()->SendMessage(message); 48 | } else { 49 | return false; 50 | } 51 | } 52 | 53 | static bool Receive(std::string* message) { 54 | if (GetCurrent()) { 55 | return GetCurrent()->ReceiveMessage(message); 56 | } else { 57 | return false; 58 | } 59 | } 60 | 61 | protected: 62 | virtual bool CheckConnection() = 0; 63 | virtual bool SendMessage(const std::string& message) = 0; 64 | virtual bool ReceiveMessage(std::string* message) = 0; 65 | }; 66 | 67 | // Provides client functionality from stdin and stdout while in scope. 68 | class ClientFromStandardStreams : public ClientConnection { 69 | public: 70 | ClientFromStandardStreams() : capture_to_(this) { 71 | std::cerr << "Opening ClientFromStandardStreams connection." << std::endl; 72 | } 73 | 74 | ~ClientFromStandardStreams() { 75 | std::cerr << "Closing ClientFromStandardStreams connection." << std::endl; 76 | } 77 | 78 | protected: 79 | bool CheckConnection() { return !!std::cout && !!std::cin; } 80 | 81 | bool SendMessage(const std::string& message) { 82 | return !!(std::cout << "*** Message: " << message << " ***" << std::endl); 83 | } 84 | 85 | bool ReceiveMessage(std::string* message) { 86 | assert(message); 87 | return !!std::getline(std::cin, *message, '\n'); 88 | } 89 | 90 | private: 91 | const ScopedCapture capture_to_; 92 | }; 93 | 94 | // This is a helper function to send to and recieve from the client. 95 | bool PromptForInfo(const std::string& prompt, std::string* response) { 96 | assert(response); 97 | while (ClientConnection::IsActive()) { 98 | if (!ClientConnection::Send(prompt)) { 99 | return false; 100 | } 101 | if (!ClientConnection::Receive(response)) { 102 | return false; 103 | } 104 | if (!response->empty()) { 105 | return true; 106 | } 107 | } 108 | return false; 109 | } 110 | 111 | // This is the main routine to handle the lifetime of the connection. 112 | void HandleConnection() { 113 | std::string name; 114 | if (!PromptForInfo("What is your name?", &name)) { 115 | std::cerr << "Connection closed without providing a name." << std::endl; 116 | } else { 117 | ClientConnection::Send("Your name is supposedly \"" + name + "\"."); 118 | } 119 | } 120 | 121 | int main() { 122 | // While in scope, client will serve as the ClientConnection for all calls. 123 | ClientFromStandardStreams client; 124 | HandleConnection(); 125 | } 126 | -------------------------------------------------------------------------------- /example/function.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // This is an example of wrapping a general function that takes arguments and 20 | // has a non-void return. 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "thread-capture.h" 31 | #include "thread-crosser.h" 32 | 33 | using capture_thread::ThreadCapture; 34 | using capture_thread::ThreadCrosser; 35 | 36 | // (See threaded.cc for comments.) 37 | class LogText : public ThreadCapture { 38 | public: 39 | LogText() : cross_and_capture_to_(this) {} 40 | 41 | static void Log(std::string line) { 42 | if (GetCurrent()) { 43 | GetCurrent()->LogLine(std::move(line)); 44 | } else { 45 | std::cerr << "*** Not captured: \"" << line << "\" ***" << std::endl; 46 | } 47 | } 48 | 49 | std::list CopyLines() { 50 | std::lock_guard lock(data_lock_); 51 | return lines_; 52 | } 53 | 54 | private: 55 | void LogLine(std::string line) { 56 | std::lock_guard lock(data_lock_); 57 | lines_.emplace_back(std::move(line)); 58 | } 59 | 60 | std::mutex data_lock_; 61 | std::list lines_; 62 | const AutoThreadCrosser cross_and_capture_to_; 63 | }; 64 | 65 | // A function whose calls we want to keep track of. 66 | bool LessThan(const std::string& left, const std::string& right) { 67 | if (left < right) { 68 | LogText::Log("\"" + left + "\" < \"" + right + "\""); 69 | return true; 70 | } else { 71 | LogText::Log("\"" + left + "\" >= \"" + right + "\""); 72 | return false; 73 | } 74 | } 75 | 76 | // This simulates a sorting function that might use multiple threads, as a 77 | // hidden implementation detail. 78 | template 79 | void ThreadedSort(Iterator begin, Iterator end, const Compare& compare) { 80 | std::thread worker( 81 | [&begin, &end, &compare] { std::sort(begin, end, compare); }); 82 | worker.join(); 83 | } 84 | 85 | int main() { 86 | const std::vector words{ 87 | "this", "is", "a", "list", "of", "words", "to", "sort", 88 | }; 89 | 90 | std::vector words_copy; 91 | 92 | // Captures log entries while in scope. 93 | LogText logger; 94 | 95 | words_copy = words; 96 | // Here we don't know for sure if LessThan is going to be called in this 97 | // thread or not. 98 | ThreadedSort(words_copy.begin(), words_copy.end(), &LessThan); 99 | 100 | words_copy = words; 101 | // ThreadCrosser::WrapFunction ensures that the scope is captured, regardless 102 | // of how ThreadedSort splits up the process. 103 | ThreadedSort(words_copy.begin(), words_copy.end(), 104 | ThreadCrosser::WrapFunction(&LessThan)); 105 | 106 | for (const auto& line : logger.CopyLines()) { 107 | std::cerr << "Captured: " << line << std::endl; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /example/inherit.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // ***Although this example is technically safe, its use is a potential 20 | // indicator of bad design, due to scope ambiguity.*** As such, please treat 21 | // this example as experimental, and seriously reconsider your design before 22 | // using this design pattern. 23 | 24 | // This is a proof-of-concept example of how you might inherit the object that 25 | // is currently being captured to. For example your API might have multiple 26 | // entry points that delegate to each other, but that requires exactly one 27 | // report per call made to the API. 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include "thread-capture.h" 34 | 35 | using capture_thread::ThreadCapture; 36 | 37 | // (See simple.cc for general comments.) 38 | class LogText : public ThreadCapture { 39 | public: 40 | enum class InheritType { 41 | kNew, // A new logger should be created. 42 | kInherit, // Logging should be delegated to the existing logger. 43 | }; 44 | 45 | LogText(InheritType type) : type_(type), capture_to_(this) {} 46 | 47 | static void Log(std::string line) { 48 | if (GetCurrent()) { 49 | GetCurrent()->LogLine(std::move(line)); 50 | } 51 | } 52 | 53 | const std::list& GetLines() const { return Delegate()->lines_; } 54 | 55 | private: 56 | void LogLine(std::string line) { 57 | Delegate()->lines_.emplace_back(std::move(line)); 58 | } 59 | 60 | LogText* Delegate() { 61 | if (type_ == InheritType::kInherit && capture_to_.Previous()) { 62 | return capture_to_.Previous()->Delegate(); 63 | } 64 | return this; 65 | } 66 | 67 | const LogText* Delegate() const { 68 | if (type_ == InheritType::kInherit && capture_to_.Previous()) { 69 | return capture_to_.Previous()->Delegate(); 70 | } 71 | return this; 72 | } 73 | 74 | std::list lines_; 75 | const InheritType type_; 76 | const ScopedCapture capture_to_; 77 | }; 78 | 79 | void QueryHandler1(const std::string& query) { 80 | LogText logger(LogText::InheritType::kInherit); 81 | LogText::Log("QueryHandler1 called: " + query); 82 | for (const auto& line : logger.GetLines()) { 83 | std::cerr << "Available from QueryHandler1: \"" << line << "\"" 84 | << std::endl; 85 | } 86 | } 87 | 88 | void QueryHandler2(const std::string& query) { 89 | LogText logger(LogText::InheritType::kNew); 90 | LogText::Log("QueryHandler2 called: " + query); 91 | QueryHandler1(query + "!!!"); 92 | for (const auto& line : logger.GetLines()) { 93 | std::cerr << "Available from QueryHandler2: \"" << line << "\"" 94 | << std::endl; 95 | } 96 | } 97 | 98 | int main() { 99 | std::cerr << "Inherited logger used:" << std::endl; 100 | QueryHandler2("query"); 101 | std::cerr << "New logger used:" << std::endl; 102 | QueryHandler1("another query"); 103 | } 104 | -------------------------------------------------------------------------------- /example/limit.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // This is a proof-of-concept example of how the framework could be used to 20 | // limit the amount of effort expended by a computation. We do not expect this 21 | // to be a widely-used use-case. 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "thread-capture.h" 30 | 31 | using capture_thread::ThreadCapture; 32 | 33 | // The base class just provides an interface for tracking/reporting resources. 34 | class LimitEffort : public ThreadCapture { 35 | public: 36 | static bool ShouldContinue() { 37 | return GetCurrent() ? !GetCurrent()->LimitReached() : true; 38 | } 39 | 40 | static void Consume(int amount) { 41 | if (GetCurrent()) { 42 | GetCurrent()->DecrementResources(amount); 43 | } 44 | } 45 | 46 | protected: 47 | LimitEffort() = default; 48 | virtual ~LimitEffort() = default; 49 | 50 | virtual bool LimitReached() = 0; 51 | virtual void DecrementResources(int amount) {} 52 | 53 | // Don't add a ScopedCapture member to the base class, because that would make 54 | // it impossible to write an implementation that doesn't automatically capture 55 | // logging. 56 | }; 57 | 58 | // This implementation imposes a time-based limit. 59 | class LimitTime : public LimitEffort { 60 | public: 61 | explicit LimitTime(double seconds) 62 | : seconds_(seconds), 63 | start_time_(std::chrono::high_resolution_clock::now()), 64 | capture_to_(this) {} 65 | 66 | double ResourcesConsumed() const { 67 | const auto current_time = std::chrono::high_resolution_clock::now(); 68 | return std::chrono::duration_cast(current_time - 69 | start_time_) 70 | .count() / 71 | 1000000.0; 72 | } 73 | 74 | protected: 75 | bool LimitReached() override { return ResourcesConsumed() > seconds_; } 76 | 77 | private: 78 | const double seconds_; 79 | const std::chrono::high_resolution_clock::time_point start_time_; 80 | const ScopedCapture capture_to_; 81 | }; 82 | 83 | // This implementation imposes a counter-based limit. 84 | class LimitCount : public LimitEffort { 85 | public: 86 | explicit LimitCount(int count) : count_(count), capture_to_(this) {} 87 | 88 | int ResourcesRemaining() const { return count_; } 89 | 90 | protected: 91 | bool LimitReached() override { return ResourcesRemaining() <= 0; } 92 | 93 | void DecrementResources(int amount) override { count_ -= amount; } 94 | 95 | private: 96 | int count_; 97 | const ScopedCapture capture_to_; 98 | }; 99 | 100 | // Performs the actual work. It's important to remember that this function, and 101 | // its callers all the way up, need to do something reasonable when stopping 102 | // early. In this case, we're just counting. In other situations, you might have 103 | // a partial result of a computation, which itself might be an acceptable result 104 | // (e.g., an aborted search operation), or it might not be (e.g., a partially- 105 | // completed matrix multiplication.) 106 | void ResourceConsumingWorker() { 107 | for (int i = 0; i < 100 && LimitEffort::ShouldContinue(); ++i) { 108 | std::cerr << i << ' '; 109 | LimitEffort::Consume(i); 110 | std::this_thread::sleep_for(std::chrono::milliseconds(i)); 111 | } 112 | std::cerr << std::endl; 113 | } 114 | 115 | void ProcessByTime() { 116 | LimitTime limit(1.0); 117 | ResourceConsumingWorker(); 118 | std::cerr << "Resources consumed: " << limit.ResourcesConsumed() << std::endl; 119 | } 120 | 121 | void ProcessByCount() { 122 | LimitCount limit(500); 123 | ResourceConsumingWorker(); 124 | std::cerr << "Resources remaining: " << limit.ResourcesRemaining() 125 | << std::endl; 126 | } 127 | 128 | int main() { 129 | std::cerr << "Process with time limit..." << std::endl; 130 | ProcessByTime(); 131 | std::cerr << "Process with count limit..." << std::endl; 132 | ProcessByCount(); 133 | std::cerr << "Process without limit..." << std::endl; 134 | ResourceConsumingWorker(); 135 | } 136 | -------------------------------------------------------------------------------- /example/manual.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // This is an example of how you can limit the sharing of instrumentation across 20 | // threads to specific points where you explicitly allow it. This is more 21 | // efficient and makes the code easier to follow than automatic sharing, but it 22 | // doesn't scale well if you have multiple types of instrumentation. (See 23 | // threaded.cc for information about *automatic* sharing of instrumentation.) 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "thread-capture.h" 32 | #include "thread-crosser.h" 33 | 34 | using capture_thread::ThreadCapture; 35 | using capture_thread::ThreadCrosser; 36 | 37 | // (See simple.cc and threaded.cc for general comments.) 38 | class LogText : public ThreadCapture { 39 | public: 40 | LogText() : capture_to_(this) {} 41 | 42 | // By default, manually crossing threads is disallowed. To enable it for a 43 | // specific class, add the `using` declarations below to make these two 44 | // classes public. 45 | using ThreadCapture::ThreadBridge; 46 | using ThreadCapture::CrossThreads; 47 | 48 | static void Log(std::string line) { 49 | if (GetCurrent()) { 50 | GetCurrent()->LogLine(std::move(line)); 51 | } else { 52 | std::cerr << "*** Not captured: \"" << line << "\" ***" << std::endl; 53 | } 54 | } 55 | 56 | std::list CopyLines() { 57 | std::lock_guard lock(data_lock_); 58 | return lines_; 59 | } 60 | 61 | private: 62 | void LogLine(std::string line) { 63 | std::lock_guard lock(data_lock_); 64 | lines_.emplace_back(std::move(line)); 65 | } 66 | 67 | std::mutex data_lock_; 68 | std::list lines_; 69 | 70 | // Unlike AutoThreadCrosser, ScopedCapture prevents ThreadCrosser from 71 | // automatically sharing instrumentation across threads. 72 | const ScopedCapture capture_to_; 73 | }; 74 | 75 | int main() { 76 | LogText logger; 77 | 78 | // Instrumentation isn't shared by default. 79 | std::thread unlogged_thread1( 80 | [] { LogText::Log("Logging has not been passed on here."); }); 81 | unlogged_thread1.join(); 82 | 83 | // Since ScopedCapture is used instead of AutoThreadCrosser, ThreadCrosser:: 84 | // WrapCall has no effect on the LogText instrumentation. 85 | std::thread unlogged_thread2(ThreadCrosser::WrapCall([] { 86 | LogText::Log("Logging has not been passed on, even with WrapCall."); 87 | })); 88 | unlogged_thread2.join(); 89 | 90 | // Use ThreadBridge to create a bridge point. This captures the current scope 91 | // at the point it's instantiated; therefore, order matters! 92 | const LogText::ThreadBridge bridge; 93 | std::thread logged_thread([&bridge] { 94 | // Connect the threads via the bridge with CrossThreads. The ThreadBridge 95 | // must stay in scope in the other thread until the CrossThreads in this 96 | // thread goes out of scope. 97 | LogText::CrossThreads capture(bridge); 98 | LogText::Log("Logging has been manually passed on here."); 99 | }); 100 | logged_thread.join(); 101 | 102 | for (const auto& line : logger.CopyLines()) { 103 | std::cerr << "Captured: " << line << std::endl; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /example/mock.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // This example demonstrates how the framework can be used to mock resources 20 | // that are otherwise handled by non-trivial default functionality. 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "thread-capture.h" 30 | 31 | using capture_thread::ThreadCapture; 32 | 33 | // Handles the creation of file objects. Unless an instance of an implementation 34 | // is in scope, uses a default file-open operation. Implementations can override 35 | // if/how files are opened. 36 | class FileFactory : public ThreadCapture { 37 | public: 38 | // Returns an open file, or nullptr. 39 | static std::unique_ptr ReadFile(const std::string& filename) { 40 | if (GetCurrent()) { 41 | return GetCurrent()->GetReadStream(filename); 42 | } else { 43 | return std::unique_ptr(new std::ifstream(filename)); 44 | } 45 | } 46 | 47 | protected: 48 | FileFactory() = default; 49 | virtual ~FileFactory() = default; 50 | 51 | // Overrides how files are opened. 52 | virtual std::unique_ptr GetReadStream( 53 | const std::string& filename) = 0; 54 | }; 55 | 56 | // Replaces the default file-open behavior of FileFactory when in scope. 57 | class FileMocker : public FileFactory { 58 | public: 59 | FileMocker() : capture_to_(this) {} 60 | 61 | // Registers a string-backed mock file that's used in place of an actual file 62 | // on the filesystem. 63 | void MockFile(std::string filename, std::string content) { 64 | files_.emplace(std::move(filename), std::move(content)); 65 | } 66 | 67 | protected: 68 | std::unique_ptr GetReadStream( 69 | const std::string& filename) override { 70 | const auto mock = files_.find(filename); 71 | if (mock == files_.end()) { 72 | return nullptr; 73 | } else { 74 | return std::unique_ptr( 75 | new std::istringstream(mock->second)); 76 | } 77 | } 78 | 79 | private: 80 | std::unordered_map files_; 81 | const ScopedCapture capture_to_; 82 | }; 83 | 84 | // Some arbitrary function whose API we can't change to add mocking capability. 85 | int OpenConfigAndCountWords() { 86 | const auto input = FileFactory::ReadFile("CMakeLists.txt"); 87 | if (!input) { 88 | return -1; 89 | } else { 90 | int count = 0; 91 | std::string sink; 92 | while ((*input >> sink)) { 93 | ++count; 94 | } 95 | return count; 96 | } 97 | } 98 | 99 | int main() { 100 | std::cerr << "Word count *without* mock: " << OpenConfigAndCountWords() 101 | << std::endl; 102 | 103 | { 104 | // Only overrides FileFactory::ReadFile while in scope. 105 | FileMocker mocker; 106 | 107 | std::cerr << "Word count with missing file: " << OpenConfigAndCountWords() 108 | << std::endl; 109 | 110 | mocker.MockFile("CMakeLists.txt", "one two three"); 111 | std::cerr << "Word count *with* mock: " << OpenConfigAndCountWords() 112 | << std::endl; 113 | } 114 | 115 | std::cerr << "Word count *without* mock: " << OpenConfigAndCountWords() 116 | << std::endl; 117 | } 118 | -------------------------------------------------------------------------------- /example/multi.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // This example demonstrates how ThreadCrosser::WrapCall will automatically 20 | // capture all ThreadCapture instances currently in scope, as long as the latter 21 | // each have an AutoThreadCrosser as a member. No additional work is required 22 | // for crossing threads if you need to use multiple types of ThreadCapture. 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #include "thread-capture.h" 29 | #include "thread-crosser.h" 30 | 31 | using capture_thread::ThreadCapture; 32 | using capture_thread::ThreadCrosser; 33 | 34 | // Example implementation, 1 of 2. 35 | class LogTypeOne : public ThreadCapture { 36 | public: 37 | explicit LogTypeOne(const std::string& value) 38 | : value_(value), cross_and_capture_to_(this) {} 39 | 40 | static void Show() { 41 | if (GetCurrent()) { 42 | GetCurrent()->ShowValue(); 43 | } 44 | } 45 | 46 | private: 47 | void ShowValue() const { std::cout << value_ << std::endl; } 48 | 49 | const std::string value_; 50 | const AutoThreadCrosser cross_and_capture_to_; 51 | }; 52 | 53 | // Example implementation, 2 of 2. 54 | class LogTypeTwo : public ThreadCapture { 55 | public: 56 | explicit LogTypeTwo(const std::string& value) 57 | : value_(value), cross_and_capture_to_(this) {} 58 | 59 | static void Show() { 60 | if (GetCurrent()) { 61 | GetCurrent()->ShowValue(); 62 | } 63 | } 64 | 65 | private: 66 | void ShowValue() const { std::cout << value_ << std::endl; } 67 | 68 | const std::string value_; 69 | const AutoThreadCrosser cross_and_capture_to_; 70 | }; 71 | 72 | void WorkerThread() { 73 | LogTypeOne::Show(); 74 | LogTypeTwo::Show(); 75 | } 76 | 77 | int main() { 78 | const LogTypeOne superceded_by_type1("should not print"); 79 | const LogTypeOne type1("type1 was captured"); 80 | const LogTypeTwo type2("type2 was captured"); 81 | 82 | // It doesn't matter how many implementations are in scope; all are captured 83 | // with a single call to ThreadCrosser::WrapCall. Only the most-recent of each 84 | // ThreadCapture type will be used! (See threaded.cc.) 85 | std::thread worker1(ThreadCrosser::WrapCall(&WorkerThread)); 86 | worker1.join(); 87 | } 88 | -------------------------------------------------------------------------------- /example/paths.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // This is a proof-of-concept example of how the framework could be used to keep 20 | // track of a directory structure in a decentralized way, e.g., in a tool that 21 | // installs a project in a user-specified root directory. 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "thread-capture.h" 30 | 31 | using capture_thread::ThreadCapture; 32 | 33 | // A helper class for building paths from components. 34 | class PathBuilder { 35 | public: 36 | std::string String() const { return path_.str(); } 37 | 38 | PathBuilder& Add(const std::string& component) { 39 | if (!component.empty()) { 40 | if (component.front() == '/') { 41 | path_.str(""); 42 | path_ << component; 43 | } else { 44 | if (!trailing_slash_) { 45 | path_ << '/'; 46 | } 47 | path_ << component; 48 | } 49 | trailing_slash_ = component.back() == '/'; 50 | } 51 | return *this; 52 | } 53 | 54 | private: 55 | bool trailing_slash_ = true; 56 | std::ostringstream path_; 57 | }; 58 | 59 | // Tracks persistent root and local paths. 60 | class Path : public ThreadCapture { 61 | public: 62 | static std::string Root() { 63 | if (GetCurrent()) { 64 | return GetCurrent()->RootPath(); 65 | } else { 66 | return ""; 67 | } 68 | } 69 | 70 | static std::string Working() { 71 | if (GetCurrent()) { 72 | PathBuilder builder; 73 | builder.Add(Root()); 74 | GetCurrent()->AppendLocalPath(&builder); 75 | return builder.String(); 76 | } else { 77 | return Root(); 78 | } 79 | } 80 | 81 | protected: 82 | virtual std::string RootPath() const = 0; 83 | virtual void AppendLocalPath(PathBuilder* builder) const = 0; 84 | 85 | static std::string DelegateRootPath(const Path& path) { 86 | return path.RootPath(); 87 | } 88 | 89 | static void DelegateLocalPath(const Path& path, PathBuilder* builder) { 90 | path.AppendLocalPath(builder); 91 | } 92 | }; 93 | 94 | // Sets the root path, which persists while the instance is in scope. 95 | class InRootPath : public Path { 96 | public: 97 | InRootPath(const std::string& root_path) 98 | : root_path_(root_path), capture_to_(this) {} 99 | 100 | protected: 101 | std::string RootPath() const override { return root_path_; } 102 | 103 | void AppendLocalPath(PathBuilder* builder) const override { 104 | const auto previous = capture_to_.Previous(); 105 | if (previous) { 106 | DelegateLocalPath(*previous, builder); 107 | } 108 | } 109 | 110 | private: 111 | const std::string root_path_; 112 | const ScopedCapture capture_to_; 113 | }; 114 | 115 | // Sets the local path relative to the current root + local path. 116 | class InLocalPath : public Path { 117 | public: 118 | InLocalPath(const std::string& local_path) 119 | : local_path_(local_path), capture_to_(this) {} 120 | 121 | protected: 122 | std::string RootPath() const override { 123 | const auto previous = capture_to_.Previous(); 124 | return previous ? DelegateRootPath(*previous) : ""; 125 | } 126 | 127 | void AppendLocalPath(PathBuilder* builder) const override { 128 | assert(builder); 129 | const auto previous = capture_to_.Previous(); 130 | if (previous) { 131 | DelegateLocalPath(*previous, builder); 132 | } 133 | builder->Add(local_path_); 134 | } 135 | 136 | private: 137 | const std::string local_path_; 138 | const ScopedCapture capture_to_; 139 | }; 140 | 141 | // Simulates installing project binaries. 142 | void InstallBin(const std::vector& targets) { 143 | InLocalPath path("bin"); 144 | for (const auto& target : targets) { 145 | InLocalPath file(target); 146 | std::cerr << "Installing binary " << Path::Working() << std::endl; 147 | } 148 | } 149 | 150 | // Simulates installing project libraries. 151 | void InstallLib(const std::vector& targets) { 152 | InLocalPath path("lib"); 153 | for (const auto& target : targets) { 154 | InLocalPath file(target); 155 | std::cerr << "Installing library " << Path::Working() << std::endl; 156 | } 157 | } 158 | 159 | // Simulates installing the project. 160 | void InstallProjectIn(const std::string& path) { 161 | InRootPath root(path); 162 | std::cerr << "Installing project in " << Path::Root() << std::endl; 163 | InstallBin({"binary1", "binary2"}); 164 | InstallLib({"lib1.so", "lib2.so"}); 165 | } 166 | 167 | int main() { InstallProjectIn("/usr/local"); } 168 | -------------------------------------------------------------------------------- /example/simple.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // This is a minimal example of basic usage of the framework. Use this only in 20 | // cases where you do not need (or want) functionality to cross threads. See 21 | // threaded.cc for an example of crossing threads. 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "thread-capture.h" 28 | 29 | using capture_thread::ThreadCapture; 30 | 31 | // Inherit from ThreadCapture, passing the class name as the template argument. 32 | class LogText : public ThreadCapture { 33 | public: 34 | LogText() : capture_to_(this) {} 35 | 36 | // The public *capturing* API is static, and accesses the private API via 37 | // GetCurrent(). 38 | static void Log(std::string line) { 39 | if (GetCurrent()) { 40 | GetCurrent()->LogLine(std::move(line)); 41 | } else { 42 | std::cerr << "*** Not captured: \"" << line << "\" ***" << std::endl; 43 | } 44 | } 45 | 46 | // The public *accessing* API is non-static, and provides the accumulated 47 | // information in whatever format happens to be useful. 48 | const std::list& GetLines() const { return lines_; } 49 | 50 | private: 51 | // The private API records data when this logger is being captured to. 52 | void LogLine(std::string line) { lines_.emplace_back(std::move(line)); } 53 | 54 | // Accumulated data. 55 | std::list lines_; 56 | 57 | // ScopedCapture ensures that the static API logs to this object when: 58 | // 1. This object is in scope. 59 | // 2. No logger of the *same* type is higher in the current stack. 60 | const ScopedCapture capture_to_; 61 | }; 62 | 63 | void NoLogger() { LogText::Log("No logger is in scope."); } 64 | 65 | void LoggedOp1() { LogText::Log("The logger is in scope."); } 66 | 67 | void LoggedOp2() { LogText::Log("It captures all lines."); } 68 | 69 | int main() { 70 | NoLogger(); 71 | { 72 | LogText logger; 73 | LoggedOp1(); 74 | LoggedOp2(); 75 | 76 | for (const auto& line : logger.GetLines()) { 77 | std::cerr << "Captured: " << line << std::endl; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /example/speed.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // This is a simple (and noisy) test of the overhead required for each 20 | // ThreadCapture captured by ThreadCrosser::WrapCall. 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "thread-capture.h" 28 | #include "thread-crosser.h" 29 | 30 | using capture_thread::ThreadCapture; 31 | using capture_thread::ThreadCrosser; 32 | 33 | constexpr int kRepetitions = 5; 34 | constexpr int kMaxWraps = 4; 35 | constexpr int kMaxScopes = 4; 36 | constexpr int kIterations = 1000000; 37 | 38 | class NoOp : public ThreadCapture { 39 | public: 40 | NoOp() : cross_and_capture_to_(this) {} 41 | 42 | private: 43 | const AutoThreadCrosser cross_and_capture_to_; 44 | }; 45 | 46 | static std::chrono::duration GetCurrentTime() { 47 | return std::chrono::duration_cast>( 48 | std::chrono::high_resolution_clock::now().time_since_epoch()); 49 | } 50 | 51 | void Execute(const std::function& function) { 52 | const auto start_time = GetCurrentTime(); 53 | for (int i = 0; i < kIterations; ++i) { 54 | function(i); 55 | } 56 | const auto finish_time = GetCurrentTime(); 57 | const double elapsed_ms = 1000. * (finish_time - start_time).count(); 58 | std::cout << '\t' << elapsed_ms << '\t' << elapsed_ms / kIterations 59 | << std::endl; 60 | } 61 | 62 | void ExecuteWithWrapping(std::function function, int wraps) { 63 | if (wraps > 0) { 64 | ExecuteWithWrapping(ThreadCrosser::WrapFunction(function), wraps - 1); 65 | } else { 66 | Execute(function); 67 | } 68 | } 69 | 70 | void ExecuteWithScopesAndWrapping(int scopes, int wraps) { 71 | if (scopes > 0) { 72 | NoOp noop; 73 | ExecuteWithScopesAndWrapping(scopes - 1, wraps); 74 | } else { 75 | ExecuteWithWrapping([](int x) { return x; }, wraps); 76 | } 77 | } 78 | 79 | int main() { 80 | for (int i = 0; i < kRepetitions; ++i) { 81 | for (int wraps = 1; wraps < kMaxWraps + 1; ++wraps) { 82 | for (int scopes = 1; scopes < kMaxScopes + 1; ++scopes) { 83 | std::cout << i << '\t' << scopes << '\t' << wraps; 84 | ExecuteWithScopesAndWrapping(scopes, wraps); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /example/threaded.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // This is a minimal example of creating functionality that can cross threads. 20 | // This is in contrast to simple.cc, which is strictly compartmentalized to 21 | // individual threads. 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "thread-capture.h" 30 | #include "thread-crosser.h" 31 | 32 | using capture_thread::ThreadCapture; 33 | using capture_thread::ThreadCrosser; 34 | 35 | // (See simple.cc for comments. The only comments here relate to threading.) 36 | class LogText : public ThreadCapture { 37 | public: 38 | LogText() : cross_and_capture_to_(this) {} 39 | 40 | static void Log(std::string line) { 41 | if (GetCurrent()) { 42 | GetCurrent()->LogLine(std::move(line)); 43 | } else { 44 | std::cerr << "*** Not captured: \"" << line << "\" ***" << std::endl; 45 | } 46 | } 47 | 48 | std::list CopyLines() { 49 | std::lock_guard lock(data_lock_); 50 | return lines_; 51 | } 52 | 53 | private: 54 | void LogLine(std::string line) { 55 | std::lock_guard lock(data_lock_); 56 | lines_.emplace_back(std::move(line)); 57 | } 58 | 59 | // To support threading, just add mutex protection and an AutoThreadCrosser. 60 | // AutoThreadCrosser ensures that logging is passed on to worker threads, but 61 | // *only* when the thread function is wrapped with ThreadCrosser::WrapCall. 62 | 63 | // Note that if you implement multiple types of ThreadCapture in this way, a 64 | // single call to ThreadCrosser::WrapCall will pass on *all* of them that are 65 | // currently in scope in the current thread. For example, if you're logging 66 | // strings (e.g., this class) and have a separate ThreadCapture to log timing 67 | // that also uses AutoThreadCrosser, a single call to ThreadCrosser::WrapCall 68 | // will pass on both string logging and timing logging. 69 | 70 | std::mutex data_lock_; 71 | std::list lines_; 72 | const AutoThreadCrosser cross_and_capture_to_; 73 | }; 74 | 75 | void NoLogger() { LogText::Log("No logger is in scope."); } 76 | 77 | void LoggedOp() { LogText::Log("The logger is in scope."); } 78 | 79 | void LoggedOpInThread() { 80 | LogText::Log("ThreadCrosser::WrapCall passes on logging."); 81 | } 82 | 83 | void UnloggedOpInThread() { 84 | LogText::Log("Logging has not been passed on here."); 85 | } 86 | 87 | int main() { 88 | NoLogger(); 89 | { 90 | LogText logger; 91 | LoggedOp(); 92 | 93 | std::thread logged_thread(ThreadCrosser::WrapCall(&LoggedOpInThread)); 94 | std::thread unlogged_thread(&UnloggedOpInThread); 95 | 96 | logged_thread.join(); 97 | unlogged_thread.join(); 98 | 99 | for (const auto& line : logger.CopyLines()) { 100 | std::cerr << "Captured: " << line << std::endl; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /example/throttle.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // This is a proof-of-concept example of how you might rate-throttle worker 20 | // threads using a shared timer. For example, you might multithread an operation 21 | // that repeatedly polls a resource where each poll operation is comparatively 22 | // slow, but when combined, the threads would exceed some desired rate limit. 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "thread-capture.h" 34 | #include "thread-crosser.h" 35 | 36 | using capture_thread::ThreadCapture; 37 | using capture_thread::ThreadCrosser; 38 | 39 | // Base class for throttlers that limit the rate of processing. 40 | class RateThrottler : public ThreadCapture { 41 | public: 42 | static void Wait() { 43 | if (GetCurrent()) { 44 | GetCurrent()->WaitForNextEvent(); 45 | } 46 | } 47 | 48 | protected: 49 | RateThrottler() = default; 50 | virtual ~RateThrottler() = default; 51 | 52 | virtual void WaitForNextEvent() = 0; 53 | }; 54 | 55 | // Limits the processing rate based on a shared internal timer. 56 | class SharedThrottler : public RateThrottler { 57 | public: 58 | explicit SharedThrottler(double seconds_between_events) 59 | : seconds_between_events_(seconds_between_events), 60 | last_time_(GetCurrentTime() - seconds_between_events_), 61 | cross_and_capture_to_(this) {} 62 | 63 | protected: 64 | void WaitForNextEvent() override { 65 | std::lock_guard lock(time_lock_); 66 | const auto current_time = GetCurrentTime(); 67 | std::this_thread::sleep_for(seconds_between_events_ - 68 | (current_time - last_time_)); 69 | last_time_ = GetCurrentTime(); 70 | } 71 | 72 | private: 73 | static std::chrono::duration GetCurrentTime() { 74 | return std::chrono::duration_cast>( 75 | std::chrono::high_resolution_clock::now().time_since_epoch()); 76 | } 77 | 78 | const std::chrono::duration seconds_between_events_; 79 | std::mutex time_lock_; 80 | std::chrono::duration last_time_; 81 | const AutoThreadCrosser cross_and_capture_to_; 82 | }; 83 | 84 | // This represents a worker thread that is expected to not exceed the rate limit 85 | // on its own, but might exceed that rate in combination with other workers. 86 | void Worker(int number) { 87 | for (int i = 0; i < 5; ++i) { 88 | RateThrottler::Wait(); 89 | std::this_thread::sleep_for(std::chrono::milliseconds(125)); 90 | std::ostringstream formatted; 91 | formatted << "Thread #" << number << ": " << i << std::endl; 92 | std::cerr << formatted.str(); 93 | } 94 | } 95 | 96 | // The work is parallelized because each individual operation is slow. 97 | void Execute() { 98 | std::list> threads; 99 | for (int i = 0; i < 5; ++i) { 100 | threads.emplace_back( 101 | new std::thread(ThreadCrosser::WrapCall(std::bind(&Worker, i)))); 102 | } 103 | for (const auto& thread : threads) { 104 | thread->join(); 105 | } 106 | } 107 | 108 | int main() { 109 | { 110 | std::cerr << "Using kMean of 100ms" << std::endl; 111 | SharedThrottler throttler(0.1); 112 | Execute(); 113 | } 114 | std::cerr << "Without throttling" << std::endl; 115 | Execute(); 116 | } 117 | -------------------------------------------------------------------------------- /example/trace.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // This example demonstrates how the framework might be used for call tracing, 20 | // by incrementally creating a call stack. 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "thread-capture.h" 29 | 30 | using capture_thread::ThreadCapture; 31 | using capture_thread::ThreadCrosser; 32 | 33 | // (See threaded.cc for more info about threading.) 34 | class TraceContext : public ThreadCapture { 35 | public: 36 | explicit TraceContext(std::string name) 37 | : name_(std::move(name)), cross_and_capture_to_(this) {} 38 | 39 | static std::vector GetTrace() { 40 | std::vector trace; 41 | if (GetCurrent()) { 42 | GetCurrent()->AppendTrace(&trace); 43 | } 44 | return trace; 45 | } 46 | 47 | private: 48 | // AutoThreadCrosser::Previous provides access to the next frame on the stack. 49 | // this can be used recursively to create a trace back to the first frame in 50 | // the current scope. 51 | 52 | void AppendTrace(std::vector* trace) const { 53 | assert(trace); 54 | trace->push_back(name_); 55 | if (cross_and_capture_to_.Previous()) { 56 | cross_and_capture_to_.Previous()->AppendTrace(trace); 57 | } 58 | } 59 | 60 | const std::string name_; 61 | const AutoThreadCrosser cross_and_capture_to_; 62 | }; 63 | 64 | void PrintTrace() { 65 | const auto trace = TraceContext::GetTrace(); 66 | for (unsigned int i = 0; i < trace.size(); ++i) { 67 | std::cerr << "Frame " << i << ": " << trace[i] << std::endl; 68 | } 69 | } 70 | 71 | int main() { 72 | // Each important scope is labeled with a TraceContext. It's important to 73 | // remember that it must be *named*; otherwise, it will go out of scope 74 | // immediately, making it effectively a no-op. 75 | TraceContext trace_frame("main"); 76 | 77 | // ThreadCrosser::WrapCall will capture the current frame and pass it on when 78 | // the callback is executed, regardless of where that happens. 79 | const auto execute = ThreadCrosser::WrapCall([] { 80 | TraceContext trace_frame("execute"); 81 | PrintTrace(); 82 | }); 83 | 84 | std::thread worker_thread([execute] { 85 | // Not part of the trace when execute is called. 86 | TraceContext trace_frame("worker_thread"); 87 | execute(); 88 | }); 89 | worker_thread.join(); 90 | } 91 | -------------------------------------------------------------------------------- /include/thread-capture.h: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #ifndef THREAD_CAPTURE_H_ 20 | #define THREAD_CAPTURE_H_ 21 | 22 | #include 23 | 24 | #include "thread-crosser.h" 25 | 26 | namespace capture_thread { 27 | 28 | // Base class for instrumentation classes that need to be made available within 29 | // a single thread, and possibly shared across threads. Derive a new class Type 30 | // from ThreadCapture to define a new instrumentation type. Scoping is 31 | // managed by adding a ScopedCapture *or* an AutoThreadCrosser member to 32 | // instantiable implementations of the instrumentation. (If Type is abstract, 33 | // add the member to the instantiable subclasses.) See ThreadCrosser for info 34 | // about *automatic* sharing across threads, and ThreadBridge (below) for info 35 | // about *manually* sharing across threads. 36 | template 37 | class ThreadCapture { 38 | protected: 39 | ThreadCapture() = default; 40 | virtual ~ThreadCapture() = default; 41 | 42 | class CrossThreads; 43 | 44 | // Manages scoping of instrumentation within a single thread. Use ThreadBridge 45 | // + CrossThreads to share instrumentation across threads. Alternatively, use 46 | // AutoThreadCrosser *instead* of ScopedCapture to allow ThreadCrosser to 47 | // handle this automatically. 48 | class ScopedCapture { 49 | public: 50 | explicit inline ScopedCapture(Type* capture) 51 | : previous_(GetCurrent()), current_(capture) { 52 | SetCurrent(current_); 53 | } 54 | 55 | inline ~ScopedCapture() { SetCurrent(Previous()); } 56 | 57 | inline Type* Previous() const { return previous_; } 58 | 59 | private: 60 | ScopedCapture(const ScopedCapture&) = delete; 61 | ScopedCapture(ScopedCapture&&) = delete; 62 | ScopedCapture& operator=(const ScopedCapture&) = delete; 63 | ScopedCapture& operator=(ScopedCapture&&) = delete; 64 | void* operator new(std::size_t size) = delete; 65 | 66 | friend class CrossThreads; 67 | Type* const previous_; 68 | Type* const current_; 69 | }; 70 | 71 | // Creates a bridge point for sharing a single type of instrumentation between 72 | // threads. Create this in the main thread, then enable crossing in the worker 73 | // thread with CrossThreads. This is only needed if the instrumentation uses 74 | // ScopedCapture instead of AutoThreadCrosser. 75 | // 76 | // NOTE: This isn't visible by default. If you want to make this functionality 77 | // available for a specific instrumentation class, add the following with 78 | // public visibility within the instrumentation class: 79 | // 80 | // using ThreadCapture::ThreadBridge; 81 | class ThreadBridge { 82 | public: 83 | inline ThreadBridge() : capture_(GetCurrent()) {} 84 | 85 | private: 86 | ThreadBridge(const ThreadBridge&) = delete; 87 | ThreadBridge(ThreadBridge&&) = delete; 88 | ThreadBridge& operator=(const ThreadBridge&) = delete; 89 | ThreadBridge& operator=(ThreadBridge&&) = delete; 90 | void* operator new(std::size_t size) = delete; 91 | 92 | friend class CrossThreads; 93 | Type* const capture_; 94 | }; 95 | 96 | class AutoThreadCrosser; 97 | 98 | // Connects a worker thread to the main thread via a ThreadBridge for manually 99 | // sharing of a single type of instrumentation. 100 | // 101 | // NOTE: This isn't visible by default. If you want to make this functionality 102 | // available for a specific instrumentation class, add the following with 103 | // public visibility within the instrumentation class: 104 | // 105 | // using ThreadCapture::CrossThreads; 106 | class CrossThreads { 107 | public: 108 | explicit inline CrossThreads(const ThreadBridge& bridge) 109 | : previous_(GetCurrent()) { 110 | SetCurrent(bridge.capture_); 111 | } 112 | 113 | inline ~CrossThreads() { SetCurrent(previous_); } 114 | 115 | private: 116 | explicit inline CrossThreads(const ScopedCapture& capture) 117 | : previous_(GetCurrent()) { 118 | SetCurrent(capture.current_); 119 | } 120 | 121 | CrossThreads(const CrossThreads&) = delete; 122 | CrossThreads(CrossThreads&&) = delete; 123 | CrossThreads& operator=(const CrossThreads&) = delete; 124 | CrossThreads& operator=(CrossThreads&&) = delete; 125 | void* operator new(std::size_t size) = delete; 126 | 127 | friend class AutoThreadCrosser; 128 | Type* const previous_; 129 | }; 130 | 131 | // Enables automatic crossing of threads for a specific instrumentation type 132 | // when ThreadCrosser::WrapCall or ThreadCrosser::WrapFunction are used. Add 133 | // this as a member of the implementation that you want to automatically 134 | // share. Use ScopedCapture *instead* if you don't want ThreadCrosser to 135 | // automatically share instrumentation. 136 | class AutoThreadCrosser : public ThreadCrosser { 137 | public: 138 | AutoThreadCrosser(Type* capture) 139 | : cross_with_(this), capture_to_(capture) {} 140 | 141 | inline Type* Previous() const { return capture_to_.Previous(); } 142 | 143 | private: 144 | AutoThreadCrosser(const AutoThreadCrosser&) = delete; 145 | AutoThreadCrosser(AutoThreadCrosser&&) = delete; 146 | AutoThreadCrosser& operator=(const AutoThreadCrosser&) = delete; 147 | AutoThreadCrosser& operator=(AutoThreadCrosser&&) = delete; 148 | void* operator new(std::size_t size) = delete; 149 | 150 | void FindTopAndCall(const std::function& call, 151 | const ReverseScope& reverse_scope) const final; 152 | 153 | void ReconstructContextAndCall( 154 | const std::function& call, 155 | const ReverseScope& reverse_scope) const final; 156 | 157 | const ScopedCrosser cross_with_; 158 | const ScopedCapture capture_to_; 159 | }; 160 | 161 | // Gets the most-recent object from the stack of the current thread. 162 | static inline Type* GetCurrent() { return current_; } 163 | 164 | private: 165 | ThreadCapture(const ThreadCapture&) = delete; 166 | ThreadCapture(ThreadCapture&&) = delete; 167 | ThreadCapture& operator=(const ThreadCapture&) = delete; 168 | ThreadCapture& operator=(ThreadCapture&&) = delete; 169 | void* operator new(std::size_t size) = delete; 170 | 171 | static inline void SetCurrent(Type* value) { current_ = value; } 172 | 173 | static thread_local Type* current_; 174 | }; 175 | 176 | template 177 | thread_local Type* ThreadCapture::current_(nullptr); 178 | 179 | template 180 | void ThreadCapture::AutoThreadCrosser::FindTopAndCall( 181 | const std::function& call, 182 | const ReverseScope& reverse_scope) const { 183 | if (cross_with_.Parent()) { 184 | cross_with_.Parent()->FindTopAndCall( 185 | call, {cross_with_.Parent(), &reverse_scope}); 186 | } else { 187 | ReconstructContextAndCall(call, reverse_scope); 188 | } 189 | } 190 | 191 | template 192 | void ThreadCapture::AutoThreadCrosser::ReconstructContextAndCall( 193 | const std::function& call, 194 | const ReverseScope& reverse_scope) const { 195 | // Makes the ThreadCapture available in this scope so that it overrides the 196 | // default behavior of instrumentation calls made in call. 197 | const CrossThreads capture(capture_to_); 198 | if (reverse_scope.previous) { 199 | const auto current = reverse_scope.previous->current; 200 | assert(current); 201 | if (current) { 202 | current->ReconstructContextAndCall(call, *reverse_scope.previous); 203 | } 204 | } else { 205 | // Makes the ThreadCrosser available in this scope so that call itself can 206 | // cross threads again. 207 | const DelegateCrosser crosser(cross_with_); 208 | assert(call); 209 | if (call) { 210 | call(); 211 | } 212 | } 213 | } 214 | 215 | } // namespace capture_thread 216 | 217 | #endif // THREAD_CAPTURE_H_ 218 | -------------------------------------------------------------------------------- /include/thread-crosser.h: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #ifndef THREAD_CROSSER_H_ 20 | #define THREAD_CROSSER_H_ 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | namespace capture_thread { 28 | 29 | // Manages automatic thread-crossing for sharing instrumentation classes derived 30 | // from ThreadCapture. The static API allows the caller to automatically share 31 | // all instrumentation types that are in scope, provided they use 32 | // AutoThreadCrosser to manage scoping. Not all classes will have this enabled, 33 | // since it can cause unexpected results. 34 | class ThreadCrosser { 35 | public: 36 | // Wraps a callback to share instrumentation that's currently in scope with a 37 | // worker thread. Call this in the main thread, then pass the returned value 38 | // to the worker thread. 39 | // 40 | // NOTE: The returned function will be invalidated if any instrumentation goes 41 | // out of scope; therefore, the main thread must wait for the worker thread to 42 | // call it before continuing. 43 | static inline std::function WrapCall(std::function call) { 44 | return WrapFunction(std::move(call)); 45 | } 46 | 47 | // Wraps an arbitrary function to share instrumentation that's currently in 48 | // scope with a worker thread. Call this in the main thread, then pass the 49 | // returned value to the worker thread. 50 | // 51 | // NOTE: The returned function will be invalidated if any instrumentation goes 52 | // out of scope; therefore, the main thread must wait for the worker thread to 53 | // call it before continuing. 54 | template 55 | static inline std::function WrapFunction( 56 | Return (*function)(Args...)) { 57 | return WrapFunction(std::function(function)); 58 | } 59 | 60 | // Wraps an arbitrary function to share instrumentation that's currently in 61 | // scope with a worker thread. Call this in the main thread, then pass the 62 | // returned value to the worker thread. 63 | // 64 | // NOTE: The returned function will be invalidated if any instrumentation goes 65 | // out of scope; therefore, the main thread must wait for the worker thread to 66 | // call it before continuing. 67 | template 68 | static std::function WrapFunction( 69 | std::function function); 70 | 71 | private: 72 | ThreadCrosser(const ThreadCrosser&) = delete; 73 | ThreadCrosser(ThreadCrosser&&) = delete; 74 | ThreadCrosser& operator=(const ThreadCrosser&) = delete; 75 | ThreadCrosser& operator=(ThreadCrosser&&) = delete; 76 | void* operator new(std::size_t size) = delete; 77 | 78 | ThreadCrosser() = default; 79 | virtual ~ThreadCrosser() = default; 80 | 81 | // Keeps track of a reverse call stack when wrapping a callback. (This is used 82 | // to create a linked-list of ThreadCrosser on the stack.) 83 | struct ReverseScope { 84 | const ThreadCrosser* const current; 85 | const ReverseScope* const previous; 86 | }; 87 | 88 | // Performs the function call in the full ThreadCapture context above this 89 | // ThreadCrosser. 90 | inline void CallInFullContext(const std::function& call) const { 91 | FindTopAndCall(call, {this, nullptr}); 92 | } 93 | 94 | // Traverses to the top of the ThreadCrosser stack to recursively rebuild the 95 | // stack of ThreadCapture, then calls the callback. 96 | virtual void FindTopAndCall(const std::function& call, 97 | const ReverseScope& reverse_scope) const = 0; 98 | 99 | // Instantiates the ThreadCapture context associated with this ThreadCrosser, 100 | // then recursively calls the next ThreadCrosser. 101 | virtual void ReconstructContextAndCall( 102 | const std::function& call, 103 | const ReverseScope& reverse_scope) const = 0; 104 | 105 | class ScopedCrosser; 106 | 107 | // Makes a captured ThreadCrosser available within the current thread. This is 108 | // only to allow thread-crossing to happen again within that thread. 109 | class DelegateCrosser { 110 | public: 111 | explicit inline DelegateCrosser(const ScopedCrosser& crosser) 112 | : parent_(GetCurrent()) { 113 | SetCurrent(crosser.current_); 114 | } 115 | 116 | inline ~DelegateCrosser() { SetCurrent(parent_); } 117 | 118 | private: 119 | DelegateCrosser(const DelegateCrosser&) = delete; 120 | DelegateCrosser(DelegateCrosser&&) = delete; 121 | DelegateCrosser& operator=(const DelegateCrosser&) = delete; 122 | DelegateCrosser& operator=(DelegateCrosser&&) = delete; 123 | void* operator new(std::size_t size) = delete; 124 | 125 | ThreadCrosser* const parent_; 126 | }; 127 | 128 | // Captures the ThreadCrosser so it can be used by DelegateCrosser, which will 129 | // make it available in another thread. 130 | class ScopedCrosser { 131 | public: 132 | explicit inline ScopedCrosser(ThreadCrosser* capture) 133 | : parent_(GetCurrent()), current_(capture) { 134 | SetCurrent(capture); 135 | } 136 | 137 | inline ~ScopedCrosser() { SetCurrent(Parent()); } 138 | 139 | inline ThreadCrosser* Parent() const { return parent_; } 140 | 141 | private: 142 | ScopedCrosser(const ScopedCrosser&) = delete; 143 | ScopedCrosser(ScopedCrosser&&) = delete; 144 | ScopedCrosser& operator=(const ScopedCrosser&) = delete; 145 | ScopedCrosser& operator=(ScopedCrosser&&) = delete; 146 | void* operator new(std::size_t size) = delete; 147 | 148 | friend class DelegateCrosser; 149 | ThreadCrosser* const parent_; 150 | ThreadCrosser* const current_; 151 | }; 152 | 153 | // AutoMove (and its specializations) ensure that argument-passing when 154 | // calling a std::function doesn't make copies when it would be inappropriate, 155 | // e.g., when Type cannot be copied or moved. This is a class instead of a 156 | // function so that Type can be made explicit, which is necessary for type- 157 | // checking to work. 158 | 159 | template 160 | struct AutoMove; 161 | 162 | // AutoCall (and its specializations) ensure that return values are not 163 | // inappropriately copied, e.g., when returning by reference, or when the 164 | // return type is void. This is a class instead of a function so that the 165 | // types can be made explicit, which is necessary for type-checking to work. 166 | 167 | template 168 | struct AutoCall; 169 | 170 | static ThreadCrosser* GetCurrent(); 171 | static void SetCurrent(ThreadCrosser* value); 172 | 173 | template 174 | friend class ThreadCapture; 175 | }; 176 | 177 | template 178 | std::function ThreadCrosser::WrapFunction( 179 | std::function function) { 180 | const auto current = GetCurrent(); 181 | if (function && current) { 182 | return [current, function](Args... args) -> Return { 183 | return AutoCall::Execute(*current, function, 184 | AutoMove::Pass(args)...); 185 | }; 186 | } else { 187 | return function; 188 | } 189 | } 190 | 191 | // Handles pass-by-value. 192 | template 193 | struct ThreadCrosser::AutoMove { 194 | static Type&& Pass(Type& value) { return std::move(value); } 195 | }; 196 | 197 | // Handles pass-by-reference. 198 | template 199 | struct ThreadCrosser::AutoMove { 200 | static Type& Pass(Type& value) { return value; } 201 | }; 202 | 203 | // Handles return-by-value. 204 | template 205 | struct ThreadCrosser::AutoCall { 206 | static Return Execute(const ThreadCrosser& current, 207 | const std::function& function, 208 | Args... args) { 209 | typename std::remove_cv::type value = Return(); 210 | current.CallInFullContext([&value, &function, &args...] { 211 | value = function(AutoMove::Pass(args)...); 212 | }); 213 | return value; 214 | } 215 | }; 216 | 217 | // Handles return-by-reference. 218 | template 219 | struct ThreadCrosser::AutoCall { 220 | static Return& Execute(const ThreadCrosser& current, 221 | const std::function& function, 222 | Args... args) { 223 | Return* value(nullptr); 224 | current.CallInFullContext([&value, &function, &args...] { 225 | value = &function(AutoMove::Pass(args)...); 226 | }); 227 | assert(value); 228 | return *value; 229 | } 230 | }; 231 | 232 | // Handles void return type. 233 | template 234 | struct ThreadCrosser::AutoCall { 235 | static void Execute(const ThreadCrosser& current, 236 | const std::function& function, 237 | Args... args) { 238 | current.CallInFullContext( 239 | [&function, &args...] { function(AutoMove::Pass(args)...); }); 240 | } 241 | }; 242 | 243 | // Handles callback type. 244 | template <> 245 | struct ThreadCrosser::AutoCall { 246 | static void Execute(const ThreadCrosser& current, 247 | const std::function& function) { 248 | current.CallInFullContext(function); 249 | } 250 | }; 251 | 252 | } // namespace capture_thread 253 | 254 | #endif // THREAD_CROSSER_H_ 255 | -------------------------------------------------------------------------------- /src/thread-crosser.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #include "thread-crosser.h" 20 | 21 | namespace capture_thread { 22 | 23 | namespace { 24 | thread_local ThreadCrosser* current(nullptr); 25 | } 26 | 27 | // static 28 | ThreadCrosser* ThreadCrosser::GetCurrent() { return current; } 29 | 30 | // static 31 | void ThreadCrosser::SetCurrent(ThreadCrosser* value) { current = value; } 32 | 33 | } // namespace capture_thread 34 | -------------------------------------------------------------------------------- /test/readme-test.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | // Code used in the quick-start in README.md. Make sure the two stay in sync! 20 | 21 | // STEP 1 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "thread-capture.h" 28 | 29 | // This class provides the instrumentation logic, both at the point the 30 | // instrumentation is used (e.g., logging points) and where it is enabled (e.g., 31 | // log-capture points.) Note that instances of ThreadCapture cannot be moved, 32 | // copied, or dynamically allocated. 33 | class Logger : public capture_thread::ThreadCapture { 34 | public: 35 | Logger() : cross_and_capture_to_(this) {} 36 | 37 | // The static API is used at the instrumentation points. It will often be a 38 | // no-op if no instrumentation is in scope. 39 | static void Log(const std::string& line) { 40 | // GetCurrent() provides the instrumentation currently in scope, and is 41 | // always thread-safe and repeatable. The implementation of the 42 | // instrumentation must be explicitly made thread-safe, however. 43 | if (GetCurrent()) { 44 | GetCurrent()->LogLine(line); 45 | } else { 46 | std::cerr << "Not captured: \"" << line << "\"" << std::endl; 47 | } 48 | } 49 | 50 | // The non-static public API allows the creator of the instrumentation object 51 | // to access its contents. This is only necessary when the instrumentation is 52 | // gathering information, as opposed to propagating information. 53 | std::list GetLines() { 54 | std::lock_guard lock(lock_); 55 | return lines_; 56 | } 57 | 58 | private: 59 | // The private implementation applies to the instrumentation only when it's in 60 | // scope. This does not need to exactly mirror the static API, and in fact 61 | // only needs to differentiate between default and override behaviors. 62 | void LogLine(const std::string& line) { 63 | std::lock_guard lock(lock_); 64 | lines_.emplace_back(line); 65 | } 66 | 67 | std::mutex lock_; 68 | std::list lines_; 69 | // Add an AutoThreadCrosser to ensure that scoping is handled correctly. If 70 | // you absolutely don't want the instrumentation crossing threads, use 71 | // ScopedCapture instead. Always initialize with `this`. 72 | const AutoThreadCrosser cross_and_capture_to_; 73 | }; 74 | 75 | // STEP 2 76 | 77 | // #include the header for your instrumentation class. 78 | 79 | // This function already exists in your code, and performs some sort of work for 80 | // which you want to use the instrumentation. 81 | void MyExistingFunction() { 82 | // Add calls to the static API where you need access to the instrumentation. 83 | Logger::Log("MyExistingFunction called"); 84 | } 85 | 86 | // STEP 3 87 | 88 | #include 89 | #include "thread-crosser.h" 90 | 91 | // (You don't need to #include the header for your instrumentation class here.) 92 | 93 | // This function already exists in your code, and parallelizes some 94 | // functionality that needs to use the instrumentation, but doesn't need to use 95 | // the instrumentation itself. 96 | void ParallelizeWork() { 97 | // Previously, the code just created a thread. 98 | // std::thread worker(&MyExistingFunction); 99 | 100 | // To pass along the instrumentation, wrap the thread with WrapCall. Also use 101 | // WrapCall when passing work to a worker thread, e.g., a thread pool. 102 | std::thread worker( 103 | capture_thread::ThreadCrosser::WrapCall(&MyExistingFunction)); 104 | worker.join(); 105 | } 106 | 107 | // STEP 4 108 | 109 | // #include the header for your instrumentation class. 110 | 111 | int main() { 112 | // If no instrumentation is in scope, the default behavior of the static API 113 | // is used where instrumentation calls are made. In this case, this will just 114 | // print the line to std::cerr. 115 | ParallelizeWork(); 116 | 117 | // To make the instrumentation available within a given scope, just 118 | // instantiate your class. The framework will take care of the rest. 119 | Logger logger; 120 | 121 | // Since a Logger is in scope, the line will be captured to that instance, 122 | // rather than the default behavior of printing to std::cerr. 123 | ParallelizeWork(); 124 | 125 | // In this case the instrumentation captures data, which is now available in 126 | // the local instance. 127 | for (const std::string& line : logger.GetLines()) { 128 | std::cerr << "The logger captured: \"" << line << "\"" << std::endl; 129 | } 130 | } 131 | 132 | // CAVEATS 133 | 134 | using MyLogger = Logger; 135 | 136 | namespace scope_example1 { 137 | 138 | using capture_thread::ThreadCrosser; 139 | 140 | void f() { MyLogger capture_messages; } 141 | void g() { MyLogger::Log("g was called"); } 142 | 143 | void Execute() { 144 | f(); 145 | g(); 146 | } 147 | 148 | } // namespace scope_example1 149 | 150 | namespace scope_example2 { 151 | 152 | using capture_thread::ThreadCrosser; 153 | 154 | // Fine, because no wrapping is done. 155 | std::function f() { 156 | MyLogger capture_messages; 157 | return [] { MyLogger::Log("f was called"); }; 158 | } 159 | 160 | // Fine, because no instrumentation goes out of scope. 161 | std::function g() { 162 | return ThreadCrosser::WrapCall([] { MyLogger::Log("g was called"); }); 163 | } 164 | 165 | // DANGER! capture_messages goes out of scope, invalidating the function. 166 | std::function h() { 167 | MyLogger capture_messages; 168 | return ThreadCrosser::WrapCall([] { MyLogger::Log("h was called"); }); 169 | } 170 | 171 | void Execute() { 172 | f()(); // Fine. 173 | g()(); // Fine. 174 | h()(); // SIGSEGV! 175 | 176 | // Fine. g captures capture_messages, but capture_messages doesn't go out of 177 | // scope until the worker thread is joined. 178 | MyLogger capture_messages; 179 | std::thread worker(g()); 180 | worker.join(); 181 | } 182 | 183 | } // namespace scope_example2 184 | -------------------------------------------------------------------------------- /test/thread-capture-test.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include "thread-capture.h" 25 | 26 | #include "log-text.h" 27 | #include "log-values.h" 28 | 29 | using testing::ElementsAre; 30 | 31 | namespace capture_thread { 32 | 33 | using testing::LogText; 34 | using testing::LogTextSingleThread; 35 | using testing::LogValues; 36 | using testing::LogValuesSingleThread; 37 | 38 | TEST(ThreadCaptureTest, NoLoggerInterferenceWithDifferentTypes) { 39 | LogText::Log("not logged"); 40 | LogValues::Count(0); 41 | { 42 | LogTextSingleThread text_logger; 43 | LogText::Log("logged 1"); 44 | { 45 | LogValuesSingleThread count_logger; 46 | LogValues::Count(1); 47 | LogText::Log("logged 2"); 48 | EXPECT_THAT(count_logger.GetCounts(), ElementsAre(1)); 49 | } 50 | LogText::Log("logged 3"); 51 | EXPECT_THAT(text_logger.GetLines(), 52 | ElementsAre("logged 1", "logged 2", "logged 3")); 53 | } 54 | } 55 | 56 | TEST(ThreadCaptureTest, SameTypeOverrides) { 57 | LogTextSingleThread text_logger1; 58 | LogText::Log("logged 1"); 59 | { 60 | LogTextSingleThread text_logger2; 61 | LogText::Log("logged 2"); 62 | EXPECT_THAT(text_logger2.GetLines(), ElementsAre("logged 2")); 63 | } 64 | LogText::Log("logged 3"); 65 | EXPECT_THAT(text_logger1.GetLines(), ElementsAre("logged 1", "logged 3")); 66 | } 67 | 68 | TEST(ThreadCaptureTest, ThreadsAreNotCrossed) { 69 | LogTextSingleThread logger; 70 | LogText::Log("logged 1"); 71 | 72 | std::thread worker([] { LogText::Log("logged 2"); }); 73 | worker.join(); 74 | 75 | EXPECT_THAT(logger.GetLines(), ElementsAre("logged 1")); 76 | } 77 | 78 | TEST(ThreadCaptureTest, ManualThreadCrossing) { 79 | LogTextSingleThread logger; 80 | LogText::Log("logged 1"); 81 | 82 | const LogText::ThreadBridge bridge; 83 | std::thread worker([&bridge] { 84 | LogText::CrossThreads logger(bridge); 85 | LogText::Log("logged 2"); 86 | }); 87 | worker.join(); 88 | 89 | EXPECT_THAT(logger.GetLines(), ElementsAre("logged 1", "logged 2")); 90 | } 91 | 92 | } // namespace capture_thread 93 | 94 | int main(int argc, char* argv[]) { 95 | testing::InitGoogleTest(&argc, argv); 96 | return RUN_ALL_TESTS(); 97 | } 98 | -------------------------------------------------------------------------------- /test/thread-crosser-test.cc: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------------------------------- 2 | Copyright 2017 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ----------------------------------------------------------------------------- */ 16 | 17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com] 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | #include "thread-capture.h" 26 | #include "thread-crosser.h" 27 | 28 | #include "callback-queue.h" 29 | #include "log-text.h" 30 | #include "log-values.h" 31 | 32 | using testing::ElementsAre; 33 | 34 | namespace capture_thread { 35 | 36 | using testing::CallbackQueue; 37 | using testing::LogText; 38 | using testing::LogTextMultiThread; 39 | using testing::LogTextSingleThread; 40 | using testing::LogValues; 41 | using testing::LogValuesMultiThread; 42 | 43 | TEST(ThreadCrosserTest, WrapCallIsFineWithoutLogger) { 44 | bool called = false; 45 | ThreadCrosser::WrapCall([&called] { 46 | called = true; 47 | LogText::Log("not logged"); 48 | })(); 49 | EXPECT_TRUE(called); 50 | } 51 | 52 | TEST(ThreadCrosserTest, WrapCallIsNotLazy) { 53 | LogTextMultiThread logger1; 54 | bool called = false; 55 | const auto callback = ThreadCrosser::WrapCall([&called] { 56 | called = true; 57 | LogText::Log("logged 1"); 58 | }); 59 | LogTextMultiThread logger2; 60 | callback(); 61 | EXPECT_TRUE(called); 62 | EXPECT_THAT(logger1.GetLines(), ElementsAre("logged 1")); 63 | EXPECT_THAT(logger2.GetLines(), ElementsAre()); 64 | } 65 | 66 | TEST(ThreadCrosserTest, WrapCallOnlyCapturesCrossers) { 67 | LogTextMultiThread logger1; 68 | LogTextSingleThread logger2; 69 | bool called = false; 70 | const auto callback = ThreadCrosser::WrapCall([&called] { 71 | called = true; 72 | LogText::Log("logged 1"); 73 | }); 74 | callback(); 75 | EXPECT_TRUE(called); 76 | LogText::Log("logged 2"); 77 | EXPECT_THAT(logger1.GetLines(), ElementsAre("logged 1")); 78 | EXPECT_THAT(logger2.GetLines(), ElementsAre("logged 2")); 79 | } 80 | 81 | TEST(ThreadCrosserTest, WrapCallIsIdempotent) { 82 | LogTextMultiThread logger1; 83 | bool called = false; 84 | const auto callback = 85 | ThreadCrosser::WrapCall(ThreadCrosser::WrapCall([&called] { 86 | called = true; 87 | LogText::Log("logged 1"); 88 | })); 89 | LogTextMultiThread logger2; 90 | callback(); 91 | EXPECT_TRUE(called); 92 | EXPECT_THAT(logger1.GetLines(), ElementsAre("logged 1")); 93 | EXPECT_THAT(logger2.GetLines(), ElementsAre()); 94 | } 95 | 96 | TEST(ThreadCrosserTest, WrapCallFallsThroughWithoutLogger) { 97 | bool called = false; 98 | const auto callback = ThreadCrosser::WrapCall([&called] { 99 | called = true; 100 | LogText::Log("logged 1"); 101 | }); 102 | LogTextMultiThread logger; 103 | callback(); 104 | EXPECT_TRUE(called); 105 | EXPECT_THAT(logger.GetLines(), ElementsAre("logged 1")); 106 | } 107 | 108 | TEST(ThreadCrosserTest, WrapCallWithNullCallbackIsNull) { 109 | EXPECT_FALSE(ThreadCrosser::WrapCall(nullptr)); 110 | LogTextMultiThread logger; 111 | EXPECT_FALSE(ThreadCrosser::WrapCall(nullptr)); 112 | } 113 | 114 | TEST(ThreadCrosserTest, WrapFunctionIsFineWithoutLogger) { 115 | bool called = false; 116 | ThreadCrosser::WrapFunction(std::function([&called] { 117 | called = true; 118 | LogText::Log("not logged"); 119 | }))(); 120 | EXPECT_TRUE(called); 121 | } 122 | 123 | // Used by WrapFunctionTypeInferenceFromFunctionPointer. 124 | int Identity(int x) { 125 | LogText::Log("logged 1"); 126 | return x; 127 | } 128 | 129 | TEST(ThreadCrosserTest, WrapFunctionTypeInferenceFromFunctionPointer) { 130 | LogTextMultiThread logger; 131 | auto identity = ThreadCrosser::WrapFunction(&Identity); 132 | EXPECT_EQ(identity(1), 1); 133 | EXPECT_THAT(logger.GetLines(), ElementsAre("logged 1")); 134 | } 135 | 136 | TEST(ThreadCrosserTest, WrapFunctionTypeCheckConstValueReturn) { 137 | using Type = std::unique_ptr; 138 | const std::function function( 139 | [](Type left, Type& right) { 140 | LogText::Log("logged 1"); 141 | *right = *left; 142 | return 3; 143 | }); 144 | LogTextMultiThread logger; 145 | const auto wrapped = ThreadCrosser::WrapFunction(function); 146 | Type left(new int(1)), right(new int(2)); 147 | EXPECT_EQ(wrapped(std::move(left), right), 3); 148 | EXPECT_FALSE(left); 149 | EXPECT_EQ(*right, 1); 150 | EXPECT_THAT(logger.GetLines(), ElementsAre("logged 1")); 151 | } 152 | 153 | TEST(ThreadCrosserTest, WrapFunctionTypeCheckValueReturn) { 154 | using Type = std::unique_ptr; 155 | const std::function function( 156 | [](Type left, Type& right) -> Type { 157 | LogText::Log("logged 1"); 158 | *right = *left; 159 | return left; 160 | }); 161 | LogTextMultiThread logger; 162 | const auto wrapped = ThreadCrosser::WrapFunction(function); 163 | Type left(new int(1)), right(new int(2)); 164 | int* left_ptr = left.get(); 165 | Type result = wrapped(std::move(left), right); 166 | EXPECT_FALSE(left); 167 | EXPECT_EQ(result.get(), left_ptr); 168 | EXPECT_EQ(*right, 1); 169 | EXPECT_THAT(logger.GetLines(), ElementsAre("logged 1")); 170 | } 171 | 172 | TEST(ThreadCrosserTest, WrapFunctionTypeCheckReferenceReturn) { 173 | using Type = std::unique_ptr; 174 | const std::function function( 175 | [](Type left, Type& right) -> Type& { 176 | LogText::Log("logged 1"); 177 | *right = *left; 178 | return right; 179 | }); 180 | LogTextMultiThread logger; 181 | const auto wrapped = ThreadCrosser::WrapFunction(function); 182 | Type left(new int(1)), right(new int(2)); 183 | EXPECT_EQ(&wrapped(std::move(left), right), &right); 184 | EXPECT_FALSE(left); 185 | EXPECT_EQ(*right, 1); 186 | EXPECT_THAT(logger.GetLines(), ElementsAre("logged 1")); 187 | } 188 | 189 | TEST(ThreadCrosserTest, WrapFunctionTypeCheckVoidReturn) { 190 | using Type = std::unique_ptr; 191 | const std::function function([](Type left, Type& right) { 192 | LogText::Log("logged 1"); 193 | *right = *left; 194 | }); 195 | LogTextMultiThread logger; 196 | const auto wrapped = ThreadCrosser::WrapFunction(function); 197 | Type left(new int(1)), right(new int(2)); 198 | wrapped(std::move(left), right); 199 | EXPECT_FALSE(left); 200 | EXPECT_EQ(*right, 1); 201 | EXPECT_THAT(logger.GetLines(), ElementsAre("logged 1")); 202 | } 203 | 204 | TEST(ThreadCrosserTest, WrapFunctionNotLazyWithValueReturn) { 205 | const std::function function([]() -> int { 206 | LogText::Log("logged 1"); 207 | return 1; 208 | }); 209 | LogTextMultiThread logger1; 210 | const auto wrapped = ThreadCrosser::WrapFunction(function); 211 | LogTextMultiThread logger2; 212 | EXPECT_EQ(wrapped(), 1); 213 | EXPECT_THAT(logger1.GetLines(), ElementsAre("logged 1")); 214 | EXPECT_THAT(logger2.GetLines(), ElementsAre()); 215 | } 216 | 217 | TEST(ThreadCrosserTest, WrapFunctionNotLazyWithReferenceReturn) { 218 | int value = 0; 219 | const std::function function([&value]() -> int& { 220 | LogText::Log("logged 1"); 221 | return value; 222 | }); 223 | LogTextMultiThread logger1; 224 | const auto wrapped = ThreadCrosser::WrapFunction(function); 225 | LogTextMultiThread logger2; 226 | EXPECT_EQ(&wrapped(), &value); 227 | EXPECT_THAT(logger1.GetLines(), ElementsAre("logged 1")); 228 | EXPECT_THAT(logger2.GetLines(), ElementsAre()); 229 | } 230 | 231 | TEST(ThreadCrosserTest, WrapFunctionNotLazyWithVoidReturn) { 232 | const std::function function([]() { LogText::Log("logged 1"); }); 233 | LogTextMultiThread logger1; 234 | const auto wrapped = ThreadCrosser::WrapFunction(function); 235 | LogTextMultiThread logger2; 236 | wrapped(); 237 | EXPECT_THAT(logger1.GetLines(), ElementsAre("logged 1")); 238 | EXPECT_THAT(logger2.GetLines(), ElementsAre()); 239 | } 240 | 241 | TEST(ThreadCrosserTest, WrapFunctionWithNullCallbackIsNull) { 242 | std::function callback(nullptr); 243 | EXPECT_FALSE(ThreadCrosser::WrapFunction(callback)); 244 | LogTextMultiThread logger; 245 | EXPECT_FALSE(ThreadCrosser::WrapFunction(callback)); 246 | } 247 | 248 | TEST(ThreadCrosserTest, SingleThreadCrossing) { 249 | LogTextMultiThread logger; 250 | LogText::Log("logged 1"); 251 | 252 | std::thread worker(ThreadCrosser::WrapCall([] { LogText::Log("logged 2"); })); 253 | worker.join(); 254 | 255 | EXPECT_THAT(logger.GetLines(), ElementsAre("logged 1", "logged 2")); 256 | } 257 | 258 | TEST(ThreadCrosserTest, MultipleThreadCrossingWithMultipleLoggers) { 259 | LogTextMultiThread text_logger; 260 | LogText::Log("logged 1"); 261 | LogValuesMultiThread count_logger; 262 | LogValues::Count(1); 263 | 264 | std::thread worker(ThreadCrosser::WrapCall([] { 265 | std::thread worker(ThreadCrosser::WrapCall([] { 266 | LogText::Log("logged 2"); 267 | LogValues::Count(2); 268 | })); 269 | worker.join(); 270 | })); 271 | worker.join(); 272 | 273 | EXPECT_THAT(text_logger.GetLines(), ElementsAre("logged 1", "logged 2")); 274 | EXPECT_THAT(count_logger.GetCounts(), ElementsAre(1, 2)); 275 | } 276 | 277 | TEST(ThreadCrosserTest, MultipleThreadCrossingWithDifferentLoggerScopes) { 278 | LogTextMultiThread text_logger; 279 | 280 | std::thread worker(ThreadCrosser::WrapCall([] { 281 | LogValuesMultiThread count_logger; 282 | std::thread worker(ThreadCrosser::WrapCall([] { 283 | LogText::Log("logged 1"); 284 | LogValues::Count(1); 285 | })); 286 | worker.join(); 287 | EXPECT_THAT(count_logger.GetCounts(), ElementsAre(1)); 288 | })); 289 | worker.join(); 290 | 291 | EXPECT_THAT(text_logger.GetLines(), ElementsAre("logged 1")); 292 | } 293 | 294 | TEST(ThreadCrosserTest, MultipleThreadCrossingWithOverride) { 295 | LogTextMultiThread logger1; 296 | 297 | std::thread worker(ThreadCrosser::WrapCall([] { 298 | LogTextMultiThread logger2; 299 | std::thread worker( 300 | ThreadCrosser::WrapCall([] { LogText::Log("logged 2"); })); 301 | worker.join(); 302 | EXPECT_THAT(logger2.GetLines(), ElementsAre("logged 2")); 303 | })); 304 | worker.join(); 305 | 306 | EXPECT_THAT(logger1.GetLines(), ElementsAre()); 307 | } 308 | 309 | TEST(ThreadCrosserTest, MultipleThreadCrossingWithMasking) { 310 | LogTextMultiThread logger1; 311 | LogTextMultiThread logger2; 312 | 313 | std::thread worker(ThreadCrosser::WrapCall([] { 314 | std::thread worker( 315 | ThreadCrosser::WrapCall([] { LogText::Log("logged 2"); })); 316 | worker.join(); 317 | })); 318 | worker.join(); 319 | 320 | EXPECT_THAT(logger1.GetLines(), ElementsAre()); 321 | EXPECT_THAT(logger2.GetLines(), ElementsAre("logged 2")); 322 | } 323 | 324 | TEST(ThreadCrosserTest, DifferentLoggersInSameThread) { 325 | CallbackQueue queue; 326 | 327 | std::thread worker([&queue] { 328 | while (true) { 329 | LogTextMultiThread logger; 330 | if (!queue.PopAndCall()) { 331 | break; 332 | } 333 | LogText::Log("logged in thread"); 334 | EXPECT_THAT(logger.GetLines(), ElementsAre("logged in thread")); 335 | } 336 | }); 337 | 338 | LogTextMultiThread logger1; 339 | queue.Push(ThreadCrosser::WrapCall([] { LogText::Log("logged 1"); })); 340 | queue.WaitUntilEmpty(); 341 | EXPECT_THAT(logger1.GetLines(), ElementsAre("logged 1")); 342 | 343 | { 344 | // It's important for the test case that logger2 overrides logger1, i.e., 345 | // that they are both in scope at the same time. 346 | LogTextMultiThread logger2; 347 | queue.Push(ThreadCrosser::WrapCall([] { LogText::Log("logged 2"); })); 348 | queue.WaitUntilEmpty(); 349 | EXPECT_THAT(logger2.GetLines(), ElementsAre("logged 2")); 350 | } 351 | 352 | queue.Push(ThreadCrosser::WrapCall([] { LogText::Log("logged 3"); })); 353 | queue.WaitUntilEmpty(); 354 | EXPECT_THAT(logger1.GetLines(), ElementsAre("logged 1", "logged 3")); 355 | 356 | queue.Terminate(); 357 | worker.join(); 358 | } 359 | 360 | TEST(ThreadCrosserTest, ReverseOrderOfLoggersOnStack) { 361 | LogTextMultiThread logger1; 362 | const auto callback = 363 | ThreadCrosser::WrapCall([] { LogText::Log("logged 1"); }); 364 | 365 | LogTextMultiThread logger2; 366 | const auto worker_call = ThreadCrosser::WrapCall([callback] { 367 | // In callback(), logger1 overrides logger2, whereas in the main thread 368 | // logger2 overrides logger1. 369 | callback(); 370 | LogText::Log("logged 2"); 371 | }); 372 | 373 | LogTextMultiThread logger3; 374 | 375 | // Call using a thread. 376 | std::thread worker(worker_call); 377 | worker.join(); 378 | 379 | EXPECT_THAT(logger1.GetLines(), ElementsAre("logged 1")); 380 | EXPECT_THAT(logger2.GetLines(), ElementsAre("logged 2")); 381 | EXPECT_THAT(logger3.GetLines(), ElementsAre()); 382 | 383 | // Call in the main thread. 384 | worker_call(); 385 | 386 | EXPECT_THAT(logger1.GetLines(), ElementsAre("logged 1", "logged 1")); 387 | EXPECT_THAT(logger2.GetLines(), ElementsAre("logged 2", "logged 2")); 388 | EXPECT_THAT(logger3.GetLines(), ElementsAre()); 389 | } 390 | 391 | } // namespace capture_thread 392 | 393 | int main(int argc, char* argv[]) { 394 | testing::InitGoogleTest(&argc, argv); 395 | return RUN_ALL_TESTS(); 396 | } 397 | --------------------------------------------------------------------------------