Line data Source code
1 : // Copyright (c) 2012-2019 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 <fcntl.h>
44 :
45 :
46 : // last include
47 : //
48 : #include <snapdev/poison.h>
49 :
50 :
51 :
52 : namespace ed
53 : {
54 :
55 :
56 :
57 : /** \brief Initializes the "thread done signal" object.
58 : *
59 : * To know that a thread is done, we need some form of signal that the
60 : * poll() can wake up on. For the purpose we currently use a pipe because
61 : * a full socket is rather slow to setup compare to a simple pipe.
62 : *
63 : * To use this signal, one creates a Thread Done Signal and adds the
64 : * new connection to the Snap Communicator object. Then when the thread
65 : * is done, the thread calls the thread_done() function. That will wake
66 : * up the main process.
67 : *
68 : * The same snap_thread_done_signal class can be used multiple times,
69 : * but only by one thread at a time. Otherwise you cannot know which
70 : * thread sent the message and by the time you attempt a join, you may
71 : * be testing the wrong thread (either that or you need another type
72 : * of synchronization mechanism.)
73 : *
74 : * \code
75 : * class thread_done_impl
76 : * : ed::thread_done_signal::thread_done_signal
77 : * {
78 : * ...
79 : * void process_read()
80 : * {
81 : * // this function gets called when the thread is about
82 : * // to exit or has exited; since the write to the pipe
83 : * // happens before the thread really exited, but should
84 : * // be near the very end, you should be fine calling the
85 : * // snap_thread::stop() function to join with it very
86 : * // quickly.
87 : * ...
88 : * }
89 : * ...
90 : * };
91 : *
92 : * // in the main thread
93 : * ed::thread_done_signal::pointer_t s(new thread_done_impl);
94 : * ed::communicator::instance()->add_connection(s);
95 : *
96 : * // create thread... and make sure the thread has access to 's'
97 : * ...
98 : *
99 : * // in the thread, before exiting we do:
100 : * s->thread_done();
101 : *
102 : * // around here, in the timeline, the process_read() function
103 : * // gets called
104 : * \endcode
105 : *
106 : * \todo
107 : * Change the implementation to use eventfd() instead of pipe2().
108 : * Pipes are using more resources and are slower to use than
109 : * an eventfd.
110 : */
111 0 : thread_done_signal::thread_done_signal()
112 : {
113 0 : if(pipe2(f_pipe, O_NONBLOCK | O_CLOEXEC) != 0)
114 : {
115 : // pipe could not be created
116 : //
117 0 : throw event_dispatcher_initialization_error("somehow the pipes used to detect the death of a thread could not be created.");
118 : }
119 0 : }
120 :
121 :
122 : /** \brief Close the pipe used to detect the thread death.
123 : *
124 : * The destructor is expected to close the pipe opned in the constructor.
125 : */
126 0 : thread_done_signal::~thread_done_signal()
127 : {
128 0 : close(f_pipe[0]);
129 0 : close(f_pipe[1]);
130 0 : }
131 :
132 :
133 : /** \brief Tell that this connection expects incoming data.
134 : *
135 : * The snap_thread_done_signal implements a signal that a secondary
136 : * thread can trigger before it quits, hence waking up the main
137 : * thread immediately instead of polling.
138 : *
139 : * \return The function returns true.
140 : */
141 0 : bool thread_done_signal::is_reader() const
142 : {
143 0 : return true;
144 : }
145 :
146 :
147 : /** \brief Retrieve the "socket" of the thread done signal object.
148 : *
149 : * The Thread Done Signal is implemented using a pair of pipes.
150 : * One of the pipes is returned as the "socket" and the other is
151 : * used to "write the signal".
152 : *
153 : * \return The signal "socket" to listen on with poll().
154 : */
155 0 : int thread_done_signal::get_socket() const
156 : {
157 0 : return f_pipe[0];
158 : }
159 :
160 :
161 : /** \brief Read the byte that was written in the thread_done().
162 : *
163 : * This function implementation reads one byte that was written by
164 : * thread_done() so the pipes can be reused multiple times.
165 : */
166 0 : void thread_done_signal::process_read()
167 : {
168 0 : char c(0);
169 0 : if(read(f_pipe[0], &c, sizeof(char)) != sizeof(char))
170 : {
171 0 : int const e(errno);
172 : SNAP_LOG_ERROR
173 0 : << "an error occurred while reading from a pipe used to know whether a thread is done (errno: "
174 0 : << e
175 0 : << " -- "
176 0 : << strerror(e)
177 0 : << ").";
178 : }
179 0 : }
180 :
181 :
182 : /** \brief Send the signal from the secondary thread.
183 : *
184 : * This function writes one byte in the pipe, which has the effect of
185 : * waking up the poll() of the main thread. This way we avoid having
186 : * to lock the file.
187 : *
188 : * The thread is expected to call this function just before it returns.
189 : */
190 0 : void thread_done_signal::thread_done()
191 : {
192 0 : char c(1);
193 0 : if(write(f_pipe[1], &c, sizeof(char)) != sizeof(char))
194 : {
195 0 : int const e(errno);
196 : SNAP_LOG_ERROR
197 0 : << "an error occurred while writing to 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 :
207 : } // namespace ed
208 : // vim: ts=4 sw=4 et
|