· 7 years ago · Oct 30, 2018, 09:30 PM
1#pragma once
2
3#include <swLib/util/ChildOf.hpp>
4#include <swLib/util/noneOf.hpp>
5#include <swLib/crypto/crypto.h>
6#include <swLib/string/printf.hpp>
7
8namespace sw::memory {
9
10///<summary>
11/// HandleTable - A table of slots, each containing a type, addressible by
12/// a Handle, which is a combination of two numbers, a sequence (indicating the
13/// instance id of the allocated handle object), and an index (indicating where
14/// in this table's static array of slots the slot exists in). All handles tables
15/// are singletones, they should only be accessed through the singleton instance
16/// api and are effectively locked to static segment of the process's memory.
17/// A handle table encourages cache coherency by setting the next free slot
18/// hint to the last freed slot. Additionally it uses no heap memory, further
19/// improving its performance situation relative to shared ptrs. The Ptr and WPtr
20/// types operate exactly as shared ptrs so you can replace a shared ptr model
21/// with a handle one if the situation warrants it (lots of allocations, all of the
22/// same type etc.).
23///</summary>
24template<typename Type, int32_t MaxSlots = 1000>
25class HandleTable : public util::Singleton<HandleTable<Type, MaxSlots>>
26{
27public:
28 using Parent = util::Singleton<HandleTable<Type, MaxSlots>>;
29 using Parent::Parent;
30
31 // Define some basic error enums
32 enum class ERR
33 {
34 InvalidHandle = 1,
35 InvalidSequence,
36 InvalidState,
37 OutOfSlots,
38 };
39
40 ~HandleTable() noexcept
41 {
42 deinit();
43 }
44
45 void deinit() noexcept
46 {
47 init_ = false;
48 for (auto i = 0; i < MaxSlots; i++)
49 slots_[i].release();
50 }
51
52 explicit operator bool () const noexcept { return init_; }
53
54 [[nodiscard]] Str __logPrefix() const noexcept
55 {
56 return toString(log::typeName(*this), "-", totalAllocated_);
57 }
58
59 // No copy, no move
60 HandleTable(const HandleTable &table) noexcept = delete;
61 HandleTable & operator = (const HandleTable &table) noexcept = delete;
62
63 HandleTable(HandleTable &&table) noexcept = delete;
64 HandleTable & operator = (HandleTable &&table) noexcept = delete;
65
66 // Allow the handle ptr class, and the slot class to call the internal apis
67 friend class Ptr;
68 friend struct Slot;
69
70 //
71 // Handle - Effectively a number, containing two fields a sequence, and
72 // an index, both referencing a slot in the table. Type safe to prevent
73 // ambiguity.
74 //
75 struct Handle
76 {
77 int32_t sequence_ = 0;
78 int32_t index_ = 0;
79
80 //
81 // valid - Simply put a handle is valid if its index is within max slots.
82 //
83 auto valid() const noexcept
84 {
85 return index_ < MaxSlots && index_ >= 0;
86 }
87
88 void reset() noexcept
89 {
90 sequence_ = 0;
91 index_ = -1;
92 }
93
94 explicit operator bool() const noexcept
95 {
96 return valid();
97 }
98
99 Str __toString() const noexcept
100 {
101 Str result;
102 if (!valid())
103 result = "{Invalid}-";
104
105 return result += toString(string::toHex(sequence_, 4), "-", string::toHex(index_, 4));
106 }
107 };
108
109private:
110 //
111 // STATE - The slot exists in exactly one of the following states, each state
112 // describes the assumptions which can be made on the slot, making the slot
113 // into a mini state machine of sorts.
114 //
115 enum class STATE
116 {
117 Init, // Handle is in its initialized default state
118 Reserved, // The slot has been reserved for allocation
119 Allocated, // The object has been allocated, no gets/puts allowed
120 Ready, // Open for buisiness, get/puts are allowed, object is allocated
121 NotReady, // Allocated, no gets allowed, puts allowed
122 };
123
124 //
125 // Slot - A slot is an entry in the statically constructed array within the
126 // HandleTable. It contains atomically modified fields to synchronize access
127 // and broker construction and deconstruction of the held object templated type.
128 //
129 struct Slot
130 {
131 // The current state of this slot
132 STATE state_ = STATE::Init;
133
134// std::shared_ptr<dev::Backtrace> trace_;
135 Location location_ = {};
136
137 // The number of check outs which have been made for this
138 // slot, refCount_ must drop to zero before the held object
139 // may be destroyed
140 std::atomic<int32_t> refCount_;
141
142 // The index this slot resides in, set lazily as we ready them
143 int32_t index_ = -1;
144
145 // Thread id which did the allocation (Debugging)
146 async::Id allocatorId_ = {};
147
148 // A very primitive lock whih we spin hard on, there is no real
149 // contention here requiring a context switch
150 mutable std::atomic_flag lock_;
151
152 // The currently bound sequence, bound from the randomly initiated
153 // then sequentially incremented sequence field in the HandleTable
154 // this prevents us from allowing handles from other tables to be valid
155 // (At least not without chance on our side), due to the type of the
156 // handle table itself, it implicitly prevent cross types from
157 // being incorrectly casted (hence no group field in the c++ version of handles).
158 // Note: debug has the random stuff turned off so you can trace problems with reproduceable
159 // indexes from time to time to find patterns.
160 int32_t sequence_ = 0;
161
162 // Here is where the object lives, its constructed when this slot is allocated
163 // and reset when released.
164 std::optional<Type> object_;
165
166 // Just default constructable
167 Slot() noexcept = default;
168
169 // Not copyable, or moveable
170 Slot(const Slot &) = delete;
171 Slot & operator = (const Slot &) = delete;
172
173 Slot(Slot &&) = delete;
174 Slot & operator = (Slot &&) = delete;
175
176 //
177 // allocate - Allocates a new object and sets the slot state to
178 // allocate. This may throw since we are constructing an external type.
179 //
180 template<typename ...Args>
181 void allocate(Location location, Args && ... args) noexcept(false)
182 {
183 location_ = location;
184
185 // Great, lock is held now, change state to allocated
186 state_ = STATE::Allocated;
187 allocatorId_ = async::currentId();
188 //trace_ = std::make_shared<dev::Backtrace>();
189
190 LOGT(HANDLE, "Allocate");
191
192 if (HandleTable::instance())
193 SW_ASSERT(!object_);
194
195 // Safe to construct in place, perfect forwarding the args
196 object_.emplace(std::forward<Args>(args)...);
197 }
198
199 //
200 // setReserved - Sets the slot state to reserved.
201 //
202 void setReserved() noexcept
203 {
204 LOGT(HANDLE, "Reserve");
205
206 if (HandleTable::instance())
207 SW_ASSERT(state_ == STATE::Init);
208
209 state_ = STATE::Reserved;
210
211 HandleTable::instance().totalAllocated_++;
212 }
213
214 //
215 // setReady - Sets the slot state to ready.
216 //
217 void setReady(int32_t sequenceId) noexcept
218 {
219 LOGT(HANDLE, "Ready:", sequenceId);
220
221 if (HandleTable::instance())
222 SW_ASSERT(state_ == STATE::Allocated);
223
224 state_ = STATE::Ready;
225 sequence_ = sequenceId;
226 }
227
228 //
229 // setNotReady - Sets the state to not ready and performs any assertions
230 // needed to verify the sanity of the operation
231 //
232 void setNotReady() noexcept
233 {
234 LOGT(HANDLE, "SetNotReady");
235
236 if (HandleTable::instance()) {
237 SW_ASSERT(state_ == STATE::Ready || state_ == STATE::NotReady);
238 SW_ASSERT(refCount_ != 0);
239 }
240
241 // The handle is locked now, set state to not ready
242 state_ = STATE::NotReady;
243 }
244
245 //
246 // decRef - Decrements the ref count, and does a few assertion checks to
247 // verify sanity of the operation.
248 //
249 void decRef() noexcept
250 {
251 LOGT(HANDLE, "DecRef");
252
253 SW_ASSERT(refCount_ > 0);
254 if (HandleTable::instance())
255 SW_ASSERT(state_ == STATE::Ready || state_ == STATE::NotReady);
256
257 refCount_--;
258 }
259
260 //
261 // addRef - Increments the ref count, and does a few assertion checks to
262 // verify sanity of the operation.
263 //
264 void addRef() noexcept
265 {
266 LOGT(HANDLE, "AddRef");
267
268 SW_ASSERT(refCount_ < std::numeric_limits<int32_t>::max());
269 if (HandleTable::instance()) {
270 SW_ASSERT(state_ == STATE::Ready);
271 }
272
273 refCount_++;
274 }
275
276 //
277 // lock - Races to set the atomic flag to true first, returns a
278 // scope to auto unlock using RAII semantics.
279 //
280 [[nodiscard]] decltype(auto) lock() const noexcept
281 {
282 return util::ScopeImpl([&]{
283 while (lock_.test_and_set(std::memory_order_acquire)) {}
284 }, [&]{ unlock(); });
285 }
286
287 //
288 // tryLock - Without spinning, attempts to atomically set the lock flag.
289 //
290 [[nodiscard]] bool tryLock() const noexcept
291 {
292 return !lock_.test_and_set(std::memory_order_acquire);
293 }
294
295 //
296 // unlock - Used internally when passing slot references back in a locked form for
297 // atomic slot access.
298 //
299 void unlock() const noexcept
300 {
301 lock_.clear();
302 }
303
304 //
305 // release - Releases the slot and destroys the bound object.
306 //
307 void release() noexcept
308 {
309 LOGT(HANDLE, "Release");
310
311 if (HandleTable::instance())
312 SW_ASSERT(state_ == STATE::Ready || state_ == STATE::NotReady);
313
314 refCount_ = 0;
315 sequence_ = 1;
316 object_.reset();
317 state_ = STATE::Init;
318
319 HandleTable::instance().totalAllocated_--;
320 }
321
322 explicit operator bool () const noexcept
323 {
324 return static_cast<bool>(HandleTable::instance());
325 }
326
327 //
328 // __toString - Renders information about the state of this slot.
329 //
330 [[nodiscard]] Str __toString() const noexcept
331 {
332 Str stateStr;
333
334 switch (state_) {
335 case STATE::Allocated:
336 stateStr = "Allocated";
337 break;
338 case STATE::Init:
339 stateStr = "Init";
340 break;
341 case STATE::NotReady:
342 stateStr = "NotReady";
343 break;
344 case STATE::Ready:
345 stateStr = "Ready";
346 break;
347 case STATE::Reserved:
348 stateStr = "Reserved";
349 break;
350 default:
351 dev::fatality(
352 _ts("Invalid handle state: ", static_cast<int>(state_)),
353 SW_LOCATION
354 );
355 break;
356 }
357
358 return string::printf<false>("%1 %2 %3-(%4)-%5[%6]",
359 location_, Handle{sequence_, index_}, stateStr, Count(refCount_.load()),
360 allocatorId_, (object_ ? toString(object_.value()) : "{null}"_s));
361 }
362 };
363
364public:
365
366 // We never copy slots, we only reference them, define that type
367 using SlotRef = std::reference_wrapper<Slot>;
368
369 //
370 // Ptr - The user facing object which contains the checked out
371 // reference to the handle, and provides apis to manage the handles
372 // lifetime.
373 //
374 class Ptr
375 {
376 protected:
377 //
378 // Ptr - Protected constructor which constructs from a SlotRef,
379 // only callable by the HandleTable's allocate method.
380 //
381 Ptr(typename std::optional<SlotRef> &&reference) noexcept :
382 reference_(std::forward<std::optional<SlotRef>>(reference))
383 {
384 }
385
386 public:
387 // Allow the handle table to access our private constructor, which constructs
388 // from the internal Slot
389 friend class HandleTable;
390
391 Ptr() noexcept = default;
392
393 //
394 // Ptr - Copy constructor/copy assignment operator both increment
395 // the ref count and copy the slot reference if the state of the slot is Ready.
396 //
397 Ptr(const Ptr &ptr) noexcept
398 {
399 operator = (ptr);
400 }
401
402 Ptr & operator = (const Ptr &ptr) noexcept
403 {
404 if (this == &ptr)
405 return *this;
406
407 // Put our ref if we're holding one
408 putRef();
409
410 // Get the ref from the peer
411 reference_ = ptr.getRef();
412
413 return *this;
414 }
415
416 //
417 // Ptr - Move construct/move assign, moves the reference from one ptr to
418 // another.
419 //
420 Ptr(Ptr &&ptr) noexcept
421 {
422 operator = (_mv(ptr));
423 }
424
425 Ptr & operator = (Ptr &&ptr) noexcept
426 {
427 // Move the other reference's state over
428 reference_ = _mv(ptr.reference_);
429 ptr.reference_.reset();
430
431 return *this;
432 }
433
434 //
435 // ~Ptr - puts the ref and decrements the refcount implicitly
436 // in the backing slot.
437 //
438 ~Ptr() noexcept
439 {
440 reset();
441 }
442
443 [[nodiscard]] Str __toString() const noexcept
444 {
445 return toString(log::typeName(*this), " ", reference_);
446 }
447
448 //
449 // get - Returns a ptr to the bound type, asserts if null.
450 //
451 const auto * get() const noexcept
452 {
453 SW_ASSERT(reference_);
454 return &reference_.value().get().object_.value();
455 }
456
457 auto * get() noexcept
458 {
459 SW_ASSERT(reference_);
460 return &reference_.value().get().object_.value();
461 }
462
463 //
464 // operator -> - Access the stored reference by ptr, aborts if
465 // not set.
466 //
467 Type * operator -> () noexcept
468 {
469 return get();
470 }
471
472 const Type * operator -> () const noexcept
473 {
474 return get();
475 }
476
477 //
478 // setNotReady - Flags this items resource as invalid, preventing
479 // further clones of the reference.
480 //
481 bool setNotReady() noexcept
482 {
483 if (reference_) {
484 HandleTable::instance().setNotReady(reference_.value());
485 return true;
486 }
487 return false;
488 }
489
490 //
491 // operator bool - Returns true if this is a valid resource. For
492 // checking whether the resource was set not ready, use isReady/isNotReady
493 // apis.
494 //
495 explicit operator bool() const noexcept
496 {
497 return reference_.has_value();
498 }
499
500 //
501 // refCount - Returns the number of references the current handle has.
502 // Negative value returned on error, no implicit checkout occurs here.
503 //
504 int32_t refCount() const noexcept
505 {
506 SW_ASSERT(reference_);
507 return reference_->get().refCount_;
508 }
509
510 //
511 // reset - The standard method that will clear the ownership and handle value
512 // from this object.
513 //
514 void reset() noexcept
515 {
516 putRef();
517 }
518
519 //
520 // isReady - Returns true if this handle has a state of Ready.
521 // A ready state indicates this resource is ready for use and has
522 // not been marked for deletion.
523 //
524 bool isReady() const noexcept
525 {
526 if (reference_)
527 return reference_->get().state_ == STATE::Ready;
528 return false;
529 }
530
531 //
532 // handle - Returns the handle to this ptr, which can then be
533 // sued to create a weak ref.
534 //
535 Handle handle() const noexcept
536 {
537 if (reference_)
538 return {reference_->get().sequence_, reference_->get().index_};
539 return {};
540 }
541
542 Location location() const noexcept
543 {
544 if (reference_)
545 return {reference_->get().location_};
546 return {};
547 }
548
549 private:
550 //
551 // getRef - Attempts atomically increment the reference count and
552 // return a clone of the slot ref. If the state of the reference is
553 // not ready this will return an empty optional result.
554 //
555 std::optional<SlotRef> getRef() const noexcept
556 {
557 if (!reference_)
558 return {};
559
560 auto &slot = reference_->get();
561 auto guard = slot.lock();
562
563 // Check for not ready state
564 if (slot.state_ == STATE::NotReady)
565 return {};
566
567 // Bump the ref and return the value
568 slot.addRef();
569
570 // And give them a copy of ours
571 return reference_;
572 }
573
574 //
575 // putRef - If we have a valid reference, calls the handle table back to
576 // put, may destroy the object if the ref count drops to zero.
577 //
578 void putRef() noexcept
579 {
580 if (!reference_)
581 return;
582
583 HandleTable::instance().put(_mv(reference_.value()));
584 reference_.reset();
585 }
586
587 // If we hold a checked out reference, this member will contain it.
588 std::optional<SlotRef> reference_ = {};
589 };
590
591 //
592 // WPtr - WeakPtr, works just like you'd expect. Instead of keeping
593 // a ref around its basically a Ptr factory if you think about it, with
594 // one trick in that it can be loosly bound to the hande address
595 // that it holds only.
596 //
597 class WPtr
598 {
599 public:
600 WPtr() noexcept = default;
601
602 WPtr(Ptr ptr) noexcept :
603 handle_(ptr.handle())
604 {
605 }
606
607 WPtr(Handle handle) noexcept :
608 handle_(handle)
609 {
610 }
611
612 //
613 // Copy constructor/copy/move/assignment/operator/thingy - just copy the handle
614 // around with no special state change needed as we never hold any state in a weak
615 // ptr besides the handle value.
616 //
617 WPtr(const WPtr &ptr) noexcept = default;
618 WPtr & operator = (const WPtr &ptr) noexcept = default;
619
620 WPtr(WPtr &&ptr) noexcept = default;
621 WPtr & operator = (WPtr &&ptr) noexcept = default;
622
623 [[nodiscard]] auto handle() const noexcept
624 {
625 return handle_;
626 }
627
628 //
629 // lock - Well now we sure do look like std::weak_ptr however
630 // we have cache locality with frequency of access encouraged through
631 // the last free slot.
632 [[nodiscard]] decltype(auto) lock() const noexcept
633 {
634 return HandleTable::instance().checkout(handle_);
635 }
636
637 //
638 // operator bool - Won't check out, but it will validate the state and handle
639 //
640 explicit operator bool () const noexcept
641 {
642 return HandleTable::instance().isValidAndReady(handle_);
643 }
644
645 [[nodiscard]] decltype(auto) __toString() const noexcept
646 {
647 return toString(log::typeName(*this), "-", handle_);
648 }
649
650 private:
651 Handle handle_;
652 };
653
654private:
655 // Define the lookup result, anytime we dive into the table we either spit out
656 // an error enum, or a slot reference.
657 using LookupResult = std::variant<ERR, SlotRef>;
658
659 //
660 // put - Decrements the reference count on the slot.
661 //
662 void put(SlotRef &&ref) noexcept
663 {
664 if (!init_)
665 return;
666
667 auto &slot = ref.get();
668 auto guard = slot.lock();
669
670 SW_ASSERT(slot.refCount_ > 0);
671
672 LOGT(HANDLE, "Put:", slot);
673
674 slot.decRef();
675
676 // A ref count of zero means we need to destroy it
677 if (slot.refCount_ != 0)
678 return;
679
680 LOGT(HANDLE, "Release:", slot);
681
682 slot.release();
683
684 // Unlock first, so an allocator doesn't needlessly try to trylock
685 // us first
686 guard.exec();
687
688#if !defined(SW_BUILD_DEBUG)
689 // Now set the next free
690 nextFreeIndex_ = slot.index_;
691#endif
692 }
693
694 //
695 // setNotReady - Flags the handle as not ready, we retain this logic in the
696 // handle table itself to facilitate future notifications to external parties
697 // interested in the life time of the slot.
698 //
699 void setNotReady(SlotRef &ref) noexcept
700 {
701 if (!init_)
702 return;
703
704 auto &slot = ref.get();
705 auto guard = slot.lock();
706
707 slot.setNotReady();
708
709 // @@ TODO perhaps notify someone, call mom or something...
710 }
711
712 //
713 // lookupAddRef- Looks up a slot atomically by its handle address, and retains the lock
714 // allowing for atomic opreations between the calling function
715 // and this one including optional state requirements and ability to leave the slot locked
716 // on successful return.
717 //
718 LookupResult lookup(Handle handle, bool keepLocked, std::vector<STATE> && rstates = {}) noexcept
719 {
720 // Sanity
721 if (!handle)
722 return {ERR::InvalidHandle};
723
724 // Look it up
725 auto &slot = slots_[handle.index_];
726
727 // Lock its state
728 auto lockGuard = slot.lock();
729
730 // Firstly, sequence id has to match
731 if (slot.sequence_ != handle.sequence_)
732 return {ERR::InvalidSequence};
733
734 // Now lookup required state, if specified
735 if (!rstates.empty()) {
736 if (util::noneOf(rstates,
737 [&](auto &&rstate) { return slot.state_ == rstate; }))
738 return {ERR::InvalidState};
739 }
740
741 // See if they want the lock held
742 if (keepLocked)
743 lockGuard.cancel();
744
745 return SlotRef{slot};
746 }
747
748 //
749 // reserve - Looks up a new slot, using the nextSlot_ index as a hint as to where
750 // to start its iteration in the slot table. Each thread here will 'race' to
751 // lock a slot, to set its state to Reserved.
752 //
753 LookupResult reserve() noexcept
754 {
755 // Use the hint and atomically increment it for the next guy
756 int32_t startIndex;
757
758 // Fix the wrap
759 if (nextFreeIndex_ < 0 || nextFreeIndex_ >= MaxSlots)
760 nextFreeIndex_ = 0;
761
762 // Increment and continue to reserve
763 startIndex = nextFreeIndex_++;
764
765 // Two passes, first will use the hint, second will start at the front
766 for (auto pass = 1; pass <= 2; pass++) {
767 // Second pass
768 if (pass == 2) {
769 // If we didn't already start at 0, we're done
770 if (startIndex == 0)
771 break;
772
773 // Start at zero and find a free slot
774 startIndex = 0;
775 }
776
777 for (auto index = startIndex; index < MaxSlots; index++) {
778 auto &slot = slots_[index];
779
780 // Try to lock the slot, proceed to the next otherwise
781 if (!slot.tryLock())
782 continue;
783
784 if (slot.index_ == -1)
785 slot.index_ = index;
786 else
787 SW_ASSERT(slot.index_ == index);
788
789 auto unlockGuard = util::ScopeImpl{[&]{ slot.unlock(); }};
790
791 // We locked it, see if its available for use
792 if (slot.state_ != STATE::Init)
793 continue;
794
795 // Fantastic, set it reserved
796 slot.setReserved();
797
798 // And return a ref (leave locked)
799 unlockGuard.cancel();
800 return SlotRef{slot};
801 }
802 }
803
804 // Failed to find a free one, return error
805 dev::enterDebugger();
806 return {ERR::OutOfSlots};
807 }
808
809public:
810 //
811 // allocate - Reserves a new slot, and constructs the object in place with no moves
812 // (in fact the objects you can store in here don't even have to support move as we
813 // use references in the slot using the handy std::reference_wrapper feature). It
814 // then marks the slot as ready. The Checkout template argument when true will
815 // cause this to return a Ptr ready to go with a checked out reference to the object.
816 //
817 template<bool Checkout = true, typename ...Args>
818 Ptr allocate(Location location, Args && ... args) noexcept(false)
819 {
820 return std::visit(
821 [this,location,&args...](auto &&result) mutable ->std::optional<SlotRef> {
822 if constexpr(std::is_enum_v<std::decay_t<decltype(result)>>) {
823 SWERR_THROW(RuntimeError, "Handle err:", static_cast<int>(result));
824 } else {
825 auto &slot = result.get();
826
827 // Tell the slot to go to allocated state
828 slot.allocate(location, std::forward<Args>(args)...);
829
830 // Set ready state
831 slot.setReady(++sequence_);
832
833 // Add a reference
834 slot.addRef();
835
836 // Manually unlock as we didn't get a guard this time around
837 slot.unlock();
838
839 // And return a ref into the ptr
840 return std::optional<SlotRef>{_mv(result)};
841 }
842 },
843 reserve()
844 );
845 }
846
847 //
848 // checkout - Kinda like alloc but, we don't construct the type we look it up
849 // by its handle address. On failure returns an empty Ptr.
850 //
851 Ptr checkout(Handle handle) noexcept
852 {
853 return std::visit(
854 [this](auto &&result) mutable -> Ptr {
855 if constexpr(std::is_enum_v<std::decay_t<decltype(result)>>) {
856 return {};
857 } else {
858 auto &slot = result.get();
859
860 // Well we can add a ref now
861 slot.addRef();
862
863 // All done with it
864 slot.unlock();
865
866 // And return a ref into the ptr
867 return std::optional<SlotRef>{_mv(result)};
868 }
869 },
870 lookup(handle, true, {{STATE::Ready}}) // Lookup (validate), keep locked, and require Ready state.
871 );
872 }
873
874 //
875 // isValidAndReady - Checks if a handle address is valid, and marked Ready. On failure
876 // returns false.
877 //
878 bool isValidAndReady(Handle handle) noexcept
879 {
880 return std::visit(
881 [this](auto &&result) mutable {
882
883 // Easy
884 if constexpr(std::is_enum_v<std::decay_t<decltype(result)>>) {
885 return false;
886 } else {
887 return true;
888 }
889 },
890 lookup(handle, false, {{STATE::Ready}}) // Lookup (validate), don't keep locked, and require Ready state.
891 );
892 }
893
894 //
895 // size - Returns the current allocation size from the atomic counter.
896 //
897 [[nodiscard]] int32_t size() const noexcept
898 {
899 return totalAllocated_;
900 }
901
902 //
903 // allocated - Returns a vector of slots which are currently allocated,
904 // used during leak detection and debugging.
905 //
906 [[nodiscard]] std::vector<int32_t> allocated() const noexcept
907 {
908 std::vector<int32_t> result;
909
910 for (auto index = 0; index < MaxSlots; index++) {
911 auto &slot = slots_[index];
912
913 auto guard = slot.lock();
914 if (slot.state_ != STATE::Init)
915 result.push_back(index);
916 }
917
918 return result;
919 }
920
921 //
922 // allocatedStrings - Returns the selected slots.
923 //
924 [[nodiscard]] std::vector<SlotRef> allocatedSlots(std::optional<std::vector<int32_t>> slotList = {}) noexcept
925 {
926 std::vector<SlotRef> result;
927
928 if (slotList) {
929 for (auto &index : slotList.value())
930 result.emplace_back(slots_[index]);
931 } else {
932 for (auto index = 0; index < MaxSlots; index++)
933 result.emplace_back(slots_[index]);
934 }
935
936 return result;
937 }
938
939private:
940 // The next sequence, initialized to a random positive value, not including
941 // the value 1
942 std::atomic<int32_t> sequence_ = dev::debugBuild() ? 1 : crypto::randomNumber<int32_t>(1);
943
944 // A hint where we may find the next free slot
945 std::atomic<int32_t> nextFreeIndex_ = dev::debugBuild() ? 0 : crypto::randomNumber<int32_t>(0, MaxSlots);
946
947 // The static array containing all possible slots
948 std::array<Slot, MaxSlots> slots_ = {};
949
950 // Basic init flat as we are statically allocated, the handle system shuts
951 // off and turns into a null op after this flag is cleared on a call to deinit
952 std::atomic_bool init_ = {true};
953
954 // The total number of consumed handles
955 std::atomic<int32_t> totalAllocated_ = {0};
956};
957
958} // namespace sw::memory