elliotchance/orderedmap

Delete operation is abnormal when iterating elements

Closed this issue · 3 comments

The following code is used to test:

m := orderedmap.NewOrderedMap()
	m.Set(10, "ten")
	m.Set(9, "nine")
	m.Set(8, "eight")
	m.Set(7, "seven")
	m.Set(6, "six")
	m.Set(5, "five")
	m.Set(4, "four")
	m.Set(3, "three")
	m.Set(2, "two")
	m.Set(1, "one")

	for el := m.Front(); el != nil; el = el.Next() {
		if el.Key.(int)%2 == 0 {
			m.Delete(el.Key)
		}
	}

	for el := m.Front(); el != nil; el = el.Next() {
		fmt.Println(el.Key, el.Value)
	}

with the output:

9 nine
8 eight
7 seven
6 six
5 five
4 four
3 three
2 two
1 one

The reason for this is

func (l *list) Remove(e *Element) {
	if e.prev == nil {
		l.root.next = e.next
	} else {
		e.prev.next = e.next
	}
	if e.next == nil {
		l.root.prev = e.prev
	} else {
		e.next.prev = e.prev
	}
	e.next = nil // avoid memory leaks
	e.prev = nil // avoid memory leaks
}

e.next and e.prev are assigned to nil.
I don't think it's necessary. So Under what circumstances will this phenomenon(memory leaks) take place?

You cannot modify a linked list while you're iterating it. The docs say this:

If the map is changing while the iteration is in-flight it may produce unexpected behavior.

To answer your question about memory leaks: it's probably not an issue, but it's better to be safe. I'm guessing at the time I was thinking about the case where more than one node was disconnected (takes more collection cycles to cleanup?) or if there was a case of a ring list (again, I think the Go garbage collector is smart enough to handle this) but it's basically no cost so I played it safe.

You cannot modify a linked list while you're iterating it. The docs say this:

If the map is changing while the iteration is in-flight it may produce unexpected behavior.

To answer your question about memory leaks: it's probably not an issue, but it's better to be safe. I'm guessing at the time I was thinking about the case where more than one node was disconnected (takes more collection cycles to cleanup?) or if there was a case of a ring list (again, I think the Go garbage collector is smart enough to handle this) but it's basically no cost so I played it safe.

Ok, I may delete elements in a for loop by other means to solve my problem. Thanks for your reply.

A linked list can be designed to support modification during iteration, its just that this implementation does not. Delete the elements after iterating:

	var deleteKeys []string
	for el := m.Front(); el != nil; el = el.Next() {
		if el.Key.(int)%2 == 0 {
			deleteKeys = append(deleteKeys, el.Key)
		}
	}

	for _, key := range deleteKeys {
		m.Delete(key)
	}

On a related note, I would recommend you use v2 so you get type safety.