Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save leehambley/4b605e5cfc7c0bbaa7fdfeac90fab3fb to your computer and use it in GitHub Desktop.
Save leehambley/4b605e5cfc7c0bbaa7fdfeac90fab3fb to your computer and use it in GitHub Desktop.

Restartable iterator

I needed this for a kind of index that allows me to subscribe to a tag in a git repo, but also get notified on a rebase or a head pointer move.

I was getting bogged down in the details (Git is a single-linked list starting at the head, so walking over it from the first commits to the newest commits is tricky)

This iterator seems to fulfil my needs around tracking the position within the list, and being able to reset.

The meta API (pointer, implements error interface, has other methods for checking if rewound or EOF) seems to be as neat as one could hope to expect.

package main

import "testing"
import "fmt"

type meta struct {
	err error
	isRewound bool
	isEOF bool
}

func (m *meta) Error() string {
	return fmt.Sprintf("result: %e", m.err)
}

func (m *meta) IsRewound() bool {
	return m.isRewound
}

func (m *meta) IsEOF() bool {
	return m.isEOF
}

type index struct {
	data  <-chan int
	reset <-chan struct{}
	cancel <-chan struct{}
}

func (i *index) Next() (int, *meta) {
	for {
	select {
		case val := <-i.data:
			// fmt.Printf("Read value from %p\n", i.data)
			return val, nil
		case <-i.reset:
			// fmt.Printf("Read reset from %p", i.reset)
			return 128, &meta{nil, true, false}
		case <-i.cancel:
			i.data = nil
			i.reset = nil
			i.cancel = nil
			// fmt.Printf("Read cancel from canceller %p\n", i.cancel)
			return 0, &meta{nil, false, true}
		}
	}
}

func TestHelloWorld(t *testing.T) {

	var (
		next   = make(chan int)
		rewind = make(chan struct{})
		cancel = make(chan struct{})
	)

	go func() {
		next <- 1
		next <- 2
		rewind <- struct{}{}
		next <- 1
		next <- 2
		cancel <- struct{}{}
		next <- 0  // after cancel index is not receiving, this kills
		           // this goroutine
	}()

	var index = index{next, rewind, cancel}

	if val, meta := index.Next(); val != 1 {
		t.Fatalf("expected first value to be 1, got %q (meta: %#v)", val, meta)
	}
	if val, _ := index.Next(); val != 2 {
		t.Fatalf("expected second value to be 2, got %q", val)
	}
	if _, meta := index.Next(); !meta.IsRewound() {
		t.Fatalf("expected metadata to cary rewind signal")
	}
	if val, _ := index.Next(); val != 1 {
		t.Fatalf("expected third value to be 1, got %q", val)
	}
	if val, _ := index.Next(); val != 2 {
		t.Fatalf("expected fourth value to be 2, got %q", val)
	}
	if _, meta := index.Next(); !meta.IsEOF() {
		fmt.Printf("expected iterator to be cancelled after reading four values")
	}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment