Mstdlib-1.24.0
m_async_writer.h
1/* The MIT License (MIT)
2 *
3 * Copyright (c) 2017 Monetra Technologies, LLC.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 * THE SOFTWARE.
22 */
23
24#ifndef M_ASYNC_WRITER_H
25#define M_ASYNC_WRITER_H
26
27#include <mstdlib/mstdlib.h>
28
29__BEGIN_DECLS
30
31/*! \addtogroup m_async_writer Asynchronous Writer
32 * \ingroup m_log
33 *
34 * Helper class that manages an internal worker thread and message queue for asynchrnous writes.
35 *
36 * Used internally in various logging modules.
37 *
38 * @{
39 */
40
41
42/*! Control what type of line endings get automatically appended to error messages. */
43typedef enum {
44 M_ASYNC_WRITER_LINE_END_NATIVE, /*!< \c '\\n' if running on Unix, \c '\\r\\n' if running on Windows */
45 M_ASYNC_WRITER_LINE_END_UNIX, /*!< always use \c '\\n' */
46 M_ASYNC_WRITER_LINE_END_WINDOWS /*!< always use \c '\\r\\n' */
48
49
50/*! Callback that will be called to write messages.
51 *
52 * If your program modifies the "thunk" object outside the callback while the writer is running,
53 * you'll need to add your own locks inside the callback to make this reentrant.
54 *
55 * The command flag allows you to pass one-off notifications to the callback. These notifications
56 * will be processed lazily (i.e., the next time the internal thread tries to write something).
57 * Only the next write is affected; after the command flag is used once, it's reset to zero.
58 *
59 * It is possible for the write callback to be called with a NULL msg and a non-zero command. This
60 * happens when the user sets a command with the force flag set to M_TRUE, but the message queue is empty.
61 * In this case, the callback should process the command, but it shouldn't write the empty message.
62 *
63 * \param[in] msg message that needs to be written. Can be modified in-place. May be NULL, for command-only calls.
64 * \param[in] thunk object passed into \a write_thunk parameter of M_async_writer_create().
65 * \param[in] cmd command flag passed into M_async_writer_set_command(). May be 0, if no command sent.
66 * \return M_TRUE if message was consumed, M_FALSE if message should be returned to queue (if possible).
67 */
68typedef M_bool (*M_async_write_cb_t)(char *msg, M_uint64 cmd, void *thunk);
69
70
71/* Callback that will be used to stop any asynchronous operations owned by the write thunk.
72 *
73 * This is an optional extra callback. Only use this if you have an extra async operation running
74 * that's managed by the thunk - for example, if your callback uses M_popen(), you'd call blocking
75 * close in here.
76 */
77typedef void (*M_async_thunk_stop_cb_t)(void *thunk);
78
79
80/* Callback that will be used to destroy the write thunk.
81 *
82 * If provided, will be called when writer is destroyed.
83 *
84 * Note: this function must not block, it is called for both synchronous and asynchronous destroys.
85 * Blocking destroys will call M_async_thunk_stop_cb_t first, do any optional blocking there.
86 *
87 * \param[in] thunk object passed into \a write_thunk parameter of M_async_writer_create().
88 */
89typedef void (*M_async_thunk_destroy_cb_t)(void *thunk);
90
91
92/*! Opaque struct that manages state for the writer. */
93struct M_async_writer;
94typedef struct M_async_writer M_async_writer_t;
95
96
97/*! Create a writer object.
98 *
99 * The writer does not automatically start running, you must call M_async_writer_start().
100 *
101 * \param[in] max_bytes maximum bytes that can be queued before messages start getting dropped
102 * \param[in] write_cb callback that will be called by an internal thread to write messages
103 * \param[in] write_thunk object that can be used to preserve callback state between writes
104 * \param[in] stop_cb optional callback that will be called during a stop request
105 * \param[in] destroy_cb callback that will be used to destroy the thunk when writer is destroyed
106 * \param[in] mode line-end mode for internally generated error messages
107 */
109 void *write_thunk, M_async_thunk_stop_cb_t stop_cb, M_async_thunk_destroy_cb_t destroy_cb,
111
112
113/*! Destroy the writer (non-blocking).
114 *
115 * This is a non-blocking operation - the worker thread is commanded to destroy itself, then immediately orphaned. The
116 * orphaned thread will still try to delete itself, if it has enough time to do so before the process ends. If the
117 * program exits before it has time to do this, it will show up as a memory leak (even though it's not).
118 *
119 * This call asks the internal thread to stop running at the next opportunity and then destroy the writer object once
120 * stopped. If the internal thread has already been stopped, the object is destroyed by the calling thread.
121 *
122 * If you set \a flush to M_TRUE, the internal thread will output all messages in the queue before it destroys itself.
123 * Otherwise, the thread will stop itself right after it finishes the current message it's working on, and will output
124 * a message describing the number of dropped messages left in the queue before destroying itself.
125 *
126 * If the internal thead is frozen, this is effectively a memory leak - the writer object won't be destroyed until
127 * the process exits. But the calling thread won't freeze, so this is probably preferable.
128 *
129 * \see M_async_writer_destroy_blocking
130 *
131 * \param[in] writer object we're operating on
132 * \param[in] flush if M_TRUE, output all messages in queue before destroying
133 */
134M_API void M_async_writer_destroy(M_async_writer_t *writer, M_bool flush);
135
136
137/*! Destroy the writer (blocking, with timeout).
138 *
139 * \warning
140 * This is a BLOCKING operation, it will wait for the worker thread to finish before returning, or for the given
141 * timeout to expire (whichever comes first).
142 *
143 * This call asks the internal thread to stop running at the next opportunity and then destroy the writer object once
144 * stopped. If the internal thread has already been stopped, the object is destroyed by the calling thread.
145 *
146 * If you set \a flush to M_TRUE, the internal thread will output all messages in the queue before it destroys itself.
147 * Otherwise, the thread will stop itself right after it finishes the current message it's working on, and will output
148 * a message describing the number of dropped messages left in the queue before destroying itself.
149 *
150 * If the timeout expires before the worker thread is done, the worker thread will be orphaned and control will return
151 * to the caller (just like in M_async_writer_destroy()). The orphaned thread will still try to delete itself, if it
152 * has enough time to do so before the process ends. If the program exits before it has time to do this, it will show
153 * up as a memory leak (even though it's not).
154 *
155 * \see M_async_writer_destroy
156 * \see M_async_writer_stop
157 *
158 * \param[in] writer object we're operating on
159 * \param[in] flush if M_TRUE, output all messages in queue before destroying
160 * \param[in] timeout_ms length of time (in milliseconds) to wait until orphaning the worker thread, or 0 for no timeout
161 * \return M_TRUE if worker thread finished within timeout, false if it did not and was orphaned
162 */
163M_API M_bool M_async_writer_destroy_blocking(M_async_writer_t *writer, M_bool flush, M_uint64 timeout_ms);
164
165
166/*! Start writing messages from the queue.
167 *
168 * This starts an internal worker thread that pulls messages off of the message queue and writes them.
169 *
170 * You can stop the worker thread with M_async_writer_stop() and then restart it with this function, and messages
171 * will still be accepted into the message queue the entire time. Start and stop only affect whether messages are
172 * being pulled off of the queue and written.
173 *
174 * \see M_async_writer_stop
175 *
176 * \param[in] writer object we're operating on
177 */
179
180
181/*! Check to see if writer has been started and is accepting messages.
182 *
183 * This is non-blocking, we're just checking whether the writer has been started
184 * and not stopped. If you need to check if a running writer is frozen or not,
185 * use M_async_writer_is_alive().
186 *
187 * \param writer object we're checking
188 * \return M_TRUE if writer has been started, M_FALSE if writer is stopped
189 */
191
192
193/*! Check to see if writer is frozen or not (blocking).
194 *
195 * Blocks until either the internal worker thread responds, or the timeout is reached.
196 *
197 * If you just want to check if the writer has been started or not, use M_async_writer_is_running() instead, it's
198 * non-blocking.
199 *
200 * Thread should respond after it finishes with the message that it's currently working on. So, the timeout
201 * should be chosen based on the time it takes for the write_cb to execute once (worst case).
202 *
203 * \param[in] writer object we're checking
204 * \param[in] timeout_ms amount of time to wait for thread to respond (in milliseconds)
205 * \return M_TRUE if thread responded to liveness check, M_FALSE if thread didn't respond within timeout
206 */
207M_API M_bool M_async_writer_is_alive(M_async_writer_t *writer, M_uint64 timeout_ms);
208
209
210/*! Stop internal worker thread.
211 *
212 * \warning
213 * This is a BLOCKING operation, it will wait for the worker thread to finish before returning. The worker thread
214 * will stop immediately after it finishes the current message it's working on (if any), so it shouldn't block for
215 * long.
216 *
217 * This is used when you need to stop the internal worker thread temporarily, and then restart it with a new
218 * thread later. Messages are still accepted into the message queue while the writer is stopped, it just doesn't
219 * write anything until M_async_writer_start() is called again.
220 *
221 * \see M_async_writer_start
222 * \see M_async_writer_destroy_blocking
223 *
224 * \param[in] writer object we're operating on
225 */
227
228
229/*! Set a command flag that will be passed to the write callback the next time it's called.
230 *
231 * This can be used to notify the write callback of a condition change (like a request to rotate logs, etc.).
232 *
233 * The command will be passed on the next call to the write callback, then reset immediately afterwards.
234 *
235 * If multiple calls to this command occur before the next write, the commands will be OR'd together into one value.
236 *
237 * You can force the write callback to always be called after the command is set by setting the \a force flag to
238 * M_TRUE. If not set, the command will be processed the next time the internal worker thread pulls a message off
239 * the queue (which might not be until the next call to M_async_writer_write(), if the queue is currently empty).
240 *
241 * \param[in] writer object we're operating on
242 * \param[in] write_command flag to pass to write_callback on next write (must be a power of two, or several flags OR'd together)
243 * \param[in] force if M_TRUE, wakes up writer thread even if message queue is empty.
244 * \return M_FALSE if command rejected due to writer being in the middle of a flush-stop.
245 */
246M_API M_bool M_async_writer_set_command(M_async_writer_t *writer, M_uint64 write_command, M_bool force);
247
248
249/*! Set a command flag, and block until that command is processed.
250 *
251 * Same as M_async_writer_set_command(), except that it blocks until the internal worker thread is done processing
252 * the command.
253 *
254 * Note that this function always sets the force flag for the command - even if the message queue is empty, the
255 * internal worker thread will be awakened and the command will be processed. If the message queue is not empty,
256 * the command will be processed when the next message is pulled off of the queue.
257 *
258 * \warning
259 * If this function is called from multiple threads on the same async_writer object, execution of the requested
260 * commands will be serialized - the command from the second thread won't even start until after the command from
261 * the first thread has finished.
262 *
263 * \param[in] writer object we're operating on
264 * \param[in] write_command flag to pass to write_callback on next write (must be a power of two, or several flags OR'd together)
265 * \return M_FALSE if command rejected due to writer being in the middle of a flush-stop.
266 */
267M_API M_bool M_async_writer_set_command_block(M_async_writer_t *writer, M_uint64 write_command);
268
269
270/*! Change the maximum buffer size.
271 *
272 * If the writer is running, the new maximum buffer size will not actually be enforced until the
273 * next time a message is written to the writer.
274 *
275 * \param[in] writer object we're operating on
276 * \param[in] max_bytes new maximum number of bytes that can be queued before messages start getting dropped
277 */
278M_API void M_async_writer_set_max_bytes(M_async_writer_t *writer, size_t max_bytes);
279
280
281/*! Write a message to the writer (non-blocking).
282 *
283 * The message will be added to a work queue, to be passed later to write_callback by an internal worker thread.
284 *
285 * If the message can't be added (empty message, message itself is larger than queue, alloc error, or writer is
286 * in the middle of a flush-stop) the message is dropped without modifying the internal queue.
287 *
288 * If the queue doesn't have enough empty space to fit the message, messages in the queue are dropped until
289 * there is enough room. The oldest message is dropped first, then the next oldest, and so on, until there's
290 * enough room in the queue to add the new message.
291 *
292 * Note that an async writer will still accept messages passed with this function when stopped - it will just
293 * add them to the message queue, and wait until the writer is started again to write them.
294 *
295 * \param[in] writer object we're operating on
296 * \param[in] msg message to add to write queue
297 * \return M_TRUE if message was added to queue, M_FALSE if it couldn't be added
298 */
299M_API M_bool M_async_writer_write(M_async_writer_t *writer, const char *msg);
300
301
302/*! Return the internal writer callback thunk.
303 *
304 * \warning
305 * If the writer is running, don't modify the thunk from external code unless you've implemented your own
306 * locking scheme between the writer callback and the external code, using locks stored in the thunk.
307 *
308 * \warning
309 * Ownership of the returned thunk remains with the M_async_writer_t object, so this pointer is only valid
310 * until M_async_writer_destroy() is called.
311 *
312 * \param[in] writer object we're operating on
313 * \return pointer to internal
314 */
316
317
318/*! Return the line ending string in use by the writer.
319 *
320 * \param[in] writer object we're operating on
321 * \return line ending string ("\\r\\n", "\\n", etc)
322 */
324
325
326/*! @} */
327
328__END_DECLS
329
330#endif /* M_ASYNC_WRITER_H */
M_bool M_async_writer_start(M_async_writer_t *writer)
M_bool M_async_writer_set_command_block(M_async_writer_t *writer, M_uint64 write_command)
M_async_writer_line_end_mode_t
Definition: m_async_writer.h:43
M_async_writer_t * M_async_writer_create(size_t max_bytes, M_async_write_cb_t write_cb, void *write_thunk, M_async_thunk_stop_cb_t stop_cb, M_async_thunk_destroy_cb_t destroy_cb, M_async_writer_line_end_mode_t mode)
void(* M_async_thunk_stop_cb_t)(void *thunk)
Definition: m_async_writer.h:77
M_bool M_async_writer_set_command(M_async_writer_t *writer, M_uint64 write_command, M_bool force)
void * M_async_writer_get_thunk(M_async_writer_t *writer)
void(* M_async_thunk_destroy_cb_t)(void *thunk)
Definition: m_async_writer.h:89
M_bool M_async_writer_destroy_blocking(M_async_writer_t *writer, M_bool flush, M_uint64 timeout_ms)
struct M_async_writer M_async_writer_t
Definition: m_async_writer.h:94
void M_async_writer_destroy(M_async_writer_t *writer, M_bool flush)
M_bool M_async_writer_is_alive(M_async_writer_t *writer, M_uint64 timeout_ms)
const char * M_async_writer_get_line_end(M_async_writer_t *writer)
M_bool(* M_async_write_cb_t)(char *msg, M_uint64 cmd, void *thunk)
Definition: m_async_writer.h:68
M_bool M_async_writer_is_running(M_async_writer_t *writer)
M_bool M_async_writer_write(M_async_writer_t *writer, const char *msg)
void M_async_writer_stop(M_async_writer_t *writer)
void M_async_writer_set_max_bytes(M_async_writer_t *writer, size_t max_bytes)
@ M_ASYNC_WRITER_LINE_END_UNIX
Definition: m_async_writer.h:45
@ M_ASYNC_WRITER_LINE_END_NATIVE
Definition: m_async_writer.h:44
@ M_ASYNC_WRITER_LINE_END_WINDOWS
Definition: m_async_writer.h:46