2323 * */
2424#pragma once
2525
26+ #include < algorithm>
2627#include < atomic>
28+ #include < cstddef>
29+ #include < cstdint>
30+ #include < limits>
2731#include < memory>
28- #include < mutex>
2932#include < string>
30- #include < unordered_map >
33+ #include < type_traits >
3134#include < utility>
35+ #include < vector>
3236#include " asu_transport/types.h"
3337
3438namespace UC ::ASU {
3539
3640template <typename Context, typename State>
3741class TaskManagerBase {
3842public:
39- TaskManagerBase (State initialState, std::string taskName)
40- : initialState_(initialState), taskName_(std::move(taskName))
43+ static constexpr std::size_t kMinSlotCount = 1024 ;
44+ static constexpr std::size_t kDefaultMaxInflightTasks = 4096 ;
45+
46+ static std::size_t RecommendSlotCount (std::size_t maxInflightTasks)
47+ {
48+ // Reserve extra slots for the configured maximum inflight workload.
49+ // For example: 4096 inflight tasks -> 8192 slots.
50+ const auto required = std::max<std::size_t >(kMinSlotCount , maxInflightTasks * 2 );
51+ return NormalizeSlotCount (required);
52+ }
53+
54+ explicit TaskManagerBase (State initialState, std::string taskName,
55+ std::size_t maxInflightTasks = kDefaultMaxInflightTasks )
56+ : initialState_(initialState),
57+ taskName_(std::move(taskName)),
58+ slotIndexBits_(ComputeSlotIndexBits(RecommendSlotCount(maxInflightTasks))),
59+ slots_(RecommendSlotCount(maxInflightTasks)),
60+ slotMask_(slots_.size() - 1),
61+ freeListShift_(slotIndexBits_ + 1 ),
62+ freeListMask_(MakeLowBitsMask(slotIndexBits_ + 1 )),
63+ freeListEnd_(slots_.size()),
64+ freeListHead_(PackFreeListHead(0 , 0 ))
4165 {
66+ for (std::size_t i = 0 ; i + 1 < slots_.size (); ++i) {
67+ slots_[i].freeNext .store (i + 1 , std::memory_order_relaxed);
68+ }
69+ slots_[slots_.size () - 1 ].freeNext .store (freeListEnd_, std::memory_order_relaxed);
4270 }
4371
4472 Status Submit (std::unique_ptr<Context> ctx, TaskId& taskId)
@@ -48,53 +76,249 @@ class TaskManagerBase {
4876 return Status::Error (StatusCode::INVALID_ARGUMENT , taskName_ + " task context is null" );
4977 }
5078
79+ auto slotIndex = FreeListPop ();
80+ if (slotIndex == freeListEnd_) {
81+ taskId = kInvalidTaskId ;
82+ return Status::Error (StatusCode::INTERNAL_ERROR , taskName_ + " task table is full" );
83+ }
84+
85+ auto & slot = slots_[slotIndex];
86+
87+ std::uint8_t expected = SlotState::EMPTY ;
88+ if (!slot.state .compare_exchange_strong (expected, SlotState::WRITING ,
89+ std::memory_order_acq_rel,
90+ std::memory_order_acquire)) {
91+ FreeListPush (slotIndex);
92+ taskId = kInvalidTaskId ;
93+ return Status::Error (StatusCode::INTERNAL_ERROR , taskName_ + " task slot not empty" );
94+ }
95+
96+ const auto generation = slot.generation .fetch_add (1 , std::memory_order_relaxed) + 1 ;
97+ if (!CanEncodeGeneration (generation)) {
98+ AtomicStoreCtx (slot, std::shared_ptr<Context>{}, std::memory_order_release);
99+ slot.taskId .store (kInvalidTaskId , std::memory_order_release);
100+ slot.state .store (SlotState::EMPTY , std::memory_order_release);
101+ FreeListPush (slotIndex);
102+ taskId = kInvalidTaskId ;
103+ return Status::Error (StatusCode::INTERNAL_ERROR ,
104+ taskName_ + " task id generation overflow" );
105+ }
106+
107+ const auto newTaskId = MakeTaskId (slotIndex, generation);
108+ ctx->state .store (initialState_, std::memory_order_release);
109+ ctx->taskId = newTaskId;
51110 auto sharedCtx = std::shared_ptr<Context>(std::move (ctx));
52- sharedCtx->state .store (initialState_, std::memory_order_release);
53111
54- std::lock_guard<std::mutex> lock (mutex_);
55- do {
56- taskId = nextTaskId_.fetch_add (1 , std::memory_order_relaxed);
57- } while (taskId == kInvalidTaskId || tasks_.find (taskId) != tasks_.end ());
112+ AtomicStoreCtx (slot, sharedCtx, std::memory_order_release);
113+ slot.taskId .store (newTaskId, std::memory_order_release);
114+ slot.state .store (SlotState::READY , std::memory_order_release);
58115
59- sharedCtx->taskId = taskId;
60- tasks_.emplace (taskId, std::move (sharedCtx));
116+ taskId = newTaskId;
61117 return Status::OK ();
62118 }
63119
64120 std::shared_ptr<Context> Get (TaskId taskId)
65121 {
66- std::lock_guard<std::mutex> lock (mutex_);
67- auto iter = tasks_.find (taskId);
68- if (iter == tasks_.end ()) { return nullptr ; }
69- return iter->second ;
122+ if (taskId == kInvalidTaskId ) { return nullptr ; }
123+
124+ const auto slotIndex = static_cast <std::size_t >(ToTaskIdUInt (taskId) & slotMask_);
125+ auto & slot = slots_[slotIndex];
126+
127+ const auto state1 = slot.state .load (std::memory_order_acquire);
128+ if (state1 != SlotState::READY ) { return nullptr ; }
129+
130+ const auto id1 = slot.taskId .load (std::memory_order_acquire);
131+ if (id1 != taskId) { return nullptr ; }
132+
133+ auto ptr = AtomicLoadCtx (slot, std::memory_order_acquire);
134+ if (!ptr) { return nullptr ; }
135+
136+ const auto id2 = slot.taskId .load (std::memory_order_acquire);
137+ const auto state2 = slot.state .load (std::memory_order_acquire);
138+ if (state2 == SlotState::READY && id2 == taskId && ptr->taskId == taskId) { return ptr; }
139+
140+ return nullptr ;
70141 }
71142
72143 std::vector<std::shared_ptr<Context>> GetAll ()
73144 {
74- std::lock_guard<std::mutex> lock (mutex_);
75145 std::vector<std::shared_ptr<Context>> tasks;
76- tasks.reserve (tasks_.size ());
77- for (const auto & item : tasks_) { tasks.emplace_back (item.second ); }
146+ for (const auto & slot : slots_) {
147+ if (slot.state .load (std::memory_order_acquire) != SlotState::READY ) { continue ; }
148+
149+ auto ctx = AtomicLoadCtx (slot, std::memory_order_acquire);
150+ if (!ctx) { continue ; }
151+
152+ const auto taskId = slot.taskId .load (std::memory_order_acquire);
153+ const auto state = slot.state .load (std::memory_order_acquire);
154+ if (state == SlotState::READY && taskId == ctx->taskId ) {
155+ tasks.emplace_back (std::move (ctx));
156+ }
157+ }
78158 return tasks;
79159 }
80160
81161 Status Remove (TaskId taskId)
82162 {
83- std::lock_guard<std::mutex> lock (mutex_);
84- auto erased = tasks_.erase (taskId);
85- if (erased == 0 ) {
163+ if (taskId == kInvalidTaskId ) {
164+ return Status::Error (StatusCode::TASK_NOT_FOUND , taskName_ + " task not found" );
165+ }
166+
167+ const auto slotIndex = static_cast <std::size_t >(ToTaskIdUInt (taskId) & slotMask_);
168+ auto & slot = slots_[slotIndex];
169+
170+ std::uint8_t expected = SlotState::READY ;
171+ if (!slot.state .compare_exchange_strong (expected, SlotState::REMOVING ,
172+ std::memory_order_acq_rel,
173+ std::memory_order_acquire)) {
174+ return Status::Error (StatusCode::TASK_NOT_FOUND , taskName_ + " task not found" );
175+ }
176+
177+ if (slot.taskId .load (std::memory_order_acquire) != taskId) {
178+ slot.state .store (SlotState::READY , std::memory_order_release);
86179 return Status::Error (StatusCode::TASK_NOT_FOUND , taskName_ + " task not found" );
87180 }
181+
182+ AtomicStoreCtx (slot, std::shared_ptr<Context>{}, std::memory_order_release);
183+ slot.taskId .store (kInvalidTaskId , std::memory_order_release);
184+ slot.state .store (SlotState::EMPTY , std::memory_order_release);
185+
186+ FreeListPush (slotIndex);
88187 return Status::OK ();
89188 }
90189
91- protected:
190+ private:
191+ using TaskIdUInt = std::make_unsigned_t <TaskId>;
192+
193+ struct SlotState {
194+ static constexpr std::uint8_t EMPTY = 0 ;
195+ static constexpr std::uint8_t WRITING = 1 ;
196+ static constexpr std::uint8_t READY = 2 ;
197+ static constexpr std::uint8_t REMOVING = 3 ;
198+ };
199+
200+ struct alignas (64 ) Slot {
201+ std::atomic<std::uint8_t > state{SlotState::EMPTY };
202+ std::atomic<TaskIdUInt> generation{0 };
203+ std::atomic<TaskId> taskId{kInvalidTaskId };
204+ std::shared_ptr<Context> ctx;
205+ std::atomic<std::size_t > freeNext{0 };
206+ };
207+
208+ private:
209+ static TaskIdUInt ToTaskIdUInt (TaskId taskId) { return static_cast <TaskIdUInt>(taskId); }
210+
211+ static std::size_t NormalizeSlotCount (std::size_t n)
212+ {
213+ n = std::max<std::size_t >(n, kMinSlotCount );
214+
215+ std::size_t power = 1 ;
216+ while (power < n) {
217+ if (power > (std::numeric_limits<std::size_t >::max () >> 1 )) { return power; }
218+ power <<= 1 ;
219+ }
220+
221+ return power;
222+ }
223+
224+ static std::size_t ComputeSlotIndexBits (std::size_t slotCount)
225+ {
226+ std::size_t bits = 0 ;
227+ for (auto s = slotCount; s > 1 ; s >>= 1 ) { ++bits; }
228+ return bits;
229+ }
230+
231+ static std::uint64_t MakeLowBitsMask (std::size_t bits)
232+ {
233+ if (bits >= std::numeric_limits<std::uint64_t >::digits) {
234+ return std::numeric_limits<std::uint64_t >::max ();
235+ }
236+ return (1ULL << bits) - 1 ;
237+ }
238+
239+ bool CanEncodeGeneration (TaskIdUInt generation) const
240+ {
241+ constexpr std::size_t kTotalBits =
242+ static_cast <std::size_t >(std::numeric_limits<TaskIdUInt>::digits);
243+
244+ if (slotIndexBits_ >= kTotalBits ) { return false ; }
245+
246+ if (generation == 0 ) { return false ; }
247+
248+ const auto generationBits = kTotalBits - slotIndexBits_;
249+ if (generationBits >= kTotalBits ) { return true ; }
250+
251+ const auto maxGeneration = (static_cast <TaskIdUInt>(1 ) << generationBits) - 1 ;
252+ return generation <= maxGeneration;
253+ }
254+
255+ TaskId MakeTaskId (std::size_t slotIndex, TaskIdUInt generation) const
256+ {
257+ const auto raw =
258+ (generation << slotIndexBits_) | static_cast <TaskIdUInt>(slotIndex & slotMask_);
259+ return static_cast <TaskId>(raw);
260+ }
261+
262+ static std::shared_ptr<Context> AtomicLoadCtx (const Slot& slot, std::memory_order order)
263+ {
264+ return std::atomic_load_explicit (&slot.ctx , order);
265+ }
266+
267+ static void AtomicStoreCtx (Slot& slot, std::shared_ptr<Context> ptr, std::memory_order order)
268+ {
269+ std::atomic_store_explicit (&slot.ctx , std::move (ptr), order);
270+ }
271+
272+ std::uint64_t PackFreeListHead (std::uint64_t generation, std::size_t index) const
273+ {
274+ return (generation << freeListShift_) | static_cast <std::uint64_t >(index);
275+ }
276+
277+ std::size_t FreeListPop ()
278+ {
279+ auto oldHead = freeListHead_.load (std::memory_order_acquire);
280+ while (true ) {
281+ const auto index = static_cast <std::size_t >(oldHead & freeListMask_);
282+ if (index == freeListEnd_) { return freeListEnd_; }
283+
284+ const auto nextIndex = slots_[index].freeNext .load (std::memory_order_acquire);
285+ const auto oldGen = oldHead >> freeListShift_;
286+ const auto newHead = PackFreeListHead (oldGen + 1 , nextIndex);
287+ if (freeListHead_.compare_exchange_weak (oldHead, newHead, std::memory_order_acq_rel,
288+ std::memory_order_acquire)) {
289+ return index;
290+ }
291+ }
292+ }
293+
294+ void FreeListPush (std::size_t slotIndex)
295+ {
296+ auto oldHead = freeListHead_.load (std::memory_order_acquire);
297+ while (true ) {
298+ slots_[slotIndex].freeNext .store (static_cast <std::size_t >(oldHead & freeListMask_),
299+ std::memory_order_release);
300+
301+ const auto oldGen = oldHead >> freeListShift_;
302+ const auto newHead = PackFreeListHead (oldGen + 1 , slotIndex);
303+ if (freeListHead_.compare_exchange_weak (oldHead, newHead, std::memory_order_release,
304+ std::memory_order_acquire)) {
305+ return ;
306+ }
307+ }
308+ }
309+
310+ private:
92311 State initialState_;
93312 std::string taskName_;
94- std::atomic<TaskId> nextTaskId_{1 };
95- // TODO: consider using a lock-free structure !
96- std::mutex mutex_;
97- std::unordered_map<TaskId, std::shared_ptr<Context>> tasks_;
313+ std::size_t slotIndexBits_{0 };
314+
315+ std::vector<Slot> slots_;
316+ std::size_t slotMask_{0 };
317+
318+ std::size_t freeListShift_{0 };
319+ std::uint64_t freeListMask_{0 };
320+ std::size_t freeListEnd_{0 };
321+ std::atomic<std::uint64_t > freeListHead_{0 };
98322};
99323
100324} // namespace UC::ASU
0 commit comments