metropolis/pkg: delete unused package combinectx
Change-Id: I7d92c2055f6010263013b6e0bd759d7f7906f2c0
Reviewed-on: https://review.monogon.dev/c/monogon/+/3000
Tested-by: Jenkins CI
Reviewed-by: Serge Bazanski <serge@monogon.tech>
diff --git a/metropolis/pkg/combinectx/BUILD.bazel b/metropolis/pkg/combinectx/BUILD.bazel
deleted file mode 100644
index 4eeca7f..0000000
--- a/metropolis/pkg/combinectx/BUILD.bazel
+++ /dev/null
@@ -1,17 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-go_library(
- name = "combinectx",
- srcs = ["combinectx.go"],
- importpath = "source.monogon.dev/metropolis/pkg/combinectx",
- visibility = ["//visibility:public"],
-)
-
-go_test(
- name = "combinectx_test",
- srcs = [
- "combinectx_test.go",
- "example_test.go",
- ],
- embed = [":combinectx"],
-)
diff --git a/metropolis/pkg/combinectx/combinectx.go b/metropolis/pkg/combinectx/combinectx.go
deleted file mode 100644
index 3ff1d72..0000000
--- a/metropolis/pkg/combinectx/combinectx.go
+++ /dev/null
@@ -1,182 +0,0 @@
-// package combinectx implements context.Contexts that 'combine' two other
-// 'parent' contexts. These can be used to deal with cases where you want to
-// cancel a method call whenever any of two pre-existing contexts expires first.
-//
-// For example, if you want to tie a method call to some incoming request
-// context and an active leader lease, then this library is for you.
-package combinectx
-
-import (
- "context"
- "sync"
- "time"
-)
-
-// Combine 'joins' two existing 'parent' contexts into a single context. This
-// context will be Done() whenever any of the parent context is Done().
-// Combining contexts spawns a goroutine that will be cleaned when any of the
-// parent contexts is Done().
-func Combine(a, b context.Context) context.Context {
- c := &Combined{
- a: a,
- b: b,
- doneC: make(chan struct{}),
- }
- go c.run()
- return c
-}
-
-type Combined struct {
- // a is the first parent context.
- a context.Context
- // b is the second parent context.
- b context.Context
-
- // mu guards done.
- mu sync.Mutex
- // done is an Error if either parent context is Done(), or nil otherwise.
- done *Error
- // doneC is closed when either parent context is Done() and Error is set.
- doneC chan struct{}
-}
-
-// Error wraps errors returned by parent contexts.
-type Error struct {
- // underlyingA points to an error returned by the first parent context if the
- // combined context was Done() as a result of the first parent context being
- // Done().
- underlyingA *error
- // underlyingB points to an error returned by the second parent context if the
- // combined context was Done() as a result of the second parent context being
- // Done().
- underlyingB *error
-}
-
-func (e *Error) Error() string {
- if e.underlyingA != nil {
- return (*e.underlyingA).Error()
- }
- if e.underlyingB != nil {
- return (*e.underlyingB).Error()
- }
- return ""
-}
-
-// First returns true if the Combined context's first parent was Done().
-func (e *Error) First() bool {
- return e.underlyingA != nil
-}
-
-// Second returns true if the Combined context's second parent was Done().
-func (e *Error) Second() bool {
- return e.underlyingB != nil
-}
-
-// Unwrap returns the underlying error of either parent context that is Done().
-func (e *Error) Unwrap() error {
- if e.underlyingA != nil {
- return *e.underlyingA
- }
- if e.underlyingB != nil {
- return *e.underlyingB
- }
- return nil
-}
-
-// Is allows errors.Is to be true against any *Error.
-func (e *Error) Is(target error) bool {
- if _, ok := target.(*Error); ok {
- return true
- }
- return false
-}
-
-// As allows errors.As to be true against any *Error.
-func (e *Error) As(target interface{}) bool {
- if v, ok := target.(**Error); ok {
- *v = e
- return true
- }
- return false
-}
-
-// run is the main logic that ties together the two parent contexts. It exits
-// when either parent context is canceled.
-func (c *Combined) run() {
- mark := func(first bool, err error) {
- c.mu.Lock()
- defer c.mu.Unlock()
- c.done = &Error{}
- if first {
- c.done.underlyingA = &err
- } else {
- c.done.underlyingB = &err
- }
- close(c.doneC)
- }
- select {
- case <-c.a.Done():
- mark(true, c.a.Err())
- case <-c.b.Done():
- mark(false, c.b.Err())
- }
-}
-
-// Deadline returns the earlier Deadline from the two parent contexts, if any.
-func (c *Combined) Deadline() (deadline time.Time, ok bool) {
- d1, ok1 := c.a.Deadline()
- d2, ok2 := c.b.Deadline()
-
- if ok1 && !ok2 {
- return d1, true
- }
- if ok2 && !ok1 {
- return d2, true
- }
- if !ok1 && !ok2 {
- return time.Time{}, false
- }
-
- if d1.Before(d2) {
- return d1, true
- }
- return d2, true
-}
-
-func (c *Combined) Done() <-chan struct{} {
- return c.doneC
-}
-
-// Err returns nil if neither parent context is Done() yet, or an error otherwise.
-// The returned errors will have the following properties:
-// 1) errors.Is(err, Error{}) will always return true.
-// 2) errors.Is(err, ctx.Err()) will return true if the combined context was
-// canceled with the same error as ctx.Err().
-// However, this does NOT mean that the combined context was Done() because
-// of the ctx being Done() - to ensure this is the case, use errors.As() to
-// retrieve an Error and its First()/Second() methods.
-// 3) errors.Is(err, context.{Canceled,DeadlineExceeded}) will return true if
-// the combined context is Canceled or DeadlineExceeded.
-// 4) errors.Is will return false otherwise.
-// 5) errors.As(err, &&Error{})) will always return true. The Error object can
-// then be used to check the cause of the combined context's error.
-func (c *Combined) Err() error {
- c.mu.Lock()
- defer c.mu.Unlock()
- if c.done == nil {
- return nil
- }
- return c.done
-}
-
-// Value returns the value located under the given key by checking the first and
-// second parent context in order.
-func (c *Combined) Value(key interface{}) interface{} {
- if v := c.a.Value(key); v != nil {
- return v
- }
- if v := c.b.Value(key); v != nil {
- return v
- }
- return nil
-}
diff --git a/metropolis/pkg/combinectx/combinectx_test.go b/metropolis/pkg/combinectx/combinectx_test.go
deleted file mode 100644
index 04efb2c..0000000
--- a/metropolis/pkg/combinectx/combinectx_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package combinectx
-
-import (
- "context"
- "errors"
- "testing"
- "time"
-)
-
-func TestCancel(t *testing.T) {
- a, aC := context.WithCancel(context.Background())
- b, bC := context.WithCancel(context.Background())
-
- c := Combine(a, b)
- if want, got := error(nil), c.Err(); want != got {
- t.Fatalf("Newly combined context should return %v, got %v", want, got)
- }
- if _, ok := c.Deadline(); ok {
- t.Errorf("Newly combined context should have no deadline")
- }
-
- // Cancel A.
- aC()
- // Cancels are not synchronous - wait for it to propagate...
- <-c.Done()
- // ...then cancel B (no-op).
- bC()
-
- if c.Err() == nil {
- t.Fatalf("After cancel, ctx.Err() should be non-nil")
- }
- if !errors.Is(c.Err(), a.Err()) {
- t.Errorf("After cancel, ctx.Err() should be a.Err()")
- }
- if !errors.Is(c.Err(), c.Err()) {
- t.Errorf("After cancel, ctx.Err() should be ctx.Err()")
- }
- if !errors.Is(c.Err(), context.Canceled) {
- t.Errorf("After cancel, ctx.Err() should be context.Canceled")
- }
- if !errors.Is(c.Err(), &Error{}) {
- t.Errorf("After cancel, ctx.Err() should be a Error pointer")
- }
- cerr := &Error{}
- if !errors.As(c.Err(), &cerr) {
- t.Fatalf("After cancel, ctx.Err() should be usable as *Error")
- }
- if !cerr.First() {
- t.Errorf("ctx.Err().First() should be true")
- }
- if cerr.Second() {
- t.Errorf("ctx.Err().Second() should be false")
- }
- if want, got := a.Err(), cerr.Unwrap(); want != got {
- t.Errorf("ctx.Err().Unwrap() should be %v, got %v", want, got)
- }
-}
-
-func TestDeadline(t *testing.T) {
- now := time.Now()
- aD := now.Add(100 * time.Millisecond)
- bD := now.Add(10 * time.Millisecond)
-
- a, aC := context.WithDeadline(context.Background(), aD)
- b, bC := context.WithDeadline(context.Background(), bD)
-
- defer aC()
- defer bC()
-
- c := Combine(a, b)
- if want, got := error(nil), c.Err(); want != got {
- t.Fatalf("Newly combined context should return %v, got %v", want, got)
- }
- if d, ok := c.Deadline(); !ok || !d.Equal(bD) {
- t.Errorf("Newly combined context should have deadline %v, got %v", bD, d)
- }
-
- <-c.Done()
-
- if c.Err() == nil {
- t.Fatalf("After deadline, ctx.Err() should be non-nil")
- }
- if !errors.Is(c.Err(), b.Err()) {
- t.Errorf("After deadline, ctx.Err() should be b.Err()")
- }
- if !errors.Is(c.Err(), context.DeadlineExceeded) {
- t.Errorf("After cancel, ctx.Err() should be context.DeadlineExceeded")
- }
- if !errors.Is(c.Err(), &Error{}) {
- t.Errorf("After cancel, ctx.Err() should be a Error pointer")
- }
- cerr := &Error{}
- if !errors.As(c.Err(), &cerr) {
- t.Fatalf("After cancel, ctx.Err() should be usable as *Error")
- }
- if cerr.First() {
- t.Errorf("ctx.Err().First() should be false")
- }
- if !cerr.Second() {
- t.Errorf("ctx.Err().Second() should be true")
- }
- if want, got := b.Err(), cerr.Unwrap(); want != got {
- t.Errorf("ctx.Err().Unwrap() should be %v, got %v", want, got)
- }
-}
diff --git a/metropolis/pkg/combinectx/example_test.go b/metropolis/pkg/combinectx/example_test.go
deleted file mode 100644
index f0cd70f..0000000
--- a/metropolis/pkg/combinectx/example_test.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package combinectx_test
-
-import (
- "context"
- "errors"
- "fmt"
- "time"
-
- "source.monogon.dev/metropolis/pkg/combinectx"
-)
-
-// ExampleCombine shows how to combine two contexts for use with a contextful
-// method.
-func ExampleCombine() {
- // Let's say you're trying to combine two different contexts: the first one is
- // some long-term local worker context, while the second is a context from some
- // incoming request.
- ctxA, cancelA := context.WithCancel(context.Background())
- ctxB, cancelB := context.WithTimeout(context.Background(), time.Millisecond*100)
- defer cancelA()
- defer cancelB()
-
- // doIO is some contextful, black box IO-heavy function. You want it to return
- // early when either the long-term context or the short-term request context
- // are Done().
- doIO := func(ctx context.Context) (string, error) {
- t := time.NewTicker(time.Second)
- defer t.Stop()
-
- select {
- case <-ctx.Done():
- return "", ctx.Err()
- case <-t.C:
- return "successfully reticulated splines", nil
- }
- }
-
- // Combine the two given contexts into one...
- ctx := combinectx.Combine(ctxA, ctxB)
- // ... and call doIO with it.
- v, err := doIO(ctx)
- if err == nil {
- fmt.Printf("doIO success: %v\n", v)
- return
- }
-
- fmt.Printf("doIO failed: %v\n", err)
-
- // The returned error will always be equal to the combined context's Err() call
- // if the error is due to the combined context being Done().
- if err == ctx.Err() {
- fmt.Printf("doIO err == ctx.Err()\n")
- }
-
- // The returned error will pass any errors.Is(err, context....) checks. This
- // ensures compatibility with blackbox code that performs special actions on
- // the given context.
- if errors.Is(err, context.DeadlineExceeded) {
- fmt.Printf("doIO err is context.DeadlineExceeded\n")
- }
-
- // The returned error can be inspected by converting it to a *Error and calling
- // .First()/.Second()/.Unwrap(). This lets the caller figure out which of the
- // parent contexts caused the combined context to expires.
- cerr := &combinectx.Error{}
- if errors.As(err, &cerr) {
- fmt.Printf("doIO err is *combinectx.Error\n")
- fmt.Printf("doIO first failed: %v, second failed: %v\n", cerr.First(), cerr.Second())
- }
-
- // Output:
- // doIO failed: context deadline exceeded
- // doIO err == ctx.Err()
- // doIO err is context.DeadlineExceeded
- // doIO err is *combinectx.Error
- // doIO first failed: false, second failed: true
-}