Skip to content

Commit 060c796

Browse files
AooooooA-CSuper User
authored andcommitted
Optimize ASU task manager with lock-free slots
1 parent c9a05d1 commit 060c796

4 files changed

Lines changed: 1366 additions & 1 deletion

File tree

ucm/transport/kv/asu/common/task_manager_base.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,4 @@ class TaskManagerBase {
9797
std::unordered_map<TaskId, std::shared_ptr<Context>> tasks_;
9898
};
9999

100-
} // namespace UC::ASU
100+
} // namespace UC::ASU
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
/**
2+
* MIT License
3+
*
4+
* Copyright (c) 2026 Huawei Technologies Co., Ltd. All rights reserved.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
* */
24+
#pragma once
25+
26+
#include <algorithm>
27+
#include <atomic>
28+
#include <cstddef>
29+
#include <cstdint>
30+
#include <limits>
31+
#include <memory>
32+
#include <string>
33+
#include <type_traits>
34+
#include <utility>
35+
#include <vector>
36+
#include "asu_transport/types.h"
37+
#include "thread/index_pool.h"
38+
39+
namespace UC::ASU {
40+
41+
template <typename Context, typename State>
42+
class SlotTaskManagerBase {
43+
public:
44+
static constexpr std::size_t kMinSlotCount = 1024;
45+
static constexpr std::size_t kDefaultMaxInflightTasks = 4096;
46+
47+
static std::size_t RecommendSlotCount(std::size_t maxInflightTasks)
48+
{
49+
// Reserve extra slots for the configured maximum inflight workload.
50+
// For example: 4096 inflight tasks -> 8192 slots.
51+
const auto required = std::max<std::size_t>(kMinSlotCount, maxInflightTasks * 2);
52+
return NormalizeSlotCount(required);
53+
}
54+
55+
explicit SlotTaskManagerBase(State initialState, std::string taskName,
56+
std::size_t maxInflightTasks = kDefaultMaxInflightTasks)
57+
: initialState_(initialState),
58+
taskName_(std::move(taskName)),
59+
slotIndexBits_(ComputeSlotIndexBits(RecommendSlotCount(maxInflightTasks))),
60+
slots_(RecommendSlotCount(maxInflightTasks)),
61+
slotMask_(slots_.size() - 1)
62+
{
63+
freeList_.Setup(static_cast<IndexPool::Index>(slots_.size()));
64+
}
65+
66+
Status Submit(std::unique_ptr<Context> ctx, TaskId& taskId)
67+
{
68+
if (!ctx) {
69+
taskId = kInvalidTaskId;
70+
return Status::Error(StatusCode::INVALID_ARGUMENT, taskName_ + " task context is null");
71+
}
72+
73+
const auto acquiredIndex = freeList_.Acquire();
74+
if (acquiredIndex == IndexPool::npos) {
75+
taskId = kInvalidTaskId;
76+
return Status::Error(StatusCode::INTERNAL_ERROR, taskName_ + " task table is full");
77+
}
78+
79+
const auto slotIndex = static_cast<std::size_t>(acquiredIndex);
80+
auto& slot = slots_[slotIndex];
81+
82+
std::uint8_t expected = SlotState::EMPTY;
83+
if (!slot.state.compare_exchange_strong(expected, SlotState::WRITING,
84+
std::memory_order_acq_rel,
85+
std::memory_order_acquire)) {
86+
freeList_.Release(acquiredIndex);
87+
taskId = kInvalidTaskId;
88+
return Status::Error(StatusCode::INTERNAL_ERROR, taskName_ + " task slot not empty");
89+
}
90+
91+
const auto generation = slot.generation.fetch_add(1, std::memory_order_relaxed) + 1;
92+
if (!CanEncodeGeneration(generation)) {
93+
AtomicStoreCtx(slot, std::shared_ptr<Context>{}, std::memory_order_release);
94+
slot.taskId.store(kInvalidTaskId, std::memory_order_release);
95+
slot.state.store(SlotState::EMPTY, std::memory_order_release);
96+
freeList_.Release(acquiredIndex);
97+
taskId = kInvalidTaskId;
98+
return Status::Error(StatusCode::INTERNAL_ERROR,
99+
taskName_ + " task id generation overflow");
100+
}
101+
102+
const auto newTaskId = MakeTaskId(slotIndex, generation);
103+
ctx->state.store(initialState_, std::memory_order_release);
104+
ctx->taskId = newTaskId;
105+
auto sharedCtx = std::shared_ptr<Context>(std::move(ctx));
106+
107+
AtomicStoreCtx(slot, sharedCtx, std::memory_order_release);
108+
slot.taskId.store(newTaskId, std::memory_order_release);
109+
slot.state.store(SlotState::READY, std::memory_order_release);
110+
111+
taskId = newTaskId;
112+
return Status::OK();
113+
}
114+
115+
std::shared_ptr<Context> Get(TaskId taskId)
116+
{
117+
if (taskId == kInvalidTaskId) { return nullptr; }
118+
119+
const auto slotIndex = static_cast<std::size_t>(ToTaskIdUInt(taskId) & slotMask_);
120+
auto& slot = slots_[slotIndex];
121+
122+
const auto state1 = slot.state.load(std::memory_order_acquire);
123+
if (state1 != SlotState::READY) { return nullptr; }
124+
125+
const auto id1 = slot.taskId.load(std::memory_order_acquire);
126+
if (id1 != taskId) { return nullptr; }
127+
128+
auto ptr = AtomicLoadCtx(slot, std::memory_order_acquire);
129+
if (!ptr) { return nullptr; }
130+
131+
const auto id2 = slot.taskId.load(std::memory_order_acquire);
132+
const auto state2 = slot.state.load(std::memory_order_acquire);
133+
if (state2 == SlotState::READY && id2 == taskId && ptr->taskId == taskId) { return ptr; }
134+
135+
return nullptr;
136+
}
137+
138+
std::vector<std::shared_ptr<Context>> GetAll()
139+
{
140+
std::vector<std::shared_ptr<Context>> tasks;
141+
for (const auto& slot : slots_) {
142+
if (slot.state.load(std::memory_order_acquire) != SlotState::READY) { continue; }
143+
144+
auto ctx = AtomicLoadCtx(slot, std::memory_order_acquire);
145+
if (!ctx) { continue; }
146+
147+
const auto taskId = slot.taskId.load(std::memory_order_acquire);
148+
const auto state = slot.state.load(std::memory_order_acquire);
149+
if (state == SlotState::READY && taskId == ctx->taskId) {
150+
tasks.emplace_back(std::move(ctx));
151+
}
152+
}
153+
return tasks;
154+
}
155+
156+
Status Remove(TaskId taskId)
157+
{
158+
if (taskId == kInvalidTaskId) {
159+
return Status::Error(StatusCode::TASK_NOT_FOUND, taskName_ + " task not found");
160+
}
161+
162+
const auto slotIndex = static_cast<std::size_t>(ToTaskIdUInt(taskId) & slotMask_);
163+
auto& slot = slots_[slotIndex];
164+
165+
if (slot.state.load(std::memory_order_acquire) != SlotState::READY) {
166+
return Status::Error(StatusCode::TASK_NOT_FOUND, taskName_ + " task not found");
167+
}
168+
169+
auto expectedTaskId = taskId;
170+
if (!slot.taskId.compare_exchange_strong(expectedTaskId, kInvalidTaskId,
171+
std::memory_order_acq_rel,
172+
std::memory_order_acquire)) {
173+
return Status::Error(StatusCode::TASK_NOT_FOUND, taskName_ + " task not found");
174+
}
175+
176+
slot.state.store(SlotState::REMOVING, std::memory_order_release);
177+
AtomicStoreCtx(slot, std::shared_ptr<Context>{}, std::memory_order_release);
178+
slot.state.store(SlotState::EMPTY, std::memory_order_release);
179+
180+
freeList_.Release(static_cast<IndexPool::Index>(slotIndex));
181+
return Status::OK();
182+
}
183+
184+
private:
185+
using TaskIdUInt = std::make_unsigned_t<TaskId>;
186+
187+
struct SlotState {
188+
static constexpr std::uint8_t EMPTY = 0;
189+
static constexpr std::uint8_t WRITING = 1;
190+
static constexpr std::uint8_t READY = 2;
191+
static constexpr std::uint8_t REMOVING = 3;
192+
};
193+
194+
struct alignas(64) Slot {
195+
std::atomic<std::uint8_t> state{SlotState::EMPTY};
196+
std::atomic<TaskIdUInt> generation{0};
197+
std::atomic<TaskId> taskId{kInvalidTaskId};
198+
std::shared_ptr<Context> ctx;
199+
};
200+
201+
private:
202+
static TaskIdUInt ToTaskIdUInt(TaskId taskId) { return static_cast<TaskIdUInt>(taskId); }
203+
204+
static std::size_t NormalizeSlotCount(std::size_t n)
205+
{
206+
n = std::max<std::size_t>(n, kMinSlotCount);
207+
208+
std::size_t power = 1;
209+
while (power < n) {
210+
if (power > (std::numeric_limits<std::size_t>::max() >> 1)) { return power; }
211+
power <<= 1;
212+
}
213+
214+
return power;
215+
}
216+
217+
static std::size_t ComputeSlotIndexBits(std::size_t slotCount)
218+
{
219+
std::size_t bits = 0;
220+
for (auto s = slotCount; s > 1; s >>= 1) { ++bits; }
221+
return bits;
222+
}
223+
224+
bool CanEncodeGeneration(TaskIdUInt generation) const
225+
{
226+
constexpr std::size_t kTotalBits =
227+
static_cast<std::size_t>(std::numeric_limits<TaskIdUInt>::digits);
228+
229+
if (slotIndexBits_ >= kTotalBits) { return false; }
230+
231+
if (generation == 0) { return false; }
232+
233+
const auto generationBits = kTotalBits - slotIndexBits_;
234+
if (generationBits >= kTotalBits) { return true; }
235+
236+
const auto maxGeneration = (static_cast<TaskIdUInt>(1) << generationBits) - 1;
237+
return generation <= maxGeneration;
238+
}
239+
240+
TaskId MakeTaskId(std::size_t slotIndex, TaskIdUInt generation) const
241+
{
242+
const auto raw =
243+
(generation << slotIndexBits_) | static_cast<TaskIdUInt>(slotIndex & slotMask_);
244+
return static_cast<TaskId>(raw);
245+
}
246+
247+
static std::shared_ptr<Context> AtomicLoadCtx(const Slot& slot, std::memory_order order)
248+
{
249+
return std::atomic_load_explicit(&slot.ctx, order);
250+
}
251+
252+
static void AtomicStoreCtx(Slot& slot, std::shared_ptr<Context> ptr, std::memory_order order)
253+
{
254+
std::atomic_store_explicit(&slot.ctx, std::move(ptr), order);
255+
}
256+
257+
State initialState_;
258+
std::string taskName_;
259+
std::size_t slotIndexBits_{0};
260+
261+
std::vector<Slot> slots_;
262+
std::size_t slotMask_{0};
263+
IndexPool freeList_;
264+
};
265+
266+
} // namespace UC::ASU

0 commit comments

Comments
 (0)