Skip to content

Commit 7dd54e1

Browse files
committed
runtime: make work.spanSPMCs.all doubly-linked
Making this a doubly-linked list allows spanQueue.destroy to immediately remove and free rings rather than simply marking them as dead and waiting for the sweeper to deal with them. For golang#75771. Change-Id: I6a6a636c0fb6be08ee967cb6d8f0577511a33c13 Reviewed-on: https://go-review.googlesource.com/c/go/+/709657 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
1 parent 3ee7617 commit 7dd54e1

File tree

1 file changed

+42
-19
lines changed

1 file changed

+42
-19
lines changed

src/runtime/mgcmark_greenteagc.go

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -635,14 +635,22 @@ func (q *spanQueue) destroy() {
635635

636636
lock(&work.spanSPMCs.lock)
637637

638-
// Mark each ring as dead. The sweeper will actually free them.
639-
//
640-
// N.B., we could free directly here, but work.spanSPMCs.all is a
641-
// singly-linked list, so we'd need to walk the entire list to find the
642-
// previous node. If the list becomes doubly-linked, we can free
643-
// directly.
638+
// Remove and free each ring.
644639
for r := (*spanSPMC)(q.chain.tail.Load()); r != nil; r = (*spanSPMC)(r.prev.Load()) {
645-
r.dead.Store(true)
640+
prev := r.allprev
641+
next := r.allnext
642+
if prev != nil {
643+
prev.allnext = next
644+
}
645+
if next != nil {
646+
next.allprev = prev
647+
}
648+
if work.spanSPMCs.all == r {
649+
work.spanSPMCs.all = next
650+
}
651+
652+
r.deinit()
653+
mheap_.spanSPMCAlloc.free(unsafe.Pointer(r))
646654
}
647655

648656
q.chain.head = nil
@@ -685,6 +693,11 @@ type spanSPMC struct {
685693
// work.spanSPMCs.lock.
686694
allnext *spanSPMC
687695

696+
// allprev is the link to the previous spanSPMC on the work.spanSPMCs
697+
// list. This is used to find and free dead spanSPMCs. Protected by
698+
// work.spanSPMCs.lock.
699+
allprev *spanSPMC
700+
688701
// dead indicates whether the spanSPMC is no longer in use.
689702
// Protected by the CAS to the prev field of the spanSPMC pointing
690703
// to this spanSPMC. That is, whoever wins that CAS takes ownership
@@ -711,7 +724,11 @@ type spanSPMC struct {
711724
func newSpanSPMC(cap uint32) *spanSPMC {
712725
lock(&work.spanSPMCs.lock)
713726
r := (*spanSPMC)(mheap_.spanSPMCAlloc.alloc())
714-
r.allnext = work.spanSPMCs.all
727+
next := work.spanSPMCs.all
728+
r.allnext = next
729+
if next != nil {
730+
next.allprev = r
731+
}
715732
work.spanSPMCs.all = r
716733
unlock(&work.spanSPMCs.lock)
717734

@@ -748,6 +765,8 @@ func (r *spanSPMC) deinit() {
748765
r.head.Store(0)
749766
r.tail.Store(0)
750767
r.cap = 0
768+
r.allnext = nil
769+
r.allprev = nil
751770
}
752771

753772
// slot returns a pointer to slot i%r.cap.
@@ -780,22 +799,26 @@ func freeDeadSpanSPMCs() {
780799
unlock(&work.spanSPMCs.lock)
781800
return
782801
}
783-
rp := &work.spanSPMCs.all
784-
for {
785-
r := *rp
786-
if r == nil {
787-
break
788-
}
802+
r := work.spanSPMCs.all
803+
for r != nil {
804+
next := r.allnext
789805
if r.dead.Load() {
790806
// It's dead. Deinitialize and free it.
791-
*rp = r.allnext
807+
prev := r.allprev
808+
if prev != nil {
809+
prev.allnext = next
810+
}
811+
if next != nil {
812+
next.allprev = prev
813+
}
814+
if work.spanSPMCs.all == r {
815+
work.spanSPMCs.all = next
816+
}
817+
792818
r.deinit()
793819
mheap_.spanSPMCAlloc.free(unsafe.Pointer(r))
794-
} else {
795-
// Still alive, likely in some P's chain.
796-
// Skip it.
797-
rp = &r.allnext
798820
}
821+
r = next
799822
}
800823
unlock(&work.spanSPMCs.lock)
801824
}

0 commit comments

Comments
 (0)