Skip to content

Commit c363658

Browse files
authored
Merge pull request #80887 from DougGregor/conformance-cache-extended-entry-deleak-6.2
2 parents a948f79 + cead04b commit c363658

File tree

2 files changed

+61
-14
lines changed

2 files changed

+61
-14
lines changed

include/swift/Runtime/Concurrent.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,12 @@ struct ConcurrentReadableHashMap {
750750

751751
/// Clear the hash table, freeing (when safe) all memory currently used for
752752
/// indices and elements.
753-
void clear() {
753+
///
754+
/// - addFreedNodes: if nonempty, this function will be called with the head
755+
/// of the free list. The function may add additional nodes to the free
756+
/// list, which will be freed when it is safe to do so.
757+
void clear(std::function<void(ConcurrentFreeListNode *&)> addFreedNodes
758+
= nullptr) {
754759
typename MutexTy::ScopedLock guard(WriterLock);
755760

756761
IndexStorage indices = Indices.load(std::memory_order_relaxed);
@@ -766,6 +771,9 @@ struct ConcurrentReadableHashMap {
766771
ConcurrentFreeListNode::add(&FreeList, ptr);
767772
ConcurrentFreeListNode::add(&FreeList, elements);
768773

774+
if (addFreedNodes)
775+
addFreedNodes(FreeList);
776+
769777
deallocateFreeListIfSafe();
770778
}
771779
};

stdlib/public/runtime/ProtocolConformance.cpp

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ namespace {
536536
};
537537

538538
struct ConformanceCacheEntry {
539-
private:
539+
public:
540540
/// Storage used when we have global actor isolation on the conformance.
541541
struct ExtendedStorage {
542542
/// The protocol to which the type conforms.
@@ -549,28 +549,48 @@ namespace {
549549
/// When the conformance is global-actor-isolated, this is the conformance
550550
/// of globalActorIsolationType to GlobalActor.
551551
const WitnessTable *globalActorIsolationWitnessTable = nullptr;
552+
553+
/// The next pointer in the list of extended storage allocations.
554+
ExtendedStorage *next = nullptr;
552555
};
553556

554557
const Metadata *Type;
555-
llvm::PointerUnion<const ProtocolDescriptor *, const ExtendedStorage *>
558+
llvm::PointerUnion<const ProtocolDescriptor *, ExtendedStorage *>
556559
ProtoOrStorage;
557560

558561
/// The witness table.
559562
const WitnessTable *Witness;
560563

561564
public:
562565
ConformanceCacheEntry(ConformanceCacheKey key,
563-
ConformanceLookupResult result)
566+
ConformanceLookupResult result,
567+
std::atomic<ExtendedStorage *> &storageHead)
564568
: Type(key.Type), Witness(result.witnessTable)
565569
{
566-
if (result.globalActorIsolationType) {
567-
ProtoOrStorage = new ExtendedStorage{
568-
key.Proto, result.globalActorIsolationType,
569-
result.globalActorIsolationWitnessTable
570-
};
571-
} else {
570+
if (!result.globalActorIsolationType) {
572571
ProtoOrStorage = key.Proto;
572+
return;
573573
}
574+
575+
// Allocate extended storage.
576+
void *memory = malloc(sizeof(ExtendedStorage));
577+
auto storage = new (memory) ExtendedStorage{
578+
key.Proto, result.globalActorIsolationType,
579+
result.globalActorIsolationWitnessTable
580+
};
581+
582+
ProtoOrStorage = storage;
583+
584+
// Add the storage pointer to the list of extended storage allocations
585+
// so that we can free them later.
586+
auto head = storageHead.load(std::memory_order_relaxed);
587+
while (true) {
588+
storage->next = head;
589+
if (storageHead.compare_exchange_weak(
590+
head, storage, std::memory_order_release,
591+
std::memory_order_relaxed))
592+
break;
593+
};
574594
}
575595

576596
bool matchesKey(const ConformanceCacheKey &key) const {
@@ -586,7 +606,7 @@ namespace {
586606
if (auto proto = ProtoOrStorage.dyn_cast<const ProtocolDescriptor *>())
587607
return proto;
588608

589-
if (auto storage = ProtoOrStorage.dyn_cast<const ExtendedStorage *>())
609+
if (auto storage = ProtoOrStorage.dyn_cast<ExtendedStorage *>())
590610
return storage->Proto;
591611

592612
return nullptr;
@@ -606,7 +626,7 @@ namespace {
606626
if (ProtoOrStorage.is<const ProtocolDescriptor *>())
607627
return ConformanceLookupResult { Witness, nullptr, nullptr };
608628

609-
if (auto storage = ProtoOrStorage.dyn_cast<const ExtendedStorage *>()) {
629+
if (auto storage = ProtoOrStorage.dyn_cast<ExtendedStorage *>()) {
610630
return ConformanceLookupResult(
611631
Witness, storage->globalActorIsolationType,
612632
storage->globalActorIsolationWitnessTable);
@@ -621,6 +641,11 @@ namespace {
621641
struct ConformanceState {
622642
ConcurrentReadableHashMap<ConformanceCacheEntry> Cache;
623643
ConcurrentReadableArray<ConformanceSection> SectionsToScan;
644+
645+
/// The head of an intrusive linked list that keeps track of all of the
646+
/// conformance cache entries that require extended storage.
647+
std::atomic<ConformanceCacheEntry::ExtendedStorage *> ExtendedStorageHead{nullptr};
648+
624649
bool scanSectionsBackwards;
625650

626651
#if USE_DYLD_SHARED_CACHE_CONFORMANCE_TABLES
@@ -709,7 +734,8 @@ struct ConformanceState {
709734
return false; // abandon the new entry
710735

711736
::new (entry) ConformanceCacheEntry(
712-
ConformanceCacheKey(type, proto), result);
737+
ConformanceCacheKey(type, proto), result,
738+
ExtendedStorageHead);
713739
return true; // keep the new entry
714740
});
715741
}
@@ -743,7 +769,20 @@ static void _registerProtocolConformances(ConformanceState &C,
743769

744770
// Blow away the conformances cache to get rid of any negative entries that
745771
// may now be obsolete.
746-
C.Cache.clear();
772+
C.Cache.clear([&](ConcurrentFreeListNode *&freeListHead) {
773+
// The extended storage for conformance entries will need to be freed
774+
// eventually. Put it on the concurrent free list so the cache will do so.
775+
auto storageHead = C.ExtendedStorageHead.load(std::memory_order_relaxed);
776+
while (storageHead) {
777+
auto current = storageHead;
778+
auto newHead = current->next;
779+
if (C.ExtendedStorageHead.compare_exchange_weak(
780+
storageHead, newHead, std::memory_order_release,
781+
std::memory_order_relaxed)) {
782+
ConcurrentFreeListNode::add(&freeListHead, current);
783+
}
784+
}
785+
});
747786
}
748787

749788
void swift::addImageProtocolConformanceBlockCallbackUnsafe(

0 commit comments

Comments
 (0)