ronantakizawa/codereview-bench huggingface dataset:
Repository: syncthing/syncthing
File: lib/versioner/simple.go (Go)

Code before:
	}

	// Versions are sorted by timestamp in the file name, oldest first.
	versions := findAllVersions(v.versionsFs, filePath)
	if len(versions) > v.keep {
		for _, toRemove := range versions[:len(versions)-v.keep] {
			l.Debugln("cleaning out", toRemove)
			err = v.versionsFs.Remove(toRemove)
			if err != nil {
				l.Warnln("removing old version:", err)
			}
		}
	}

	return nil
}

func (v simple) GetVersions() (map[string][]FileVersion, error) {
	return retrieveVersions(v.versionsFs)
}

func (v simple) Restore(filepath string, versionTime time.Time) error {
	return restoreFile(v.copyRangeMethod, v.versionsFs, v.folderFs, filepath, versionTime, TagFilename)
}

func (v simple) Clean(ctx context.Context) error {
	if v.cleanoutDays <= 0 {
		return nil
	}

	if _, err := v.versionsFs.Lstat("."); fs.IsNotExist(err) {
		return nil
	}

	cutoff := time.Now().Add(time.Duration(-24*v.cleanoutDays) * time.Hour)
	dirTracker := make(emptyDirTracker)

	walkFn := func(path string, info fs.FileInfo, err error) error {
		if err != nil {
			return err
		}

		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
		}

		if info.IsDir() && !info.IsSymlink() {
			dirTracker.addDir(path)
			return nil

Review comment (refactor):
Is there a lot of common code between this function and the one in trashcan versioner?

(Sorry, on the phone hard to check myself).

If yes, perhaps extracting common logic into a shared utility function would be beneficial.

Code after:
	}

	// Versions are sorted by timestamp in the file name, oldest first.
	versions := findAllVersions(v.versionsFs, filePath)
	if len(versions) > v.keep {
		for _, toRemove := range versions[:len(versions)-v.keep] {
			l.Debugln("cleaning out", toRemove)
			err = v.versionsFs.Remove(toRemove)
			if err != nil {
				l.Warnln("removing old version:", err)
			}
		}
	}

	return nil
}

func (v simple) GetVersions() (map[string][]FileVersion, error) {
	return retrieveVersions(v.versionsFs)
}

func (v simple) Restore(filepath string, versionTime time.Time) error {
	return restoreFile(v.copyRangeMethod, v.versionsFs, v.folderFs, filepath, versionTime, TagFilename)
}

func (v simple) Clean(ctx context.Context) error {
	return cleanByDay(ctx, v.versionsFs, v.cleanoutDays)
}
Repository: syncthing/syncthing
File: lib/versioner/versioner_test.go (Go)

Code before:
// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package versioner

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/syncthing/syncthing/lib/config"
	"github.com/syncthing/syncthing/lib/fs"
)

func TestVersionerCleanOut(t *testing.T) {
	simpleCfg := config.FolderConfiguration{
		FilesystemType: fs.FilesystemTypeBasic,
		Path:           "testdata",
		Versioning: config.VersioningConfiguration{

Review comment (security):
```suggestion
// Copyright (C) 2020 The Syncthing Authors.
```

Code after:
// Copyright (C) 2020 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package versioner

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/syncthing/syncthing/lib/config"
	"github.com/syncthing/syncthing/lib/fs"
)

func TestVersionerCleanOut(t *testing.T) {
	cfg := config.FolderConfiguration{
		FilesystemType: fs.FilesystemTypeBasic,
		Path:           "testdata",
		Versioning: config.VersioningConfiguration{
Repository: syncthing/syncthing
File: lib/osutil/atomic.go (Go)

Code before:
		return err
	}

	if err := w.next.Close(); err != nil {
		w.err = err
		return err
	}

	err := w.fs.Rename(w.next.Name(), w.path)
	if runtime.GOOS == "windows" && fs.IsPermission(err) {
		// On Windows, we might not be allowed to rename over the file
		// because it's read-only. Get us some write permissions and try
		// again.
		_ = w.fs.Chmod(w.path, 0644)
		err = w.fs.Rename(w.next.Name(), w.path)
	}
	if err != nil {
		w.err = err
		return err
	}

	// fsync the directory too
	if fd, err := w.fs.Open(filepath.Dir(w.next.Name())); err == nil {
		fd.Sync()
		fd.Close()
	}

	// Set w.err to return appropriately for any future operations.
	w.err = ErrClosed

	return nil
}


Review comment (suggestion):
Info might theoretically be non-nil yet garbage if the file didn't exist. (As in I think info is undefined if err != nil)

Code after:
		return err
	}

	if err := w.next.Close(); err != nil {
		w.err = err
		return err
	}

	info, err := w.fs.Lstat(w.path)
	if err != nil && !fs.IsNotExist(err) {
		w.err = err
		return err
	}
	err = w.fs.Rename(w.next.Name(), w.path)
	if runtime.GOOS == "windows" && fs.IsPermission(err) {
		// On Windows, we might not be allowed to rename over the file
		// because it's read-only. Get us some write permissions and try
		// again.
		_ = w.fs.Chmod(w.path, 0644)
		err = w.fs.Rename(w.next.Name(), w.path)
	}
	if err != nil {
		w.err = err
		return err
	}
	if info != nil {
		if err := w.fs.Chmod(w.path, info.Mode()); err != nil {
			w.err = err
			return err
		}
	}

	// fsync the directory too
	if fd, err := w.fs.Open(filepath.Dir(w.next.Name())); err == nil {
		fd.Sync()
		fd.Close()
	}

	// Set w.err to return appropriately for any future operations.
	w.err = ErrClosed

	return nil
}
Repository: syncthing/syncthing
File: lib/db/backend/backend.go (Go)

Code before:
	}

	if err := maybeCopyDatabase(path, strings.Replace(path, locations.LevelDBDir, locations.BadgerDir, 1), OpenLevelDBAuto, OpenBadger); err != nil {
		return nil, err
	}
	return OpenLevelDB(path, tuning)
}

func OpenMemory() Backend {
	if os.Getenv("USE_BADGER") != "" {
		return OpenBadgerMemory()
	}
	return OpenLevelDBMemory()
}

type errClosed struct{}

func (*errClosed) Error() string { return "database is closed" }

type errNotFound struct{}

func (*errNotFound) Error() string { return "key not found" }

func IsClosed(err error) bool {
	e := &errClosed{}
	return errors.As(err, &e)
}

func IsNotFound(err error) bool {
	e := &errNotFound{}
	return errors.As(err, &e)
}

// releaser manages counting on top of a waitgroup
type releaser struct {
	wg   *closeWaitGroup
	once *sync.Once
}

func newReleaser(wg *closeWaitGroup) (*releaser, error) {
	if err := wg.Add(1); err != nil {
		return nil, err
	}
	return &releaser{
		wg:   wg,
		once: new(sync.Once),
	}, nil
}

func (r releaser) Release() {
	// We use the Once because we may get called multiple times from

Review comment (suggestion):
I think `var e *errClosed` feels more descriptive than the struct literal thing, here it feels like i need to count the ampersands while stating the type explicitly is less thinking

Code after:
	}

	if err := maybeCopyDatabase(path, strings.Replace(path, locations.LevelDBDir, locations.BadgerDir, 1), OpenLevelDBAuto, OpenBadger); err != nil {
		return nil, err
	}
	return OpenLevelDB(path, tuning)
}

func OpenMemory() Backend {
	if os.Getenv("USE_BADGER") != "" {
		return OpenBadgerMemory()
	}
	return OpenLevelDBMemory()
}

type errClosed struct{}

func (*errClosed) Error() string { return "database is closed" }

type errNotFound struct{}

func (*errNotFound) Error() string { return "key not found" }

func IsClosed(err error) bool {
	var e *errClosed
	return errors.As(err, &e)
}

func IsNotFound(err error) bool {
	var e *errNotFound
	return errors.As(err, &e)
}

// releaser manages counting on top of a waitgroup
type releaser struct {
	wg   *closeWaitGroup
	once *sync.Once
}

func newReleaser(wg *closeWaitGroup) (*releaser, error) {
	if err := wg.Add(1); err != nil {
		return nil, err
	}
	return &releaser{
		wg:   wg,
		once: new(sync.Once),
	}, nil
}

func (r releaser) Release() {
	// We use the Once because we may get called multiple times from
Repository: syncthing/syncthing
File: lib/ur/failurereporting.go (Go)

Code before:
// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package ur

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"time"

	"github.com/syncthing/syncthing/lib/build"
	"github.com/syncthing/syncthing/lib/config"
	"github.com/syncthing/syncthing/lib/dialer"
	"github.com/syncthing/syncthing/lib/events"
	"github.com/syncthing/syncthing/lib/util"

	"github.com/thejerf/suture"
)

var (
	// When a specific failure first occurs, it is delayed by minDelay. If

Review comment (security):
```suggestion
// Copyright (C) 2020 The Syncthing Authors.
```

Code after:
// Copyright (C) 2020 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package ur

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"time"

	"github.com/syncthing/syncthing/lib/build"
	"github.com/syncthing/syncthing/lib/config"
	"github.com/syncthing/syncthing/lib/dialer"
	"github.com/syncthing/syncthing/lib/events"
	"github.com/syncthing/syncthing/lib/util"

	"github.com/thejerf/suture"
)

var (
	// When a specific failure first occurs, it is delayed by minDelay. If
Repository: syncthing/syncthing
File: lib/db/lowlevel.go (Go)

Code before:
			if err = t.Put(key, nil); err != nil {
				return false
			}
			l.Debugln("check local need: adding", f.Name)
		}
		return true
	})
	if err != nil {
		return 0, err
	}

	for !needDone {
		repaired++
		if err := t.Delete(dbi.Key()); err != nil {
			return 0, err
		}
		l.Debugln("check local need: removing", needName)
		next()
	}

	if err = t.Commit(); err != nil {
		return 0, err
	}

	return repaired, nil
}

// unchanged checks if two files are the same and thus don't need to be updated.
// Local flags or the invalid bit might change without the version
// being bumped.
func unchanged(nf, ef protocol.FileIntf) bool {
	return ef.FileVersion().Equal(nf.FileVersion()) && ef.IsInvalid() == nf.IsInvalid() && ef.FileLocalFlags() == nf.FileLocalFlags()
}


Review comment (suggestion):
Needs explicit check of iterator error and closing it, currently panics with `panic: Unclosed iterator at time of Txn.Discard` with Badger

Code after:
			if err = t.Put(key, nil); err != nil {
				return false
			}
			l.Debugln("check local need: adding", f.Name)
		}
		return true
	})
	if err != nil {
		return 0, err
	}

	for !needDone {
		repaired++
		if err := t.Delete(dbi.Key()); err != nil {
			return 0, err
		}
		l.Debugln("check local need: removing", needName)
		next()
	}

	if err := dbi.Error(); err != nil {
		return 0, err
	}
	dbi.Release()

	if err = t.Commit(); err != nil {
		return 0, err
	}

	return repaired, nil
}

// unchanged checks if two files are the same and thus don't need to be updated.
// Local flags or the invalid bit might change without the version
// being bumped.
func unchanged(nf, ef protocol.FileIntf) bool {
	return ef.FileVersion().Equal(nf.FileVersion()) && ef.IsInvalid() == nf.IsInvalid() && ef.FileLocalFlags() == nf.FileLocalFlags()
}
Repository: syncthing/syncthing
File: lib/fs/casefs.go (Go)

Code before:
			n, err := findCaseInsensitiveMatch(comp, node.dirNames, node.dirNamesLower)
			if err != nil {
				return "", err
			}
			node.child = &caseNode{name: n}
		}

		node = node.child
		out = filepath.Join(out, node.name)
	}

	return out, nil
}

func (r *defaultRealCaser) dropCache() {
	r.mut.Lock()
	r.root = &caseNode{name: "."}
	r.mut.Unlock()
}

// Both name and the key to children are "Real", case resolved names of the path
// component this node represents (i.e. containing no path separator).
// The key to results is also a path component, but as given to RealCase, not
// case resolved.
type caseNode struct {
	name          string
	expires       time.Time
	dirNames      []string
	dirNamesLower []string
	child         *caseNode
}

func findCaseInsensitiveMatch(name string, names, namesLower []string) (string, error) {
	lower := UnicodeLowercase(name)
	candidate := ""
	for i, n := range names {
		if n == name {
			return n, nil
		}
		if candidate == "" && namesLower[i] == lower {
			candidate = n
		}
	}
	if candidate == "" {
		return "", ErrNotExist
	}
	return candidate, nil
}


Review comment (performance):
Note this also means we will not cache bad lookups (case differs from the one in the filesystem), but we don't need to optimize for those because they are a rare case and will result in an error higher up in the call chain.

Code after:

	r.mut.Lock()
	defer r.mut.Unlock()

	node := r.root
	for _, comp := range strings.Split(name, string(PathSeparator)) {
		if node.dirNames == nil || node.expires.Before(time.Now()) {
			// Haven't called DirNames yet, or the node has expired

			var err error
			node.dirNames, err = r.fs.DirNames(out)
			if err != nil {
				return "", err
			}

			node.dirNamesLower = make([]string, len(node.dirNames))
			for i, n := range node.dirNames {
				node.dirNamesLower[i] = UnicodeLowercase(n)
			}

			node.expires = time.Now().Add(caseCacheTimeout)
			node.child = nil
		}

		// If we don't already have a correct cached child, try to find it.
		if node.child == nil || node.child.name != comp {
			// Actually loop dirNames to search for a match.
			n, err := findCaseInsensitiveMatch(comp, node.dirNames, node.dirNamesLower)
			if err != nil {
				return "", err
			}
			node.child = &caseNode{name: n}
		}

		node = node.child
		out = filepath.Join(out, node.name)
	}

	return out, nil
}

func (r *defaultRealCaser) dropCache() {
	r.mut.Lock()
	r.root = &caseNode{name: "."}
	r.mut.Unlock()
}

// Both name and the key to children are "Real", case resolved names of the path
// component this node represents (i.e. containing no path separator).
// The key to results is also a path component, but as given to RealCase, not
// case resolved.
Repository: syncthing/syncthing
File: lib/fs/folding.go (Go)

Code before:
// Copyright (C) 2017 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package fs

import (
	"unicode"
)

func UnicodeLowercase(s string) string {
	rs := []rune(s)
	for i, r := range rs {
		rs[i] = unicode.ToLower(unicode.ToUpper(r))
	}
	return string(rs)
}


Review comment (suggestion):
I don't think this works in the general case. `firstCaseChange` returns the index of the first rune to have its case changed, while `s[:i]` is counted in bytes. This should break for strings with lowercase non-ASCII characters in the beginning, which may not be any of our current test cases

Code after:
// Copyright (C) 2017 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package fs

import (
	"strings"
	"unicode"
	"unicode/utf8"
)

func UnicodeLowercase(s string) string {
	i := firstCaseChange(s)
	if i == -1 {
		return s
	}

	var rs strings.Builder
	// WriteRune always reserves utf8.UTFMax bytes for non-ASCII runes,
	// even if it doesn't need all that space. Overallocate now to prevent
	// it from ever triggering a reallocation.
	rs.Grow(utf8.UTFMax - 1 + len(s))
	rs.WriteString(s[:i])

	for _, r := range s[i:] {
		rs.WriteRune(unicode.ToLower(unicode.ToUpper(r)))
	}
	return rs.String()
}

// Byte index of the first rune r s.t. lower(upper(r)) != r.
func firstCaseChange(s string) int {
	for i, r := range s {
		if r <= unicode.MaxASCII && (r < 'A' || r > 'Z') {
			continue
		}
		if unicode.ToLower(unicode.ToUpper(r)) != r {
			return i
		}
	}
	return -1
}
Repository: syncthing/syncthing
File: lib/fs/basicfs_copy_range_ioctl.go (Go)

Code before:
		if err != nil {
			return err
		}
		if innerErr != nil {
			return innerErr
		}
		return nil
	}

	var innerErr error
	_, err = withFileDescriptors(src, dst, func(srcFd, dstFd uintptr) (int, error) {
		params := unix.FileCloneRange{
			Src_fd:      int64(srcFd),
			Src_offset:  uint64(srcOffset),
			Src_length:  uint64(size),
			Dest_offset: uint64(dstOffset),
		}
		innerErr = unix.IoctlFileCloneRange(int(dstFd), &params)
		return 0, nil
	})
	// Failure in withFileDescriptors
	if err != nil {
		return err
	}
	if innerErr != nil {
		return innerErr
	}
	return nil
}


Review comment (suggestion):
I think the innerErr magic can go now.

The reason is was there is because we used syscall.Errno which is always not nil, even if the errno is 0. 
We lose the sycall.Errno type as we return from withFileDescriptors, so we did the external variable magic to capture that.

The unix library now handles it for us, so we can just `return 0, errorFromUnixPackage` and then just return whatever was returned from withFileDescriptors straight back out.

Code after:
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

// +build linux

package fs

import (
	"io"

	"golang.org/x/sys/unix"
)

func init() {
	registerCopyRangeImplementation(CopyRangeMethodIoctl, copyRangeImplementationForBasicFile(copyRangeIoctl))
}

func copyRangeIoctl(src, dst basicFile, srcOffset, dstOffset, size int64) error {
	fi, err := src.Stat()
	if err != nil {
		return err
	}

	if srcOffset+size > fi.Size() {
		return io.ErrUnexpectedEOF
	}

	// https://www.man7.org/linux/man-pages/man2/ioctl_ficlonerange.2.html
	// If src_length is zero, the ioctl reflinks to the end of the source file.
	if srcOffset+size == fi.Size() {
		size = 0
	}

	if srcOffset == 0 && dstOffset == 0 && size == 0 {
		// Optimization for whole file copies.
		_, err := withFileDescriptors(src, dst, func(srcFd, dstFd uintptr) (int, error) {
			return 0, unix.IoctlFileClone(int(dstFd), int(srcFd))
		})
		return err
	}

	_, err = withFileDescriptors(src, dst, func(srcFd, dstFd uintptr) (int, error) {
		params := unix.FileCloneRange{
			Src_fd:      int64(srcFd),
			Src_offset:  uint64(srcOffset),
			Src_length:  uint64(size),
			Dest_offset: uint64(dstOffset),
		}
		return 0, unix.IoctlFileCloneRange(int(dstFd), &params)
	})
	return err
}
Repository: syncthing/syncthing
File: lib/config/folderconfiguration.go (Go)

Code before:
func (f FolderConfiguration) RequiresRestartOnly() FolderConfiguration {
	copy := f

	// Manual handling for things that are not taken care of by the tag
	// copier, yet should not cause a restart.

	blank := FolderConfiguration{}
	util.CopyMatchingTag(&blank, &copy, "restart", func(v string) bool {
		if len(v) > 0 && v != "false" {
			panic(fmt.Sprintf(`unexpected tag value: %s. expected untagged or "false"`, v))
		}
		return v == "false"
	})
	return copy
}

func (f *FolderConfiguration) SharedWith(device protocol.DeviceID) bool {
	for _, dev := range f.Devices {
		if dev.DeviceID == device {
			return true
		}
	}
	return false
}

func (f *FolderConfiguration) CheckAvailableSpace(req uint64) error {
	val := f.MinDiskFree.BaseValue()
	if val <= 0 {
		return nil
	}
	fs := f.Filesystem()
	usage, err := fs.Usage(".")
	if err != nil {
		return nil
	}
	usage.Free -= req
	if usage.Free > 0 {
		if err := CheckFreeSpace(f.MinDiskFree, usage); err == nil {
			return nil
		}
	}
	return fmt.Errorf("insufficient space in %v %v", fs.Type(), fs.URI())
}


Review comment (suggestion):
This breaks the check on L248-249, which is why we avoid unsigned integers to begin with. Change might still be desired, but requires careful auditing everywhere the values are used in any kind of arithmetic

Code after:
func (f FolderConfiguration) RequiresRestartOnly() FolderConfiguration {
	copy := f

	// Manual handling for things that are not taken care of by the tag
	// copier, yet should not cause a restart.

	blank := FolderConfiguration{}
	util.CopyMatchingTag(&blank, &copy, "restart", func(v string) bool {
		if len(v) > 0 && v != "false" {
			panic(fmt.Sprintf(`unexpected tag value: %s. expected untagged or "false"`, v))
		}
		return v == "false"
	})
	return copy
}

func (f *FolderConfiguration) SharedWith(device protocol.DeviceID) bool {
	for _, dev := range f.Devices {
		if dev.DeviceID == device {
			return true
		}
	}
	return false
}

func (f *FolderConfiguration) CheckAvailableSpace(req uint64) error {
	val := f.MinDiskFree.BaseValue()
	if val <= 0 {
		return nil
	}
	fs := f.Filesystem()
	usage, err := fs.Usage(".")
	if err != nil {
		return nil
	}
	if !checkAvailableSpace(req, f.MinDiskFree, usage) {
		return fmt.Errorf("insufficient space in %v %v", fs.Type(), fs.URI())
	}
	return nil
}
Repository: syncthing/syncthing
File: lib/ur/failurereporting.go (Go)

Code before:

	var url string
	var err error
	var sub events.Subscription
	var evChan <-chan events.Event
	timer := time.NewTimer(minDelay)
	resetTimer := make(chan struct{})
outer:
	for {
		select {
		case opts := <-h.optsChan:
			// Sub nil checks just for safety - config updates can be racy.
			if opts.URAccepted > 0 {
				if sub == nil {
					sub = h.evLogger.Subscribe(events.Failure)
					evChan = sub.C()
				}
			} else if sub != nil {
				sub.Unsubscribe()
				sub = nil
				evChan = nil
			}
			url = opts.CRURL + "/failure"
		case e, ok := <-evChan:
			if !ok {
				// Just to be save - shouldn't ever happen, as
				// evChan is set to nil when unsubscribing.
				h.addReport(evChanClosed, time.Now())
				evChan = nil
				continue
			}
			descr, ok := e.Data.(string)
			if !ok {
				// Same here, shouldn't ever happen.
				h.addReport(invalidEventDataType, time.Now())
				continue
			}
			h.addReport(descr, e.Time)
		case <-timer.C:
			reports := make([]FailureReport, 0, len(h.buf))
			now := time.Now()
			for descr, stat := range h.buf {
				if now.Sub(stat.last) > minDelay || now.Sub(stat.first) > maxDelay {
					reports = append(reports, FailureReport{
						Description: descr,
						Count:       stat.count,
						Version:     build.LongVersion,
					})
					delete(h.buf, descr)
				}
			}

Review comment (suggestion):
```suggestion
				// Just to be safe - shouldn't ever happen, as
```

Code after:

	var url string
	var err error
	var sub events.Subscription
	var evChan <-chan events.Event
	timer := time.NewTimer(minDelay)
	resetTimer := make(chan struct{})
outer:
	for {
		select {
		case opts := <-h.optsChan:
			// Sub nil checks just for safety - config updates can be racy.
			if opts.URAccepted > 0 {
				if sub == nil {
					sub = h.evLogger.Subscribe(events.Failure)
					evChan = sub.C()
				}
			} else if sub != nil {
				sub.Unsubscribe()
				sub = nil
				evChan = nil
			}
			url = opts.CRURL + "/failure"
		case e, ok := <-evChan:
			if !ok {
				// Just to be safe - shouldn't ever happen, as
				// evChan is set to nil when unsubscribing.
				h.addReport(evChanClosed, time.Now())
				evChan = nil
				continue
			}
			descr, ok := e.Data.(string)
			if !ok {
				// Same here, shouldn't ever happen.
				h.addReport(invalidEventDataType, time.Now())
				continue
			}
			h.addReport(descr, e.Time)
		case <-timer.C:
			reports := make([]FailureReport, 0, len(h.buf))
			now := time.Now()
			for descr, stat := range h.buf {
				if now.Sub(stat.last) > minDelay || now.Sub(stat.first) > maxDelay {
					reports = append(reports, FailureReport{
						Description: descr,
						Count:       stat.count,
						Version:     build.LongVersion,
					})
					delete(h.buf, descr)
				}
			}