MySQL 26.7.0
Source Code Documentation
task_registry_multi.h
Go to the documentation of this file.
1// Copyright (c) 2026, Oracle and/or its affiliates.
2//
3// This program is free software; you can redistribute it and/or modify
4// it under the terms of the GNU General Public License, version 2.0,
5// as published by the Free Software Foundation.
6//
7// This program is designed to work with certain software (including
8// but not limited to OpenSSL) that is licensed under separate terms,
9// as designated in a particular file or component or in included license
10// documentation. The authors of MySQL hereby grant you an additional
11// permission to link the program and your derivative works with the
12// separately licensed software that they have either included with
13// the program or referenced in the documentation.
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, version 2.0, 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#ifndef MYSQL_SCHEDULER_TASK_REGISTRY_MULTI_H
25#define MYSQL_SCHEDULER_TASK_REGISTRY_MULTI_H
26
27#include <atomic>
28#include <cassert>
29#include <memory>
30#include <optional>
31#include <unordered_map>
32#include <vector>
37
38namespace mysql::scheduler {
39
40/// @brief This template class provides a concurrent registry for tasks that
41/// handles hash conflicts by allowing multiple task entries to coexist in the
42/// same hash bucket. It implements thread-safe access to task objects
43/// using a bucketed hash table with locks per bucket.
44///
45/// Threads do not contend when they operate on separate buckets. When a hash
46/// conflict occurs, threads use spin locks to lock requested resources
47/// (bucket).
48///
49/// Spin locks excel when locks are held for very brief periods, such as in this
50/// registry, where operations like checking task activation or updating
51/// entries are quick atomic operations. The spin lock avoids expensive context
52/// switches that std::mutex would incur.
53///
54/// Each bucket (determined by task ID hash) contains an unordered_map mapping
55/// task IDs to their associated objects and state. This design resolves
56/// collisions that could occur with simple hashing, ensuring that tasks with
57/// IDs mapping to the same bucket index can be stored and accessed efficiently.
58///
59/// Key features:
60/// - Hash conflict resolution: Multiple tasks per bucket using
61/// std::unordered_map.
62/// - Thread-safe: Each bucket has its own mutex.
63/// - Limited API: Only essential operations for task management.
64/// - Template-based: Supports various task ID and object types.
65///
66/// Usage example:
67/// @code
68/// Task_registry_multi<Task_id, Task_info> registry(1000);
69/// registry.activate(task_id, Task_info{...});
70/// registry.apply(task_id, [](Task_info& info) { /* operate on info */ });
71/// registry.deactivate(task_id);
72/// @endcode
73template <typename Task_id_type, typename Task_object_type>
75 public:
76 using Task_object = Task_object_type;
78
79 static constexpr std::size_t default_capacity = 16384;
80
81 /// @brief Constructs buckets using defined capacity
82 Task_registry_multi(std::size_t capacity = default_capacity) {
83 m_buckets.reserve(capacity);
84 for (std::size_t idx = 0; idx < capacity; ++idx) {
85 m_buckets.emplace_back(std::make_unique<Bucket>());
86 }
87 }
88
89 /// Destruct registry, deinitialize if applicable
91
92 /// @brief Calls 'func' on a given task object if active
93 /// @param id Task id
94 /// @param func function to be applied on a given registered task
95 /// @return true if task was found and active, false otherwise
96 template <typename T>
97 [[nodiscard]] bool apply(Task_id_type id, T &&func) {
98 auto &bucket = get_bucket(id);
99 std::scoped_lock scope_guard(bucket->m_lock);
100 auto it = bucket->m_entries.find(id);
101 if (it != bucket->m_entries.end()) {
102 func(it->second.m_obj);
103 return true;
104 }
105 return false;
106 }
107
108 /// @brief Registers and activates a task
109 /// @param id Task id
110 /// @param obj Task object
111 /// @return true if the previous state was inactive (i.e., the task was newly
112 /// registered), false if the previous state was active (task already present)
113 /// @throws std::bad_alloc If memory allocation for the new entry fails
114 [[nodiscard]] bool activate(Task_id_type id, Task_object &&obj) {
115 auto &bucket = get_bucket(id);
116 std::scoped_lock scope_guard(bucket->m_lock);
117 auto map_entry = bucket->m_entries.emplace(
118 std::piecewise_construct, std::forward_as_tuple(id),
119 std::forward_as_tuple(std::move(obj)));
120 if (map_entry.second) {
121 bucket->m_num_entries.fetch_add(1, std::memory_order_relaxed);
122 }
123 return map_entry.second;
124 }
125
126 /// @brief Deactivates a task
127 /// @param id Task id
128 /// @return true if deactivated, false if not found or not active
129 [[nodiscard]] bool deactivate(Task_id_type id) {
130 auto &bucket = get_bucket(id);
131 std::scoped_lock scope_guard(bucket->m_lock);
132 size_t erased = bucket->m_entries.erase(id);
133 if (erased != 0) {
134 bucket->m_num_entries.fetch_sub(1, std::memory_order_relaxed);
135 }
136 return erased != 0;
137 }
138
139 /// @brief Checks if the bucket for the given task ID is active (contains any
140 /// tasks)
141 /// @param id Task id
142 /// @return true if bucket has at least one active task, false otherwise
143 [[nodiscard]] bool bucket_active(Task_id_type id) const {
144 size_t hash_value = id.get() % m_buckets.size();
145 return m_buckets[hash_value]->m_num_entries.load(
146 std::memory_order_relaxed) > 0;
147 }
148
149 private:
150 /// Each bucket is capable of handling many subentries. This structure
151 /// represents a bucket subentry, which is visible to the caller as
152 /// registry entry. Access to entry is protected with a bucket lock - all
153 /// entries in the same bucket share the same lock. Entries are put into
154 /// the same bucket when they share the same registry hash (hash conflict).
155 struct Sub_entry {
156 /// Internal object, type defined by the caller
158 /// Construct from object
159 /// @param obj Object handled by the Task_registry_multi
160 /// Access to the object is protected with registry bucket lock
161 /// (Subentries share the same lock)
162 Sub_entry(Task_object &&obj) : m_obj(std::move(obj)) {}
163 };
164
165 /// Represents a bucket, capable of handling many subentries. Access to the
166 /// bucket is protected with internal lock
167 struct alignas(
169 concurrency::detail::hardware_destructive_interference_size) Bucket {
171 std::unordered_map<Task_id_type, Sub_entry> m_entries;
173 std::atomic<size_t> m_num_entries{0};
174 };
176 using Bucket_ptr = std::unique_ptr<Bucket>;
177
178 /// @brief Obtains the bucket handle handling a given id, for internal use
179 /// @param id Task id
180 /// @return Bucket handle
181 [[nodiscard]] Bucket_ptr &get_bucket(Task_id_type id) {
182 auto hash_value = id.get() % m_buckets.size();
183 assert(m_buckets[hash_value]);
184 return m_buckets[hash_value];
185 }
186
187 /// Buckets, synchronized independently
188 std::vector<Bucket_ptr> m_buckets;
189};
190
191} // namespace mysql::scheduler
192
#endif // MYSQL_SCHEDULER_TASK_REGISTRY_MULTI_H
Implementation of a spin-lock mutex.
Definition: spin_lock_mutex.h:59
This template class provides a concurrent registry for tasks that handles hash conflicts by allowing ...
Definition: task_registry_multi.h:74
std::unique_ptr< Bucket > Bucket_ptr
Definition: task_registry_multi.h:175
std::vector< Bucket_ptr > m_buckets
Buckets, synchronized independently.
Definition: task_registry_multi.h:187
bool bucket_active(Task_id_type id) const
Checks if the bucket for the given task ID is active (contains any tasks)
Definition: task_registry_multi.h:143
~Task_registry_multi()=default
Destruct registry, deinitialize if applicable.
Bucket_ptr & get_bucket(Task_id_type id)
Obtains the bucket handle handling a given id, for internal use.
Definition: task_registry_multi.h:180
bool activate(Task_id_type id, Task_object &&obj)
Registers and activates a task.
Definition: task_registry_multi.h:114
Task_object_type Task_object
Definition: task_registry_multi.h:76
Task_registry_multi(std::size_t capacity=default_capacity)
Constructs buckets using defined capacity.
Definition: task_registry_multi.h:82
static constexpr std::size_t default_capacity
Definition: task_registry_multi.h:79
bool apply(Task_id_type id, T &&func)
Calls 'func' on a given task object if active.
Definition: task_registry_multi.h:97
bool deactivate(Task_id_type id)
Deactivates a task.
Definition: task_registry_multi.h:129
#define T
Definition: jit_executor_value.cc:373
Definition: base_dependency_tracker.h:41
Define std::hash<Gtid>.
Definition: gtid.h:355
Definition: completion_hash.h:40
Represents a bucket, capable of handling many subentries.
Definition: task_registry_multi.h:168
std::unordered_map< Task_id_type, Sub_entry > m_entries
Definition: task_registry_multi.h:170
Bucket()
Definition: task_registry_multi.h:169
std::atomic< size_t > m_num_entries
Definition: task_registry_multi.h:172
Mutex m_lock
Definition: task_registry_multi.h:171
Each bucket is capable of handling many subentries.
Definition: task_registry_multi.h:155
Sub_entry(Task_object &&obj)
Construct from object.
Definition: task_registry_multi.h:162
Task_object m_obj
Internal object, type defined by the caller.
Definition: task_registry_multi.h:157