Commit b54df94a authored by Alessio Treglia's avatar Alessio Treglia

Imported Upstream version 0~20161010~0git3baa390

parents
This diff is collapsed.
package clist
/*
The purpose of CList is to provide a goroutine-safe linked-list.
This list can be traversed concurrently by any number of goroutines.
However, removed CElements cannot be added back.
NOTE: Not all methods of container/list are (yet) implemented.
NOTE: Removed elements need to DetachPrev or DetachNext consistently
to ensure garbage collection of removed elements.
*/
import (
"sync"
"sync/atomic"
"unsafe"
)
// CElement is an element of a linked-list
// Traversal from a CElement are goroutine-safe.
type CElement struct {
prev unsafe.Pointer
prevWg *sync.WaitGroup
next unsafe.Pointer
nextWg *sync.WaitGroup
removed uint32
Value interface{}
}
// Blocking implementation of Next().
// May return nil iff CElement was tail and got removed.
func (e *CElement) NextWait() *CElement {
for {
e.nextWg.Wait()
next := e.Next()
if next == nil {
if e.Removed() {
return nil
} else {
continue
}
} else {
return next
}
}
}
// Blocking implementation of Prev().
// May return nil iff CElement was head and got removed.
func (e *CElement) PrevWait() *CElement {
for {
e.prevWg.Wait()
prev := e.Prev()
if prev == nil {
if e.Removed() {
return nil
} else {
continue
}
} else {
return prev
}
}
}
// Nonblocking, may return nil if at the end.
func (e *CElement) Next() *CElement {
return (*CElement)(atomic.LoadPointer(&e.next))
}
// Nonblocking, may return nil if at the end.
func (e *CElement) Prev() *CElement {
return (*CElement)(atomic.LoadPointer(&e.prev))
}
func (e *CElement) Removed() bool {
return atomic.LoadUint32(&(e.removed)) > 0
}
func (e *CElement) DetachNext() {
if !e.Removed() {
panic("DetachNext() must be called after Remove(e)")
}
atomic.StorePointer(&e.next, nil)
}
func (e *CElement) DetachPrev() {
if !e.Removed() {
panic("DetachPrev() must be called after Remove(e)")
}
atomic.StorePointer(&e.prev, nil)
}
func (e *CElement) setNextAtomic(next *CElement) {
for {
oldNext := atomic.LoadPointer(&e.next)
if !atomic.CompareAndSwapPointer(&(e.next), oldNext, unsafe.Pointer(next)) {
continue
}
if next == nil && oldNext != nil { // We for-loop in NextWait() so race is ok
e.nextWg.Add(1)
}
if next != nil && oldNext == nil {
e.nextWg.Done()
}
return
}
}
func (e *CElement) setPrevAtomic(prev *CElement) {
for {
oldPrev := atomic.LoadPointer(&e.prev)
if !atomic.CompareAndSwapPointer(&(e.prev), oldPrev, unsafe.Pointer(prev)) {
continue
}
if prev == nil && oldPrev != nil { // We for-loop in PrevWait() so race is ok
e.prevWg.Add(1)
}
if prev != nil && oldPrev == nil {
e.prevWg.Done()
}
return
}
}
func (e *CElement) setRemovedAtomic() {
atomic.StoreUint32(&(e.removed), 1)
}
//--------------------------------------------------------------------------------
// CList represents a linked list.
// The zero value for CList is an empty list ready to use.
// Operations are goroutine-safe.
type CList struct {
mtx sync.Mutex
wg *sync.WaitGroup
head *CElement // first element
tail *CElement // last element
len int // list length
}
func (l *CList) Init() *CList {
l.mtx.Lock()
defer l.mtx.Unlock()
l.wg = waitGroup1()
l.head = nil
l.tail = nil
l.len = 0
return l
}
func New() *CList { return new(CList).Init() }
func (l *CList) Len() int {
l.mtx.Lock()
defer l.mtx.Unlock()
return l.len
}
func (l *CList) Front() *CElement {
l.mtx.Lock()
defer l.mtx.Unlock()
return l.head
}
func (l *CList) FrontWait() *CElement {
for {
l.mtx.Lock()
head := l.head
wg := l.wg
l.mtx.Unlock()
if head == nil {
wg.Wait()
} else {
return head
}
}
}
func (l *CList) Back() *CElement {
l.mtx.Lock()
defer l.mtx.Unlock()
return l.tail
}
func (l *CList) BackWait() *CElement {
for {
l.mtx.Lock()
tail := l.tail
wg := l.wg
l.mtx.Unlock()
if tail == nil {
wg.Wait()
} else {
return tail
}
}
}
func (l *CList) PushBack(v interface{}) *CElement {
l.mtx.Lock()
defer l.mtx.Unlock()
// Construct a new element
e := &CElement{
prev: nil,
prevWg: waitGroup1(),
next: nil,
nextWg: waitGroup1(),
Value: v,
}
// Release waiters on FrontWait/BackWait maybe
if l.len == 0 {
l.wg.Done()
}
l.len += 1
// Modify the tail
if l.tail == nil {
l.head = e
l.tail = e
} else {
l.tail.setNextAtomic(e)
e.setPrevAtomic(l.tail)
l.tail = e
}
return e
}
// CONTRACT: Caller must call e.DetachPrev() and/or e.DetachNext() to avoid memory leaks.
// NOTE: As per the contract of CList, removed elements cannot be added back.
func (l *CList) Remove(e *CElement) interface{} {
l.mtx.Lock()
defer l.mtx.Unlock()
prev := e.Prev()
next := e.Next()
if l.head == nil || l.tail == nil {
panic("Remove(e) on empty CList")
}
if prev == nil && l.head != e {
panic("Remove(e) with false head")
}
if next == nil && l.tail != e {
panic("Remove(e) with false tail")
}
// If we're removing the only item, make CList FrontWait/BackWait wait.
if l.len == 1 {
l.wg.Add(1)
}
l.len -= 1
// Connect next/prev and set head/tail
if prev == nil {
l.head = next
} else {
prev.setNextAtomic(next)
}
if next == nil {
l.tail = prev
} else {
next.setPrevAtomic(prev)
}
// Set .Done() on e, otherwise waiters will wait forever.
e.setRemovedAtomic()
if prev == nil {
e.prevWg.Done()
}
if next == nil {
e.nextWg.Done()
}
return e.Value
}
func waitGroup1() (wg *sync.WaitGroup) {
wg = &sync.WaitGroup{}
wg.Add(1)
return
}
package clist
import (
"fmt"
"math/rand"
"runtime"
"sync/atomic"
"testing"
"time"
)
func TestSmall(t *testing.T) {
l := New()
el1 := l.PushBack(1)
el2 := l.PushBack(2)
el3 := l.PushBack(3)
if l.Len() != 3 {
t.Error("Expected len 3, got ", l.Len())
}
//fmt.Printf("%p %v\n", el1, el1)
//fmt.Printf("%p %v\n", el2, el2)
//fmt.Printf("%p %v\n", el3, el3)
r1 := l.Remove(el1)
//fmt.Printf("%p %v\n", el1, el1)
//fmt.Printf("%p %v\n", el2, el2)
//fmt.Printf("%p %v\n", el3, el3)
r2 := l.Remove(el2)
//fmt.Printf("%p %v\n", el1, el1)
//fmt.Printf("%p %v\n", el2, el2)
//fmt.Printf("%p %v\n", el3, el3)
r3 := l.Remove(el3)
if r1 != 1 {
t.Error("Expected 1, got ", r1)
}
if r2 != 2 {
t.Error("Expected 2, got ", r2)
}
if r3 != 3 {
t.Error("Expected 3, got ", r3)
}
if l.Len() != 0 {
t.Error("Expected len 0, got ", l.Len())
}
}
/*
This test is quite hacky because it relies on SetFinalizer
which isn't guaranteed to run at all.
*/
func _TestGCFifo(t *testing.T) {
const numElements = 1000000
l := New()
gcCount := new(uint64)
// SetFinalizer doesn't work well with circular structures,
// so we construct a trivial non-circular structure to
// track.
type value struct {
Int int
}
done := make(chan struct{})
for i := 0; i < numElements; i++ {
v := new(value)
v.Int = i
l.PushBack(v)
runtime.SetFinalizer(v, func(v *value) {
atomic.AddUint64(gcCount, 1)
})
}
for el := l.Front(); el != nil; {
l.Remove(el)
//oldEl := el
el = el.Next()
//oldEl.DetachPrev()
//oldEl.DetachNext()
}
runtime.GC()
time.Sleep(time.Second * 3)
runtime.GC()
time.Sleep(time.Second * 3)
_ = done
if *gcCount != numElements {
t.Errorf("Expected gcCount to be %v, got %v", numElements,
*gcCount)
}
}
/*
This test is quite hacky because it relies on SetFinalizer
which isn't guaranteed to run at all.
*/
func _TestGCRandom(t *testing.T) {
const numElements = 1000000
l := New()
gcCount := 0
// SetFinalizer doesn't work well with circular structures,
// so we construct a trivial non-circular structure to
// track.
type value struct {
Int int
}
for i := 0; i < numElements; i++ {
v := new(value)
v.Int = i
l.PushBack(v)
runtime.SetFinalizer(v, func(v *value) {
gcCount += 1
})
}
els := make([]*CElement, 0, numElements)
for el := l.Front(); el != nil; el = el.Next() {
els = append(els, el)
}
for _, i := range rand.Perm(numElements) {
el := els[i]
l.Remove(el)
el = el.Next()
}
runtime.GC()
time.Sleep(time.Second * 3)
if gcCount != numElements {
t.Errorf("Expected gcCount to be %v, got %v", numElements,
gcCount)
}
}
func TestScanRightDeleteRandom(t *testing.T) {
const numElements = 10000
const numTimes = 100000
const numScanners = 10
l := New()
stop := make(chan struct{})
els := make([]*CElement, numElements, numElements)
for i := 0; i < numElements; i++ {
el := l.PushBack(i)
els[i] = el
}
// Launch scanner routines that will rapidly iterate over elements.
for i := 0; i < numScanners; i++ {
go func(scannerID int) {
var el *CElement
restartCounter := 0
counter := 0
FOR_LOOP:
for {
select {
case <-stop:
fmt.Println("stopped")
break FOR_LOOP
default:
}
if el == nil {
el = l.FrontWait()
restartCounter += 1
}
el = el.Next()
counter += 1
}
fmt.Printf("Scanner %v restartCounter: %v counter: %v\n", scannerID, restartCounter, counter)
}(i)
}
// Remove an element, push back an element.
for i := 0; i < numTimes; i++ {
// Pick an element to remove
rmElIdx := rand.Intn(len(els))
rmEl := els[rmElIdx]
// Remove it
l.Remove(rmEl)
//fmt.Print(".")
// Insert a new element
newEl := l.PushBack(-1*i - 1)
els[rmElIdx] = newEl
if i%100000 == 0 {
fmt.Printf("Pushed %vK elements so far...\n", i/1000)
}
}
// Stop scanners
close(stop)
time.Sleep(time.Second * 1)
// And remove all the elements.
for el := l.Front(); el != nil; el = el.Next() {
l.Remove(el)
}
if l.Len() != 0 {
t.Fatal("Failed to remove all elements from CList")
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment