MySQL 8.0.29
Source Code Documentation
loader.h
Go to the documentation of this file.
1/*
2 Copyright (c) 2015, 2021, Oracle and/or its affiliates.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License, version 2.0,
6 as published by the Free Software Foundation.
7
8 This program is also distributed with certain software (including
9 but not limited to OpenSSL) that is licensed under separate terms,
10 as designated in a particular file or component or in included license
11 documentation. The authors of MySQL hereby grant you an additional
12 permission to link the program and your derivative works with the
13 separately licensed software that they have included with MySQL.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23*/
24
25/**
26 * @class mysql_harness::Loader
27 *
28 * @ingroup Loader
29 *
30 *
31 *
32 * ## Introduction
33 *
34 * The loader class is responsible for managing the life-cycle of
35 * plugins in the harness. Each plugin goes through seven steps in the
36 * life-cycle, of which steps #2, #3, #5 and #6 are optional:
37 *
38 * 1. Loading
39 * 2. Initialization
40 * 3. Starting
41 * 4. Running
42 * 5. Stopping
43 * 6. Deinitialization
44 * 7. Unloading
45 *
46 * ## Overview of Life-cycle Steps
47 *
48 * ### 1. Loading ###
49 *
50 * When *loading*, the plugin is loaded using the dynamic library
51 * support available on the operating system. Symbols are evaluated
52 * lazily (for example, the `RTLD_LAZY` flag is used for `dlopen`) to
53 * allow plugins to be loaded in any order. The symbols that are
54 * exported by the plugin are made available to all other plugins
55 * loaded (flag `RTLD_GLOBAL` to `dlopen`).
56 *
57 * As part of the loading procedure, the *plugin structure* (see
58 * Plugin class) is fetched from the module and used for the four
59 * optional steps below.
60 *
61 *
62 *
63 * ### 2. Initialization ###
64 *
65 * After all the plugins are successfully loaded, each plugin is given
66 * a chance to perform initialization. This step is only executed if
67 * the plugin structure defines an `init` function. Note that it is
68 * guaranteed that the init function of a plugin is called *after* the
69 * `init` function of all plugins it requires have been called. The
70 * list of these dependencies is specified via `requires` field of the
71 * `Plugin` struct.
72 *
73 * @note if some plugin `init()` function fails, any plugin `init()`
74 * functions schedulled to run after will not run, and harness will
75 * proceed straight to deinitialization step, bypassing calling
76 * `start()` and `stop()` functions.
77 *
78 *
79 *
80 * ### 3. Starting ###
81 * After all plugins have been successfully initialized, a thread is
82 * created for each plugin that has a non-NULL `start` field in the
83 * plugin structure. The threads are started in an arbitrary order,
84 * so you have to be careful about not assuming that, for example,
85 * other plugins required by the plugin have started their thread. If
86 * the plugin does not define a `start` function, no thread is created.
87 * There is a "running" flag associated with each such thread; this
88 * flag is set when the thread starts but before the `start` function
89 * is called. If necessary, the plugin can spawn more threads using
90 * standard C++11 thread calls, however, these threads should not
91 * call harness API functions.
92 *
93 *
94 *
95 * ### 4. Running ###
96 * After starting all plugins (that needed to be started), the harness
97 * will enter the *running step*. This is the "normal" phase, where the
98 * application spends most of its lifetime (application and plugins
99 * service requests or do whatever it is they do). Harness will remain
100 * in this step until one of two things happen:
101 *
102 * 1. shutdown signal is received by the harness
103 * 2. one of the plugins exits with error
104 *
105 * When one of these two events occurrs, harness progresses to the
106 * next step.
107 *
108 *
109 *
110 * ### 5. Stopping ###
111 * In this step, harness "tells" plugins running `start()` to exit this
112 * function by clearing the "running" flag. It also invokes `stop()`
113 * function for all plugins that provided it. It then waits for all
114 * running plugin threads to exit.
115 *
116 * @note under certain circumstances, `stop()` may overlap execution
117 * with `start()`, or even be called before `start()`.
118 *
119 *
120 *
121 * ### 6. Deinitialization ###
122 * After all threads have stopped, regardless of whether they stopped
123 * with an error or not, the plugins are deinitialized in reverse order
124 * of initialization by calling the function in the `deinit` field of
125 * the `Plugin` structure. Regardless of whether the `deinit()` functions
126 * return an error or not, all plugins schedulled for deinitialisation
127 * will be deinitialized.
128 *
129 * @note for any `init()` functions that failed, `deinit()` functions
130 * will not run.
131 * @note plugins may have a `deinit()` function despite not having a
132 * corresponding `init()`. In such cases, the missing `init()` is
133 * treated as if it existed and ran successfully.
134 *
135 *
136 *
137 * ### 7. Unloading ###
138 * After a plugin has deinitialized, it can be unloaded. It is
139 * guaranteed that no module is unloaded before it has been
140 * deinitialized.
141 *
142 * @note This step is currently unimplemented - meaning, it does nothing.
143 * The plugins will remain loaded in memory until the process shuts
144 * down. This makes no practical difference on application behavior
145 * at present, but might be needed if Harness gained ability to
146 * reload plugins in the future.
147
148## Behavior Diagrams
149
150Previous section described quickly each step of the life-cycle process. In this
151section, two flow charts are presented which show the operation of all seven
152steps. First shows a high-level overview, and the second shows all 7 life-cycle
153steps in more detail. Discussion of details follows in the following sections.
154
155Some points to keep in mind while viewing the diagrams:
156
157- diagrams describe code behavior rather than implementation. So for example:
158 - pseudocode does not directly correspond 1:1 to real code. However, it
159 behaves exactly like the real code.
160
161- seven life-cycle functions shown are actual functions (@c Loader's methods,
162 to be more precise)
163 - load_all(), init_all(), start_all(), main_loop(), stop_all(), deinit_all()
164 are implemented functions (first 6 steps of life-cycle)
165 - unload_all() is the 7th step of life-cycle, but it's currently unimplemented
166
167- when plugin functions exit with error, they do so by calling
168 set_error() before exiting
169
170- some things are not shown to keep the diagram simple:
171 - first error returned by any of the 7 life-cycle functions is
172 saved and passed at the end of life-cycle flow to the calling code
173
174
175### Overview
176
177@verbatim
178
179\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
180\ \
181\ START \
182\ | \
183\ | \
184\ | \
185\ V \
186\ [load_all()] \
187\ | \
188\ V \
189\ <LOAD_OK?> \
190\ | | \
191\ +---N Y \
192\ | | \
193\ | v \
194\ | [init_all()] \
195\ | | \
196\ | v \
197\ | <INIT_OK?> ( each plugin runs ) \
198\ | | | (in a separate thread) \
199\ | N Y \
200\ | | | [plugin[1]->start()] \
201\ | | v start plugin threads [plugin[2]->start()] \
202\ | | [start_all()] - - - - - - - - - - - - - - - - ->[ .. .. ] \
203\ | | | [ .. .. ] \
204\ | | | + - - - - - - - - - - - - - - - - - - - - -[plugin[n]->start()] \
205\ | | | notification when each ^ \
206\ | | | | thread terminates \
207\ | | | | \
208\ | | | | stop plugin \
209\ | | | | threads \
210\ | | | | \
211\ | | | | \
212\ | | v v \
213\ | | [main_loop()]= call ==>[stop_all()] - - - - - - - - - - - + \
214\ | | | \
215\ | | | \ \
216\ | *<--+ \ \
217\ | | \__ waits for all plugin \
218\ | v threads to terminate \
219\ | [deinit_all()] \
220\ | | \
221\ | v \
222\ +-->* \
223\ | \
224\ v \
225\ [unload_all()] \
226\ | \
227\ | \ \
228\ | \ \
229\ v \__ currently not implemented \
230\ END \
231\ \
232\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
233
234@endverbatim
235
236
237### Detailed View
238
239@verbatim
240
241 START
242 |
243 |
244 v
245\\\\ load_all() \\\\\\\\\\\\\\\\\\\\\\
246\ \
247\ LOAD_OK = true \
248\ foreach plugin: \
249\ load plugin \
250\ if (exit_status != ok): \
251\ LOAD_OK = false \
252\ break loop \
253\ \
254\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
255 |
256 |
257 v
258 <LOAD_OK?>
259 | |
260 Y N----> unload_all() (see further down)
261 |
262 |
263\\\\\\\\\\\\\\|\\\ init_all() \\\\\\\\\\\\\\\\\
264\ | \
265\ v \
266\ [INIT_OK = true, i = 1] \
267\ | \
268\ v \
269\ +----><plugin[i] exists?> \
270\ | | | \
271\ [i++] Y N---------------------+ \
272\ ^ | | \
273\ | | | \
274\ | v | \
275\ | <plugin[i] has init()?> | \
276\ | | | | \
277\ | N Y---+ | \
278\ | | | | \
279\ | | | | \
280\ | | v | \
281\ | | [plugin[i]->init()] | \
282\ | | | | \
283\ | | | | \
284\ | | | | \
285\ | | | | \
286\ | | v | \
287\ | | <exit ok?> | \
288\ | v | | | \
289\ +-------*<------Y N | \
290\ | | \
291\ | | \
292\ v | \
293\ [INIT_OK = false] | \
294\ | | \
295\ v | \
296\ *<----------------+ \
297\ | \
298\ v \
299\ [LAST_PLUGIN = i-1] \
300\ | \
301\\\\\\\\\\\\\\\\\\\\\\|\\\\\\\\\\\\\\\\\\\\\\\\
302 |
303 |
304 v
305 <INIT_OK?>
306 | |
307 Y N----> deinit_all() (see further down)
308 |
309 |
310 v
311\\\\ start_all() \\\\\\\\\\\\\\\\\\\\\\\\\
312\ \
313\ for i = 1 to LAST_PLUGIN: \
314\ if plugin[i] has start(): \ start start() in new thread
315\ new thread(plugin[i]->start()) - - - - - - - - - - - - - - - - +
316\ \
317\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ |
318 |
319 | |
320 +-----------------+
321 | |
322\\\\|\\\ main_loop() \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ \\\\\\\\
323 | | \
324 v \
325+-->* | \
326| | \
327| v | \
328| <any plugin threads running?> \
329| | | | \
330| N Y---+ \
331| | | | \
332| | <shutdown signal received && stop_all() not called yet?> \
333| | | | | \
334| | N Y \
335| | | == call ==>[stop_all()]- - - - - - - - - - - - - + | \
336| | | | tell (each) start() to exit \
337| | *<--+ | | \
338| | | \
339| | | v v \
340| | | [plugin[1]->start()] \
341| | v (one) plugin thread exits [plugin[2]->start()] \
342| | [wait for (any)]<- - - - - - - - - - - - - - - -[ .. .. ] \
343| | [ thread exit ] [ .. .. ] \
344| | | [plugin[n]->start()] \
345| | | ^ \
346| | | \
347| | v | \
348| | <thread exit ok?> \
349| | | | | \
350| | Y N---+ \
351| | | | | \
352| | | v \
353| | | <stop_all() called already?> | \
354| | v | | \
355| | *<------Y N tell (each) \
356| | | = call ==+ start() to exit \
357| | | | | | \
358| | v | | \
359+---|-------*<----------+ *==>[stop_all()]- - - - - - - - + \
360 | | \
361 | | | \
362 v | | \
363 <stop_all() called already?> | | \
364 | | | | \
365 Y N | | \
366 | == call =================+ | \
367 | | | \
368 *---+ | \
369 | | \
370\\\\|\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
371 | |
372 | |
373 v |
374 *<---- init_all() (if !INIT_OK) |
375 | |
376 | |
377 v |
378\\\\ deinit_all() \\\\\\\\\\\\\\\\\\\\ |
379\ \ |
380\ for i = LAST_PLUGIN to 1: \ |
381\ if plugin[i] has deinit(): \ |
382\ plugin[i]->deinit() \ |
383\ if (exit_status != ok): \ |
384\ # ignore error \ |
385\ \ |
386\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ |
387 | |
388 | |
389 v |
390 *<---- load_all() (if !LOAD_OK) |
391 | |
392 v |
393\\\\ unload_all() \\\\\\\\\\\\\\\\\\\\ |
394\ \ |
395\ no-op (currently unimplemented) \ |
396\ \ |
397\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ |
398 | |
399 | |
400 v /
401 END /
402 /
403 /
404 / ( each plugin runs )
405 / (in a separate thread)
406\\\\ stop_all() \\\\\\\\\\\\\\\\\\\\\\ run_flag == false
407\ \ tells start() [plugin[1]->start()]
408\ for i = 1 to LAST_PLUGIN: \ to exit [plugin[2]->start()]
409\ run_flag[i] = false - - - - - - - - - - - - - - - - ->[ .. .. ]
410\ if plugin[i] has stop(): \ [ .. .. ]
411\ plugin[i]->stop() \ [ .. .. ]
412\ if (exit_status != ok): \ [plugin[n]->start()]
413\ # ignore error \
414\ \
415\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
416
417@endverbatim
418
419
420
421
422## Discussion
423
424### Persistence (definition)
425
426Before continuing, we need to define the word "persist", used later on. When we
427say "persist", we'll mean the opposite of "exit". So when we say a function or
428a thread persists, it means that it's not returning/exiting, but instead is
429running some forever-loop or is blocked on something, etc. What it's doing
430exactly doesn't matter, what matters, is that it hasn't terminated but
431"lives on" (in case of a function, to "persist" means the same as to "block",
432but for threads that might sound confusing, this is why we need a new word).
433So when we call start() in a new thread, it will either run and keep running
434(thread will persist), or it will run briefly and return (thread will exit).
435In short, "to persist" means the opposite of "to finish running and exit".
436
437### Plugin API functions
438
439Each plugin can define none, any or all of the following 4 callbacks.
440They're function pointers, that can be null if not implemented. They're
441typically called in the order as listed below (under certain circumstances,
442stop() may overlap execution with start(), or even be called before start()).
443
444- init() -- called inside of main thread
445- start() -- main thread creates a new thread, then calls this
446- stop() -- called inside of main thread
447- deinit() -- called inside of main thread
448
449### Starting and Stopping: Start() ###
450
451It is typical to implement start function in such a way that it will
452"persist" (i.e. it will run some forever-loop processing requests
453rather than exit briefly after being called). In such case, Harness
454must have a way to terminate it during shutdown operation.
455
456For this purpose, Harness exposes a boolean "running" flag to each plugin, which
457serves as means to communicate the need to shutdown; it is read by
458`is_running()` function. This function should be routinely polled by plugin's
459`start()` function to determine if it should shut down, and once it returns
460false, plugin `start()` should terminate as soon as possible. Failure to
461terminate will block Harness from progressing further in its shutdown procedure,
462resulting in application "hanging" during shutdown. Typically, `start()`
463would be implemented more-or-less like so:
464
465 void start()
466 {
467 // run-once code
468
469 while (is_running())
470 {
471 // forever-loop code
472 }
473
474 // clean-up code
475 }
476
477There is also an alternative blocking function available, `wait_for_stop()`,
478should that be better suited for the particular plugin design. Instead of
479quickly returning a boolean flag, it will block (with an optional timeout)
480until Harness flags to shut down this plugin. It is an efficient functional
481equivalent of:
482
483 while (is_running())
484 {
485 // sleep a little or break on timeout
486 }
487
488When entering shutdown phase, Harness will notify all plugins to shut down
489via mechanisms described above. It is also permitted for plugins to exit on
490their own, whether due to error or intended behavior, without consulting
491this "running" flag. Polling the "running" flag is only needed when `start()`
492"persists" and does not normally exit until told to do so.
493
494Also, in some designs, `start()` function might find it convenient to be able to
495set the "running" flag to false, in order to trigger its own shutdown in another
496piece of code. For such cases, `clear_running()` function is provided, which
497will do exactly that.
498
499IMPORTANT! Please note that all 3 functions described above (`is_running()`,
500`wait_for_stop()` and `clear_running()`) can only be called from a thread
501running `start()` function. If `start()` spawns more theads, these
502functions CANNOT be called from them. These functions also cannot be called
503from the other three plugin functions (`init()`, `stop()` and `deinit()`).
504
505### Starting and Stopping: Stop() ###
506
507During shutdown, or after plugin `start()` function exits (whichever comes
508first), plugin's `stop()` function will be called, if defined.
509
510IMPORTANT: `start()` function runs in a different thread than `stop()`
511function. By the time `stop()` runs, depending on the circumstances,
512`start()` thread may or may not exist.
513
514IMPORTANT: `stop()` will always be called during shutdown, regardless of whether
515start() exited with error, exited successfully or is still running. `stop()`
516must be able to deal with all 3 scenarios. The rationale for this design
517decision is given Error Handling section.
518
519
520
521### Persistence in Plugin Functions ###
522
523While start() may persist, the other three functions (init(), stop() and
524deinit()) must obviously not persist, since they run in the main thread.
525Any blocking behavior exhibited in these functions (caused by a bug or
526otherwise) will cause the entire application to hang, as will start() that
527does not poll and/or honor is_running() flag.
528
529
530
531### Returning Success/Failure from Plugin Function ###
532
533Harness expects all four plugin functions (`init(), `start()`, `stop()` and
534`deinit()`) to notify it in case of an error. This is done via function:
535
536 set_error(PluginFuncEnv* env, ErrorType error, const char* format, ...);
537
538Calling this function flags that the function has failed, and passes the
539error type and string back to Harness. The converse is also true: not
540calling this function prior to exiting the function implies success.
541This distinction is important, because Harness may take certain actions
542based on the status returned by each function.
543
544IMPORTANT! Throwing exceptions from these functions is not supported.
545If your plugin uses exceptions internally, that is fine, but please
546ensure they are handled before reaching the Harness-Plugin boundary.
547
548
549### Threading Concerns ###
550
551For each plugin (independent of other plugins):
552Of the 4 plugin functions, `init()` runs first. It is guaranteed that
553it will exit before `start()` and `stop()` are called. `start()` and
554`stop()` can be called in parallel to each other, in any order, with
555their lifetimes possibly overlapping. They are guaranteed to both have
556exited before `deinit()` is called.
557
558If any of the 4 plugin functions spawn any additional threads, Harness
559makes no provisions for interacting with them in any way: calling
560Harness functions from them is not supported in particular; also such
561threads should exit before their parent function finishes running.
562
563
564
565### Error Handling ###
566
567NOTE: WL#9558 HLD version of this section additionally discusses design,
568 rationale for the approach chosen, etc; look there if interested.
569
570When plugin functions enounter an error, they are expected to signal it via
571set_error(). What happens next, depends on the case, but in all four
572cases the error will be logged automatically by the harness. Also, the first
573error passed from any plugin will be saved until the end of life-cycle
574processing, then passed down to the code calling the harness. This will allow
575the application code to deal with it accordingly (probably do some of its own
576cleaning and shut down, but that's up to the application). In general, the first
577error from init() or start() will cause the harness to initiate shut down
578procedure, while the errors from stop() and deinit() will be ignored completely
579(except of course for being logged and possibly saved for passing on at the
580end). Actions taken for each plugin function are as follows:
581
582
583
584#### init() fails:
585
586 - skip init() for remaining plugins
587
588 - don't run any start() and stop() (proceed directly to deinitialisation)
589
590 - run deinit() only for plugins initialiased so far (excluding the failing
591 one), in reverse order of initialisation, and exit
592
593 - when init() is not provided (is null), it counts as if it ran, if it would
594 have run before the failing plugin (according to topological order)
595
596
597
598#### start() fails:
599
600 - proceed to stop all plugins, then deinit() all in reverse order of
601 initialisation and exit. Please note that ALL plugins will be flagged
602 to stop and have their stop() function called (not just the ones that
603 succeeded in starting - plugin's stop() must be able to deal with such
604 a situation)
605
606
607
608#### stop() or deinit() fails:
609
610 - log error and ignore, proceed as if it didn't happen
611
612*/
613
614#ifndef MYSQL_HARNESS_LOADER_INCLUDED
615#define MYSQL_HARNESS_LOADER_INCLUDED
616
617#include "config_parser.h"
618#include "filesystem.h"
621#include "mysql/harness/plugin.h"
622
623#include "harness_export.h"
624
625#include "mpsc_queue.h"
626#include "my_compiler.h"
627
628#include <csignal>
629#include <cstdarg> // va_list
630#include <exception>
631#include <future>
632#include <istream>
633#include <list>
634#include <map>
635#include <queue>
636#include <set>
637#include <stdexcept>
638#include <string>
639#include <thread>
640#include <tuple>
641
642typedef void (*log_reopen_callback)(const std::string);
643
644#ifdef FRIEND_TEST
645class TestLoader;
646#endif
647
648namespace mysql_harness {
649
650struct Plugin;
651class Path;
652
653/**
654 * PluginFuncEnv object
655 *
656 * This object is the basis of all communication between Harness and plugin
657 * functions. It is passed to plugin functions (as an opaque pointer), and
658 * plugin functions return it back to Harness when calling Harness API
659 * functions. It has several functions:
660 *
661 * - maintains a "running" flag, which controls starting/stopping
662 * of plugins
663 *
664 * - conveys exit status back to Harness after each plugin function exits
665 *
666 * - conveys more information (AppInfo, ConfigSection, ...) to
667 * plugin functions. Note that not all fields are set for all functions -
668 * setting ConfigSection ptr when calling init() makes no sense, for example
669 *
670 * @note Below we only briefly document the methods. For more information, see
671 * Harness API documentation for their corresponding free-function proxies
672 * in plugin.h
673 */
674class HARNESS_EXPORT PluginFuncEnv {
675 public:
676 /**
677 * Constructor
678 *
679 * @param info AppInfo to pass to plugin function. Can be NULL.
680 * Pointer is owned by the caller and must outlive plugin function call.
681 * @param section ConfigSection to pass to plugin function. Can be NULL.
682 * Pointer is owned by the caller and must outlive plugin function call.
683 * @param running Set "running" flag. true = plugin should be running
684 */
685 PluginFuncEnv(const AppInfo *info, const ConfigSection *section,
686 bool running = false);
687
688 // further info getters
689 // (see also corresponding Harness API functions in plugin.h for more info)
690 const ConfigSection *get_config_section() const noexcept;
691 const AppInfo *get_app_info() const noexcept;
692
693 // running flag
694 // (see also corresponding Harness API functions in plugin.h for more info)
695 void set_running() noexcept;
696 void clear_running() noexcept;
697 bool is_running() const noexcept;
698 bool wait_for_stop(
699 uint32_t milliseconds) const noexcept; // 0 = infinite wait
700
701 // error handling
702 // (see also corresponding Harness API functions in plugin.h for more info)
703 bool exit_ok() const noexcept;
704 MY_ATTRIBUTE((format(printf, 3, 0)))
705 void set_error(ErrorType error_type, const char *fmt, va_list ap) noexcept;
706 std::tuple<std::string, std::exception_ptr> pop_error() noexcept;
707
708 private:
709 const AppInfo *app_info_; // \.
710 const ConfigSection *config_section_; // > initialized in ctor
711 bool running_; // /
712 std::string error_message_;
713 ErrorType error_type_ = kNoError;
714
715 mutable std::condition_variable cond_;
716 mutable std::mutex mutex_;
717};
718
719class HARNESS_EXPORT PluginThreads {
720 public:
721 void push_back(std::thread &&thr);
722
723 // wait for the first non-fatal exit from plugin or all plugins exited
724 // cleanly
725 void try_stopped(std::exception_ptr &first_exc);
726
727 void push_exit_status(std::exception_ptr &&eptr) {
728 plugin_stopped_events_.push(std::move(eptr));
729 }
730
731 size_t running() const { return running_; }
732
733 void wait_all_stopped(std::exception_ptr &first_exc);
734
735 void join();
736
737 private:
738 std::vector<std::thread> threads_;
739 size_t running_{0};
740
741 /**
742 * queue of events after plugin's start() function exited.
743 *
744 * nullptr if "finished without error", pointer to an exception otherwise
745 */
747};
748
749class HARNESS_EXPORT Loader {
750 public:
751 /**
752 * Constructor for Loader.
753 *
754 * @param program Name of our program
755 * @param config Router configuration
756 */
757 Loader(const std::string &program, LoaderConfig &config)
758 : config_(config), program_(program) {}
759
760 Loader(const Loader &) = delete;
761 Loader &operator=(const Loader &) = delete;
762
763 /**
764 * Destructor.
765 *
766 * The destructor will call dlclose() on all unclosed shared
767 * libraries.
768 */
769
770 ~Loader();
771
772 /**
773 * Fetch available plugins.
774 *
775 * @return List of names of available plugins.
776 */
777
778 std::list<Config::SectionKey> available() const;
779
780 /**
781 * Initialize and start all loaded plugins.
782 *
783 * All registered plugins will be initialized in proper order and
784 * started (if they have a `start` callback).
785 *
786 * @throws first exception that was triggered by an error returned from any
787 * plugin function.
788 */
789 void start();
790
791 /**
792 * Get reference to configuration object.
793 *
794 * @note In production code we initialize Loader with LoaderConfig
795 * reference maintained by DIM, so this method will return this object.
796 */
797 LoaderConfig &get_config() { return config_; }
798
799 private:
800 enum class Status { UNVISITED, ONGOING, VISITED };
801
802 /**
803 * Flags progress of Loader. The progress always proceeds from top to bottom
804 * order in this list.
805 */
806 enum class Stage {
807 // NOTE: do not alter order of these enums!
808 Unset,
809 Loading,
810 Initializing,
811 Starting,
812 Running,
813 Stopping,
814 Deinitializing,
815 Unloading,
816 };
817
818 /**
819 * Load the named plugin from a specific library.
820 *
821 * @param plugin_name Name of the plugin to be loaded.
822 *
823 * @param library_name Name of the library the plugin should be
824 * loaded from.
825 *
826 * @throws bad_plugin (std::runtime_error) on load error
827 */
828 const Plugin *load_from(const std::string &plugin_name,
829 const std::string &library_name);
830
831 const Plugin *load(const std::string &plugin_name);
832
833 /**
834 * Load the named plugin and all dependent plugins.
835 *
836 * @param plugin_name Name of the plugin to be loaded.
837 * @param key Key of the plugin to be loaded.
838 *
839 * @throws bad_plugin (std::runtime_error) on load error
840 * @throws bad_section (std::runtime_error) when section 'plugin_name' is not
841 * present in configuration
842 *
843 * @post After the execution of this procedure, the plugin and all
844 * plugins required by that plugin will be loaded.
845 */
846 /** @overload */
847 const Plugin *load(const std::string &plugin_name, const std::string &key);
848
849 // IMPORTANT design note: start_all() will block until PluginFuncEnv objects
850 // have been created for all plugins. This guarantees that the required
851 // PluginFuncEnv will always exist when plugin stop() function is called.
852
853 // start() calls these, indents reflect call hierarchy
854 void load_all(); // throws bad_plugin on load error
855 void setup_info();
856 std::exception_ptr
857 run(); // returns first exception returned from below harness functions
858 std::exception_ptr init_all(); // returns first exception triggered by init()
859
860 void
861 start_all(); // forwards first exception triggered by start() to main_loop()
862
863 std::exception_ptr
864 main_loop(); // returns first exception triggered by start() or stop()
865
866 // class stop_all() and waits for plugins the terminate
867 std::exception_ptr stop_and_wait_all();
868
869 std::exception_ptr stop_all(); // returns first exception triggered by stop()
870
871 std::exception_ptr
872 deinit_all(); // returns first exception triggered by deinit()
873
874 void unload_all();
875 size_t external_plugins_to_load_count();
876
877 /**
878 * Topological sort of all plugins and their dependencies.
879 *
880 * Will create a list of plugins in topological order from "top"
881 * to "bottom".
882 */
883 bool topsort();
884 bool visit(const std::string &name, std::map<std::string, Status> *seen,
885 std::list<std::string> *order);
886
887 /**
888 * Holds plugin's API call information
889 *
890 * @note There's 1 instance per plugin type (not plugin instance)
891 */
892 class HARNESS_EXPORT PluginInfo {
893 public:
894 PluginInfo(const std::string &folder, const std::string &libname);
895 PluginInfo(const Plugin *const plugin) : plugin_(plugin) {}
896
897 void load_plugin_descriptor(const std::string &name); // throws bad_plugin
898
899 const Plugin *plugin() const { return plugin_; }
900
901 const DynamicLibrary &library() const { return module_; }
902
903 private:
905 const Plugin *plugin_{};
906 };
907
908 using PluginMap = std::map<std::string, PluginInfo>;
909
910 // Init order is important, so keep config_ first.
911
912 /**
913 * Configuration sections for all plugins.
914 */
916
917 /**
918 * Map of all successfully-loaded plugins (without key name).
919 */
921
922 /**
923 * Map of all {plugin instance -> plugin start() PluginFuncEnv} objects.
924 * Initially these objects are created in Loader::start_all() and then kept
925 * around until the end of Loader::stop_all(). At the time of writing,
926 * PluginFuncEnv objects for remaining plugin functions (init(), stop() and
927 * deinit()) are not required to live beyond their respective functions calls,
928 * and are therefore created on stack (automatic variables) as needed during
929 * those calls.
930 */
931 std::map<const ConfigSection *, std::shared_ptr<PluginFuncEnv>>
933
934 /**
935 * active plugin threads.
936 */
938
939 /**
940 * Initialization order.
941 */
942 std::list<std::string> order_;
943
944 std::string logging_folder_;
945 std::string plugin_folder_;
946 std::string runtime_folder_;
947 std::string config_folder_;
948 std::string data_folder_;
949 std::string program_;
950 AppInfo appinfo_;
951
952 void spawn_signal_handler_thread();
953
955 std::condition_variable signal_thread_ready_cond_;
956 bool signal_thread_ready_{false};
957 std::thread signal_thread_;
958
959 /**
960 * Checks if all the options in the configuration fed to the Loader are
961 * supported.
962 *
963 * @throws std::runtime_error if there is unsupported option in the
964 * configuration
965 */
966 void check_config_options_supported();
967
968 /**
969 * Checks if all the options in the section [DEFAULT] in the configuration fed
970 * to the Loader are supported.
971 *
972 * @throws std::runtime_error if there is unsupported option in the [DEFAULT]
973 * section of the configuration
974 */
975 void check_default_config_options_supported();
976
977#ifdef FRIEND_TEST
978 friend class ::TestLoader;
979#endif
980
981}; // class Loader
982
984 public:
985 /**
986 * @throws std::system_error if out of threads
987 */
989 // rely on move semantics
992 }
993
994 /**
995 * stop the log_reopen_thread_function.
996 *
997 * @throws std::system_error from request_application_shutdown()
998 */
999 void stop();
1000
1001 /**
1002 * join the log_reopen thread.
1003 *
1004 * @throws std::system_error same as std::thread::join
1005 */
1006 void join();
1007
1008 /**
1009 * destruct the thread.
1010 *
1011 * Same as std::thread it may call std::terminate in case the thread isn't
1012 * joined yet, but joinable.
1013 *
1014 * In case join() fails as best-effort, a log-message is attempted to be
1015 * written.
1016 */
1018
1019 /**
1020 * thread function
1021 */
1023
1024 /**
1025 * request reopen
1026 *
1027 * @note Empty dst will cause reopen only, and the old content will not be
1028 * moved to dst.
1029 * @note This method uses mutex::try_lock() to avoid blocking the interrupt
1030 * handler if a signal is received during an already ongoing concurrent
1031 * reopen. The consequence is that reopen requests are ignored if rotation is
1032 * already in progress.
1033 *
1034 * @param dst filename to use for old log file during reopen
1035 * @throws std::system_error same as std::unique_lock::lock does
1036 */
1037 void request_reopen(const std::string &dst = "");
1038
1039 /* Log reopen state triplet */
1041
1042 /* Check log reopen completed */
1043 bool is_completed() const { return (state_ == REOPEN_NONE); }
1044
1045 /* Check log reopen requested */
1046 bool is_requested() const { return (state_ == REOPEN_REQUESTED); }
1047
1048 /* Check log reopen active */
1049 bool is_active() const { return (state_ == REOPEN_ACTIVE); }
1050
1051 /* Retrieve error from the last reopen */
1052 std::string get_last_error() const { return errmsg_; }
1053
1054 private:
1055 /* The thread handle */
1056 std::thread reopen_thr_;
1057
1058 /* The log reopen thread state */
1060
1061 /* The last error message from the log reopen thread */
1062 std::string errmsg_;
1063
1064 /* The destination filename to use for the old logfile during reopen */
1065 std::string dst_;
1066
1067}; // class LogReopenThread
1068
1069} // namespace mysql_harness
1070
1071/**
1072 * Setter for the log reopen thread completion callback function.
1073 *
1074 * @param cb Function to call at completion.
1075 */
1076HARNESS_EXPORT
1078
1079/**
1080 * The default implementation for log reopen thread completion callback
1081 * function.
1082 *
1083 * @param errmsg Error message. Empty string assumes successful completion.
1084 */
1085HARNESS_EXPORT
1086void default_log_reopen_complete_cb(const std::string errmsg);
1087
1088/*
1089 * Reason for shutdown
1090 */
1092
1093/**
1094 * request application shutdown.
1095 *
1096 * @param reason reason for the shutdown
1097 * @throws std::system_error same as std::unique_lock::lock does
1098 */
1099HARNESS_EXPORT
1101 const ShutdownReason reason = SHUTDOWN_REQUESTED);
1102
1103/**
1104 * notify a "log_reopen" is requested with optional filename for old logfile.
1105 *
1106 * @param dst rename old logfile to filename before reopen
1107 * @throws std::system_error same as std::unique_lock::lock does
1108 */
1109HARNESS_EXPORT
1110void request_log_reopen(const std::string &dst = "");
1111
1112/**
1113 * check reopen completed
1114 */
1115HARNESS_EXPORT
1117
1118/**
1119 * get last log reopen error
1120 */
1121HARNESS_EXPORT
1122std::string log_reopen_get_error();
1123
1124#ifdef _WIN32
1125HARNESS_EXPORT
1126void register_ctrl_c_handler();
1127#endif
1128
1129#ifdef FRIEND_TEST
1130namespace unittest_backdoor {
1131HARNESS_EXPORT
1132void set_shutdown_pending(bool shutdown_pending);
1133} // namespace unittest_backdoor
1134#endif
1135
1136#endif /* MYSQL_HARNESS_LOADER_INCLUDED */
A helper class for handling file paths.
Definition: path.h:37
Configuration section.
Definition: config_parser.h:140
A DynamicLibrary.
Definition: dynamic_loader.h:72
Configuration file handler for the loader.
Definition: loader_config.h:45
Holds plugin's API call information.
Definition: loader.h:892
const Plugin * plugin() const
Definition: loader.h:899
DynamicLibrary module_
Definition: loader.h:904
const DynamicLibrary & library() const
Definition: loader.h:901
PluginInfo(const Plugin *const plugin)
Definition: loader.h:895
Definition: loader.h:749
std::list< std::string > order_
Initialization order.
Definition: loader.h:942
Stage
Flags progress of Loader.
Definition: loader.h:806
std::string logging_folder_
Definition: loader.h:944
Loader(const std::string &program, LoaderConfig &config)
Constructor for Loader.
Definition: loader.h:757
std::string data_folder_
Definition: loader.h:948
std::thread signal_thread_
Definition: loader.h:957
LoaderConfig & config_
Configuration sections for all plugins.
Definition: loader.h:915
LoaderConfig & get_config()
Get reference to configuration object.
Definition: loader.h:797
std::mutex signal_thread_ready_m_
Definition: loader.h:954
Loader & operator=(const Loader &)=delete
std::string config_folder_
Definition: loader.h:947
std::condition_variable signal_thread_ready_cond_
Definition: loader.h:955
Loader(const Loader &)=delete
std::map< std::string, PluginInfo > PluginMap
Definition: loader.h:908
std::string program_
Definition: loader.h:949
std::string plugin_folder_
Definition: loader.h:945
AppInfo appinfo_
Definition: loader.h:950
Status
Definition: loader.h:800
PluginThreads plugin_threads_
active plugin threads.
Definition: loader.h:937
std::map< const ConfigSection *, std::shared_ptr< PluginFuncEnv > > plugin_start_env_
Map of all {plugin instance -> plugin start() PluginFuncEnv} objects.
Definition: loader.h:932
PluginMap plugins_
Map of all successfully-loaded plugins (without key name).
Definition: loader.h:920
std::string runtime_folder_
Definition: loader.h:946
Definition: loader.h:983
bool is_completed() const
Definition: loader.h:1043
std::string dst_
Definition: loader.h:1065
~LogReopenThread()
destruct the thread.
Definition: loader.cc:1483
std::string errmsg_
Definition: loader.h:1062
LogReopenState
Definition: loader.h:1040
@ REOPEN_ACTIVE
Definition: loader.h:1040
@ REOPEN_REQUESTED
Definition: loader.h:1040
@ REOPEN_NONE
Definition: loader.h:1040
std::thread reopen_thr_
Definition: loader.h:1056
static void log_reopen_thread_function(LogReopenThread *t)
thread function
Definition: loader.cc:1508
bool is_active() const
Definition: loader.h:1049
std::string get_last_error() const
Definition: loader.h:1052
void join()
join the log_reopen thread.
Definition: loader.cc:1478
void stop()
stop the log_reopen_thread_function.
Definition: loader.cc:1473
LogReopenState state_
Definition: loader.h:1059
bool is_requested() const
Definition: loader.h:1046
LogReopenThread()
Definition: loader.h:988
void request_reopen(const std::string &dst="")
request reopen
Definition: loader.cc:1554
PluginFuncEnv object.
Definition: loader.h:674
std::mutex mutex_
Definition: loader.h:716
const AppInfo * app_info_
Definition: loader.h:709
bool running_
Definition: loader.h:711
const ConfigSection * config_section_
Definition: loader.h:710
std::string error_message_
Definition: loader.h:712
std::condition_variable cond_
Definition: loader.h:715
Definition: loader.h:719
size_t running() const
Definition: loader.h:731
WaitingMPSCQueue< std::exception_ptr > plugin_stopped_events_
queue of events after plugin's start() function exited.
Definition: loader.h:746
void push_exit_status(std::exception_ptr &&eptr)
Definition: loader.h:727
std::vector< std::thread > threads_
Definition: loader.h:738
provide waiting pop and push operator to thread-safe queues.
Definition: waiting_queue_adaptor.h:38
error_type
Definition: error.h:35
Fido Client Authentication Plugin
Definition: fido_client_plugin.cc:220
static void start(mysql_harness::PluginFuncEnv *env)
Definition: http_auth_backend_plugin.cc:168
static void run(mysql_harness::PluginFuncEnv *)
Definition: io_plugin.cc:188
HARNESS_EXPORT bool log_reopen_completed()
check reopen completed
Definition: loader.cc:190
HARNESS_EXPORT void default_log_reopen_complete_cb(const std::string errmsg)
The default implementation for log reopen thread completion callback function.
Definition: loader.cc:276
ShutdownReason
Definition: loader.h:1091
@ SHUTDOWN_FATAL_ERROR
Definition: loader.h:1091
@ SHUTDOWN_NONE
Definition: loader.h:1091
@ SHUTDOWN_REQUESTED
Definition: loader.h:1091
HARNESS_EXPORT std::string log_reopen_get_error()
get last log reopen error
Definition: loader.cc:200
HARNESS_EXPORT void request_log_reopen(const std::string &dst="")
notify a "log_reopen" is requested with optional filename for old logfile.
Definition: loader.cc:181
HARNESS_EXPORT void set_log_reopen_complete_callback(log_reopen_callback cb)
Setter for the log reopen thread completion callback function.
Definition: loader.cc:266
HARNESS_EXPORT void request_application_shutdown(const ShutdownReason reason=SHUTDOWN_REQUESTED)
request application shutdown.
Definition: loader.cc:163
void(* log_reopen_callback)(const std::string)
Definition: loader.h:642
Header for compiler-dependent features.
Log info(cout, "NOTE")
bool load(THD *, const dd::String_type &fname, dd::String_type *buf)
Read an sdi file from disk and store in a buffer.
Definition: sdi_file.cc:307
Definition: common.h:41
const ConfigSection * get_config_section(const PluginFuncEnv *env) noexcept
Definition: loader.cc:396
const AppInfo * get_app_info(const PluginFuncEnv *env) noexcept
Definition: loader.cc:392
void set_error(PluginFuncEnv *env, ErrorType error_type, const char *fmt,...) noexcept
Definition: loader.cc:412
std::string join(Container cont, const std::string &delim)
join elements of an container into a string separated by a delimiter.
Definition: string.h:150
bool is_running(const PluginFuncEnv *env) noexcept
Definition: loader.cc:402
bool wait_for_stop(const PluginFuncEnv *env, uint32_t milliseconds) noexcept
Definition: loader.cc:404
void clear_running(PluginFuncEnv *env) noexcept
Definition: loader.cc:408
Definition: loader.cc:1583
HARNESS_EXPORT void set_shutdown_pending(bool shutdown_pending)
Definition: loader.cc:1585
required string key
Definition: replication_asynchronous_connection_failover.proto:59
LEX_CSTRING * plugin_name(st_plugin_int **ref)
Definition: sql_plugin_ref.h:94
case opt name
Definition: sslopt-case.h:32