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