Line data Source code
1 : // Copyright (c) 2012-2019 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // This program is free software; you can redistribute it and/or modify
4 : // it under the terms of the GNU General Public License as published by
5 : // the Free Software Foundation; either version 2 of the License, or
6 : // (at your option) any later version.
7 : //
8 : // This program is distributed in the hope that it will be useful,
9 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 : // GNU General Public License for more details.
12 : //
13 : // You should have received a copy of the GNU General Public License
14 : // along with this program; if not, write to the Free Software
15 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
16 :
17 :
18 : // self
19 : //
20 : #include "eventdispatcher/thread_done_signal.h"
21 :
22 : #include "eventdispatcher/exception.h"
23 :
24 :
25 : // snaplogger lib
26 : //
27 : #include "snaplogger/message.h"
28 :
29 :
30 : //// snapdev lib
31 : ////
32 : //#include "snapdev/not_reached.h"
33 : //#include "snapdev/not_used.h"
34 : //#include "snapdev/string_replace_many.h"
35 : //
36 : //
37 : //// libaddr lib
38 : ////
39 : //#include "libaddr/addr_parser.h"
40 : //
41 : //
42 : //// C++ lib
43 : ////
44 : //#include <sstream>
45 : //#include <limits>
46 : //#include <atomic>
47 :
48 :
49 : // C lib
50 : //
51 : #include <fcntl.h>
52 : //#include <poll.h>
53 : //#include <unistd.h>
54 : //#include <sys/eventfd.h>
55 : //#include <sys/inotify.h>
56 : //#include <sys/ioctl.h>
57 : //#include <sys/resource.h>
58 : //#include <sys/syscall.h>
59 : //#include <sys/time.h>
60 :
61 :
62 : // last include
63 : //
64 : #include <snapdev/poison.h>
65 :
66 :
67 :
68 : /** \file
69 : * \brief Implementation of the Thread Done Signal class.
70 : *
71 : * When you create threads, it is often useful to know once a thread
72 : * is done via a signal (i.e. without having to be blocked joining
73 : * the thread).
74 : */
75 :
76 : namespace ed
77 : {
78 :
79 :
80 :
81 : /** \brief Initializes the "thread done signal" object.
82 : *
83 : * To know that a thread is done, we need some form of signal that the
84 : * poll() can wake up on. For the purpose we currently use a pipe because
85 : * a full socket is rather slow to setup compare to a simple pipe.
86 : *
87 : * To use this signal, one creates a Thread Done Signal and adds the
88 : * new connection to the Snap Communicator object. Then when the thread
89 : * is done, the thread calls the thread_done() function. That will wake
90 : * up the main process.
91 : *
92 : * The same snap_thread_done_signal class can be used multiple times,
93 : * but only by one thread at a time. Otherwise you cannot know which
94 : * thread sent the message and by the time you attempt a join, you may
95 : * be testing the wrong thread (either that or you need another type
96 : * of synchronization mechanism.)
97 : *
98 : * \code
99 : * class thread_done_impl
100 : * : ed::thread_done_signal::thread_done_signal
101 : * {
102 : * ...
103 : * void process_read()
104 : * {
105 : * // this function gets called when the thread is about
106 : * // to exit or has exited; since the write to the pipe
107 : * // happens before the thread really exited, but should
108 : * // be near the very end, you should be fine calling the
109 : * // snap_thread::stop() function to join with it very
110 : * // quickly.
111 : * ...
112 : * }
113 : * ...
114 : * };
115 : *
116 : * // in the main thread
117 : * ed::thread_done_signal::pointer_t s(new thread_done_impl);
118 : * ed::communicator::instance()->add_connection(s);
119 : *
120 : * // create thread... and make sure the thread has access to 's'
121 : * ...
122 : *
123 : * // in the thread, before exiting we do:
124 : * s->thread_done();
125 : *
126 : * // around here, in the timeline, the process_read() function
127 : * // gets called
128 : * \endcode
129 : *
130 : * \todo
131 : * Change the implementation to use eventfd() instead of pipe2().
132 : * Pipes are using more resources and are slower to use than
133 : * an eventfd.
134 : */
135 0 : thread_done_signal::thread_done_signal()
136 : {
137 0 : if(pipe2(f_pipe, O_NONBLOCK | O_CLOEXEC) != 0)
138 : {
139 : // pipe could not be created
140 : //
141 0 : throw event_dispatcher_initialization_error("somehow the pipes used to detect the death of a thread could not be created.");
142 : }
143 0 : }
144 :
145 :
146 : /** \brief Close the pipe used to detect the thread death.
147 : *
148 : * The destructor is expected to close the pipe opned in the constructor.
149 : */
150 0 : thread_done_signal::~thread_done_signal()
151 : {
152 0 : close(f_pipe[0]);
153 0 : close(f_pipe[1]);
154 0 : }
155 :
156 :
157 : /** \brief Tell that this connection expects incoming data.
158 : *
159 : * The snap_thread_done_signal implements a signal that a secondary
160 : * thread can trigger before it quits, hence waking up the main
161 : * thread immediately instead of polling.
162 : *
163 : * \return The function returns true.
164 : */
165 0 : bool thread_done_signal::is_reader() const
166 : {
167 0 : return true;
168 : }
169 :
170 :
171 : /** \brief Retrieve the "socket" of the thread done signal object.
172 : *
173 : * The Thread Done Signal is implemented using a pair of pipes.
174 : * One of the pipes is returned as the "socket" and the other is
175 : * used to "write the signal".
176 : *
177 : * \return The signal "socket" to listen on with poll().
178 : */
179 0 : int thread_done_signal::get_socket() const
180 : {
181 0 : return f_pipe[0];
182 : }
183 :
184 :
185 : /** \brief Read the byte that was written in the thread_done().
186 : *
187 : * This function implementation reads one byte that was written by
188 : * thread_done() so the pipes can be reused multiple times.
189 : */
190 0 : void thread_done_signal::process_read()
191 : {
192 0 : char c(0);
193 0 : if(read(f_pipe[0], &c, sizeof(char)) != sizeof(char))
194 : {
195 0 : int const e(errno);
196 : SNAP_LOG_ERROR
197 0 : << "an error occurred while reading from a pipe used to know whether a thread is done (errno: "
198 0 : << e
199 0 : << " -- "
200 0 : << strerror(e)
201 0 : << ").";
202 : }
203 0 : }
204 :
205 :
206 : /** \brief Send the signal from the secondary thread.
207 : *
208 : * This function writes one byte in the pipe, which has the effect of
209 : * waking up the poll() of the main thread. This way we avoid having
210 : * to lock the file.
211 : *
212 : * The thread is expected to call this function just before it returns.
213 : */
214 0 : void thread_done_signal::thread_done()
215 : {
216 0 : char c(1);
217 0 : if(write(f_pipe[1], &c, sizeof(char)) != sizeof(char))
218 : {
219 0 : int const e(errno);
220 : SNAP_LOG_ERROR
221 0 : << "an error occurred while writing to a pipe used to know whether a thread is done (errno: "
222 0 : << e
223 0 : << " -- "
224 0 : << strerror(e)
225 0 : << ").";
226 : }
227 0 : }
228 :
229 :
230 :
231 : } // namespace ed
232 : // vim: ts=4 sw=4 et
|