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