Botan 3.13.0
Crypto and TLS for C&
threaded_fork.cpp
Go to the documentation of this file.
1/*
2* Threaded Fork
3* (C) 2013 Joel Low
4* 2013 Jack Lloyd
5*
6* Botan is released under the Simplified BSD License (see license.txt)
7*/
8
9#include <botan/filters.h>
10
11#if defined(BOTAN_HAS_THREAD_UTILS)
12
13 #include <botan/internal/barrier.h>
14 #include <botan/internal/semaphore.h>
15
16 #include <exception>
17 #include <mutex>
18
19namespace Botan {
20
21struct Threaded_Fork_Data {
22 // NOLINTBEGIN(*-non-private-member-variables-in-classes)
23 /*
24 * Semaphore for indicating that there is work to be done (or to
25 * quit)
26 */
27 Semaphore m_input_ready_semaphore;
28
29 /*
30 * Synchronises all threads to complete processing data in lock-step.
31 */
32 Barrier m_input_complete_barrier;
33
34 /*
35 * The work that needs to be done. This should be only when the threads
36 * are NOT running (i.e. before notifying the work condition, after
37 * the input_complete_barrier has reset.)
38 */
39 const uint8_t* m_input = nullptr;
40
41 /*
42 * The length of the work that needs to be done.
43 */
44 size_t m_input_length = 0;
45
46 // NOLINTEND(*-non-private-member-variables-in-classes)
47
48 void clear_exception() {
49 const std::lock_guard lock(m_exception_mutex);
50 m_exception = nullptr;
51 }
52
53 void capture_exception() {
54 const std::lock_guard lock(m_exception_mutex);
55 if(!m_exception) {
56 m_exception = std::current_exception();
57 }
58 }
59
60 std::exception_ptr exception() {
61 const std::lock_guard lock(m_exception_mutex);
62 return m_exception;
63 }
64
65 private:
66 std::mutex m_exception_mutex;
67 std::exception_ptr m_exception;
68};
69
70/*
71* Threaded_Fork constructor
72*/
73Threaded_Fork::Threaded_Fork(Filter* f1, Filter* f2, Filter* f3, Filter* f4) :
74 Fork(nullptr, static_cast<size_t>(0)), m_thread_data(new Threaded_Fork_Data) {
75 Filter* filters[4] = {f1, f2, f3, f4};
76 set_next(filters, 4);
77}
78
79/*
80* Threaded_Fork constructor
81*/
82Threaded_Fork::Threaded_Fork(Filter* filters[], size_t count) :
83 Fork(nullptr, static_cast<size_t>(0)), m_thread_data(new Threaded_Fork_Data) {
84 set_next(filters, count);
85}
86
87Threaded_Fork::~Threaded_Fork() {
88 m_thread_data->m_input = nullptr;
89 m_thread_data->m_input_length = 0;
90
91 m_thread_data->m_input_ready_semaphore.release(m_threads.size());
92
93 for(auto& thread : m_threads) {
94 thread->join();
95 }
96}
97
98std::string Threaded_Fork::name() const {
99 return "Threaded Fork";
100}
101
102void Threaded_Fork::set_next(Filter* f[], size_t n) {
103 Fork::set_next(f, n);
104 n = m_next.size();
105
106 if(n < m_threads.size()) {
107 m_threads.resize(n);
108 } else {
109 m_threads.reserve(n);
110 for(size_t i = m_threads.size(); i != n; ++i) {
111 m_threads.push_back(std::make_shared<std::thread>([this, next = m_next[i]] { thread_entry(next); }));
112 }
113 }
114}
115
116void Threaded_Fork::send(const uint8_t input[], size_t length) {
117 /*
118 * The workers treat a nullptr input pointer as a shutdown marker, and a (nullptr, 0)
119 * call is otherwise valid. Just ignore an empty input here, matching Filter::send
120 */
121 if(length == 0) {
122 return;
123 }
124
125 if(!m_write_queue.empty()) {
126 thread_delegate_work(m_write_queue.data(), m_write_queue.size());
127 }
128 thread_delegate_work(input, length);
129
130 bool nothing_attached = true;
131 for(size_t j = 0; j != total_ports(); ++j) {
132 if(m_next[j] != nullptr) {
133 nothing_attached = false;
134 }
135 }
136
137 if(nothing_attached) {
138 m_write_queue += std::make_pair(input, length);
139 } else {
140 m_write_queue.clear();
141 }
142}
143
144void Threaded_Fork::thread_delegate_work(const uint8_t input[], size_t length) {
145 //Set the data to do.
146 m_thread_data->clear_exception();
147 m_thread_data->m_input = input;
148 m_thread_data->m_input_length = length;
149
150 //Let the workers start processing.
151 m_thread_data->m_input_complete_barrier.wait(total_ports() + 1);
152 m_thread_data->m_input_ready_semaphore.release(total_ports());
153
154 //Wait for all the filters to finish processing.
155 m_thread_data->m_input_complete_barrier.sync();
156
157 const auto exception = m_thread_data->exception();
158
159 //Reset the thread data
160 m_thread_data->m_input = nullptr;
161 m_thread_data->m_input_length = 0;
162
163 if(exception) {
164 std::rethrow_exception(exception);
165 }
166}
167
168void Threaded_Fork::thread_entry(Filter* filter) {
169 while(true) {
170 m_thread_data->m_input_ready_semaphore.acquire();
171
172 if(m_thread_data->m_input == nullptr) {
173 break;
174 }
175
176 // Plain Fork skips null ports the same way
177 if(filter != nullptr) {
178 try {
179 filter->write(m_thread_data->m_input, m_thread_data->m_input_length);
180 } catch(...) {
181 m_thread_data->capture_exception();
182 }
183 }
184 m_thread_data->m_input_complete_barrier.sync();
185 }
186}
187
188} // namespace Botan
189
190#endif
secure_vector< T > lock(const std::vector< T > &in)
Definition secmem.h:145