donegroup

package module
v1.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 21, 2024 License: MIT Imports: 5 Imported by: 2

README

donegroup Go Reference CI Coverage Code to Test Ratio Test Execution Time

donegroup is a package that provides a graceful cleanup transaction to context.Context when the context is canceled ( done ).

errgroup.Group + <-ctx.Done() = donegroup

Usage

Use donegroup.WithCancel instead of context.WithCancel.

Then, it can wait for the cleanup processes associated with the context using donegroup.Wait.

// before
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// after
ctx, cancel := donegroup.WithCancel(context.Background())
defer func() {
	cancel()
	if err := donegroup.Wait(ctx); err != nil {
		log.Fatal(err)
	}
}()
Basic usage ( donegroup.Cleanup )
gantt
    dateFormat  mm
    axisFormat  

    section main
        donegroup.WithCancel :milestone, m1, 00, 0m
        cancel context using cancel() : milestone, m2, 03, 0m
        donegroup.Wait :milestone, m3, 04, 0m
        waiting for all registered funcs to finish     :b, 04, 2m

    section cleanup func1
        register func1 using donegroup.Cleanup :milestone, m1, 01, 0m
        waiting for context cancellation   :a, 01, 2m
        executing func1  :active, b, 03, 3m

    section cleanup func2
        register func2 using donegroup.Cleanup :milestone, m3, 02, 0m
        waiting for context cancellation  :a, 02, 1m
        executing func2 :active, b, 03, 2m
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/k1LoW/donegroup"
)

func main() {
	ctx, cancel := donegroup.WithCancel(context.Background())

	// Cleanup process func1 of some kind
	if err := donegroup.Cleanup(ctx, func(_ context.Context) error {
		fmt.Println("cleanup func1")
		return nil
	}); err != nil {
		log.Fatal(err)
	}

	// Cleanup process func2 of some kind
	if err := donegroup.Cleanup(ctx, func(_ context.Context) error {
		time.Sleep(1 * time.Second)
		fmt.Println("cleanup func2")
		return nil
	}); err != nil {
		log.Fatal(err)
	}

	defer func() {
		cancel()
		if err := donegroup.Wait(ctx); err != nil {
			log.Fatal(err)
		}
	}()

	// Main process of some kind
	fmt.Println("main")

	// Output:
	// main finish
	// cleanup func1
	// cleanup func2
}

dongroup.Cleanup is similar in usage to testing.T.Cleanup, but the order of execution is not guaranteed.

Wait for a specified duration ( donegroup.WaitWithTimeout )

Using donegroup.WaitWithTimeout, it is possible to set a timeout for the cleanup processes.

Note that each cleanup process must handle its own context argument.

gantt
    dateFormat  mm
    axisFormat  

    section main
        donegroup.WithCancel :milestone, m1, 00, 0m
        cancel context using cancel() : milestone, m2, 03, 0m
        donegroup.WaitWithTimeout :milestone, m3, 04, 0m
        waiting for all registered funcs to finish     :b, 04, 1m
        timeout :milestone, m4, 05, 0m

    section cleanup func
        register func using donegroup.Cleanup :milestone, m1, 01, 0m
        waiting for context cancellation   :a, 01, 2m
        executing func  :active, b, 03, 3m
ctx, cancel := WithCancel(context.Background())

// Cleanup process of some kind
if err := Cleanup(ctx, func(ctx context.Context) error {
	fmt.Println("cleanup start")
	for i := 0; i < 10; i++ {
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
			time.Sleep(2 * time.Millisecond)
		}
	}
	fmt.Println("cleanup finish")
	return nil
}); err != nil {
	log.Fatal(err)
}

defer func() {
	cancel()
	timeout := 5 * time.Millisecond
	if err := WaitWithTimeout(ctx, timeout); err != nil {
		fmt.Println(err)
	}
}()

// Main process of some kind
fmt.Println("main start")

fmt.Println("main finish")

// Output:
// main start
// main finish
// cleanup start
// context deadline exceeded
donegroup.Awaiter

In addition to using donegroup.Cleanup to register a cleanup function after context cancellation, it is possible to use donegroup.Awaiter to make the execution of an arbitrary process wait.

gantt
    dateFormat  mm
    axisFormat  

    section main
        donegroup.WithCancel :milestone, m1, 00, 0m
        cancel context using cancel() : milestone, m2, 03, 0m
        donegroup.Wait :milestone, m3, 04, 0m
        waiting for all registered funcs to finish     :b, 04, 1m

    section func on goroutine
        executing go func  :active, b, 01, 4m
        donegroup.Awaiter :milestone, m3, 01, 0m
        completed() :milestone, m3, 05, 0m
ctx, cancel := donegroup.WithCancel(context.Background())

go func() {
	completed, err := donegroup.Awaiter(ctx)
	if err != nil {
		log.Fatal(err)
		return
	}
	time.Sleep(1000 * time.Millisecond)
	fmt.Println("do something")
	completed()
}()

// Main process of some kind
fmt.Println("main")
time.Sleep(10 * time.Millisecond)

cancel()
if err := donegroup.Wait(ctx); err != nil {
	log.Fatal(err)
}

fmt.Println("finish")

// Output:
// main
// do something
// finish

It is also possible to guarantee the execution of a function block using defer donegroup.Awaitable(ctx)().

go func() {
	defer donegroup.Awaitable(ctx)()
	time.Sleep(1000 * time.Millisecond)
	fmt.Println("do something")
	completed()
}()
Syntax sugar for go func() and donegroup.Awaiter ( donegroup.Go )

donegroup.Go can execute arbitrary process asynchronously while still waiting for it to finish, similar to donegroup.Awaiter.

gantt
    dateFormat  mm
    axisFormat  

    section main
        donegroup.WithCancel :milestone, m1, 00, 0m
        cancel context using cancel() : milestone, m2, 03, 0m
        donegroup.Wait :milestone, m3, 04, 0m
        waiting for all registered funcs to finish     :b, 04, 1m

    section func on donegroup.Go
        register and execute func using donegroup.Go :milestone, m3, 01, 0m
        executing func  :active, b, 01, 4m
donegroup.Go(ctx, func() error {
	time.Sleep(1000 * time.Millisecond)
	fmt.Println("do something")
	return nil
}()

Also, with donegroup.Go, the error can be received via donegroup.Wait.

Syntax sugar for cancel() and donegroup.Wait ( donegroup.Cancel )

If cancel() and donegroup.Wait are to be executed at the same time, donegroup.Cancel can be used.

ctx, cancel := donegroup.WithCancel(context.Background())
defer func() {
	cancel()
	if err := donegroup.Wait(ctx); err != nil {
		log.Fatal(err)
	}
}()

and

ctx, _ := donegroup.WithCancel(context.Background())
defer func() {
	if err := donegroup.Cancel(ctx); err != nil {
		log.Fatal(err)
	}
}()

are equivalent.

Documentation

Overview

Example
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/k1LoW/donegroup"
)

func main() {
	ctx, cancel := donegroup.WithCancel(context.Background())

	// Cleanup process of some kind
	if err := donegroup.Cleanup(ctx, func(_ context.Context) error {
		time.Sleep(10 * time.Millisecond)
		fmt.Println("cleanup with sleep")
		return nil
	}); err != nil {
		log.Fatal(err)
	}

	// Cleanup process of some kind
	if err := donegroup.Cleanup(ctx, func(_ context.Context) error {
		fmt.Println("cleanup")
		return nil
	}); err != nil {
		log.Fatal(err)
	}

	defer func() {
		cancel()

		if err := donegroup.Wait(ctx); err != nil {
			log.Fatal(err)
		}
	}()

	// Main process of some kind
	fmt.Println("main start")

	fmt.Println("main finish")

}
Output:

main start
main finish
cleanup
cleanup with sleep

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNotContainDoneGroup = errors.New("donegroup: context does not contain a doneGroup. Use donegroup.WithCancel to create a context with a doneGroup")

Functions

func Awaitable added in v1.3.0

func Awaitable(ctx context.Context) (completed func())

Awaitable returns a function that guarantees execution of the process until it is called. Note that if the timeout of WaitWithTimeout has passed (or the context of WaitWithContext has canceled), it will not wait.

Example
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/k1LoW/donegroup"
)

func main() {
	ctx, cancel := donegroup.WithCancel(context.Background())

	go func() {
		defer donegroup.Awaitable(ctx)()
		for {
			select {
			case <-ctx.Done():
				time.Sleep(100 * time.Millisecond)
				fmt.Println("cleanup")
				return
			case <-time.After(10 * time.Millisecond):
				fmt.Println("do something")
			}
		}
	}()

	// Main process of some kind
	fmt.Println("main")
	time.Sleep(35 * time.Millisecond)

	cancel()
	if err := donegroup.Wait(ctx); err != nil {
		log.Fatal(err)
	}

}
Output:

main
do something
do something
do something
cleanup

func AwaitableWithKey added in v1.3.0

func AwaitableWithKey(ctx context.Context, key any) (completed func())

AwaitableWithKey returns a function that guarantees execution of the process until it is called. Note that if the timeout of WaitWithTimeout has passed (or the context of WaitWithContext has canceled), it will not wait.

func Awaiter added in v1.1.0

func Awaiter(ctx context.Context) (completed func(), err error)

Awaiter returns a function that guarantees execution of the process until it is called. Note that if the timeout of WaitWithTimeout has passed (or the context of WaitWithContext has canceled), it will not wait.

Example
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/k1LoW/donegroup"
)

func main() {
	ctx, cancel := donegroup.WithCancel(context.Background())

	go func() {
		completed, err := donegroup.Awaiter(ctx)
		if err != nil {
			log.Fatal(err)
			return
		}
		<-ctx.Done()
		time.Sleep(100 * time.Millisecond)
		fmt.Println("do something")
		completed()
	}()

	// Main process of some kind
	fmt.Println("main")
	time.Sleep(10 * time.Millisecond)

	cancel()
	if err := donegroup.Wait(ctx); err != nil {
		log.Fatal(err)
	}

	fmt.Println("finish")

}
Output:

main
do something
finish

func AwaiterWithKey added in v1.1.0

func AwaiterWithKey(ctx context.Context, key any) (completed func(), err error)

AwaiterWithKey returns a function that guarantees execution of the process until it is called. Note that if the timeout of WaitWithTimeout has passed (or the context of WaitWithContext has canceled), it will not wait.

func Cancel added in v1.2.0

func Cancel(ctx context.Context) error

Cancel cancels the context. Then calls the function registered by Cleanup.

func CancelWithContext added in v1.2.0

func CancelWithContext(ctx, ctxw context.Context) error

CancelWithContext cancels the context. Then calls the function registered by Cleanup with context (ctxw).

func CancelWithContextAndKey added in v1.2.0

func CancelWithContextAndKey(ctx, ctxw context.Context, key any) error

CancelWithContextAndKey cancels the context. Then calls the function registered by Cleanup with context (ctxw).

func CancelWithKey added in v1.2.0

func CancelWithKey(ctx context.Context, key any) error

CancelWithKey cancels the context. Then calls the function registered by Cleanup.

func CancelWithTimeout added in v1.2.0

func CancelWithTimeout(ctx context.Context, timeout time.Duration) error

CancelWithTimeout cancels the context. Then calls the function registered by Cleanup with timeout.

func CancelWithTimeoutAndKey added in v1.2.0

func CancelWithTimeoutAndKey(ctx context.Context, timeout time.Duration, key any) error

CancelWithTimeoutAndKey cancels the context. Then calls the function registered by Cleanup with timeout.

func Cleanup added in v0.2.3

func Cleanup(ctx context.Context, f func(ctx context.Context) error) error

Cleanup registers a function to be called when the context is canceled.

func CleanupWithKey added in v0.2.3

func CleanupWithKey(ctx context.Context, key any, f func(ctx context.Context) error) error

CleanupWithKey Cleanup registers a function to be called when the context is canceled.

func Go added in v1.5.0

func Go(ctx context.Context, f func() error)

Go calls the function now asynchronously. If an error occurs, it is stored in the doneGroup. Note that if the timeout of WaitWithTimeout has passed (or the context of WaitWithContext has canceled), it will not wait.

func GoWithKey added in v1.5.0

func GoWithKey(ctx context.Context, key any, f func() error)

GoWithKey calls the function now asynchronously. If an error occurs, it is stored in the doneGroup. Note that if the timeout of WaitWithTimeout has passed (or the context of WaitWithContext has canceled), it will not wait.

func Wait

func Wait(ctx context.Context) error

Wait blocks until the context is canceled. Then calls the function registered by Cleanup.

func WaitWithContext

func WaitWithContext(ctx, ctxw context.Context) error

WaitWithContext blocks until the context (ctx) is canceled. Then calls the function registered by Cleanup with context (ctxw).

func WaitWithContextAndKey

func WaitWithContextAndKey(ctx, ctxw context.Context, key any) error

WaitWithContextAndKey blocks until the context is canceled. Then calls the function registered by Cleanup with context (ctxx).

func WaitWithKey

func WaitWithKey(ctx context.Context, key any) error

WaitWithKey blocks until the context is canceled. Then calls the function registered by Cleanup.

func WaitWithTimeout added in v0.2.2

func WaitWithTimeout(ctx context.Context, timeout time.Duration) error

WaitWithTimeout blocks until the context (ctx) is canceled. Then calls the function registered by Cleanup with timeout.

Example
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/k1LoW/donegroup"
)

func main() {
	ctx, cancel := donegroup.WithCancel(context.Background())

	// Cleanup process of some kind
	if err := donegroup.Cleanup(ctx, func(ctx context.Context) error {
		fmt.Println("cleanup start")
		for i := 0; i < 10; i++ {
			select {
			case <-ctx.Done():
				return ctx.Err()
			default:
				time.Sleep(2 * time.Millisecond)
			}
		}
		fmt.Println("cleanup finish")
		return nil
	}); err != nil {
		log.Fatal(err)
	}

	defer func() {
		cancel()
		timeout := 5 * time.Millisecond
		if err := donegroup.WaitWithTimeout(ctx, timeout); err != nil {
			fmt.Println(err)
		}
	}()

	// Main process of some kind
	fmt.Println("main start")

	fmt.Println("main finish")

}
Output:

main start
main finish
cleanup start
context deadline exceeded

func WaitWithTimeoutAndKey added in v0.2.2

func WaitWithTimeoutAndKey(ctx context.Context, timeout time.Duration, key any) error

WaitWithTimeoutAndKey blocks until the context is canceled. Then calls the function registered by Cleanup with timeout.

func WithCancel

func WithCancel(ctx context.Context) (context.Context, context.CancelFunc)

WithCancel returns a copy of parent with a new Done channel and a doneGroup.

func WithCancelCause added in v1.6.0

func WithCancelCause(ctx context.Context) (context.Context, context.CancelCauseFunc)

WithCancelCause returns a copy of parent with a new Done channel and a doneGroup.

func WithCancelCauseWithKey added in v1.6.0

func WithCancelCauseWithKey(ctx context.Context, key any) (context.Context, context.CancelCauseFunc)

WithCancelCauseWithKey returns a copy of parent with a new Done channel and a doneGroup.

func WithCancelWithKey

func WithCancelWithKey(ctx context.Context, key any) (context.Context, context.CancelFunc)

WithCancelWithKey returns a copy of parent with a new Done channel and a doneGroup.

func WithDeadline added in v1.6.0

func WithDeadline(ctx context.Context, d time.Time) (context.Context, context.CancelFunc)

WithDeadline returns a copy of parent with a new Done channel and a doneGroup. If the deadline is exceeded, the cause is set to context.DeadlineExceeded.

func WithDeadlineCause added in v1.6.0

func WithDeadlineCause(ctx context.Context, d time.Time, cause error) (context.Context, context.CancelFunc)

WithDeadlineCause returns a copy of parent with a new Done channel and a doneGroup.

func WithDeadlineCauseWithKey added in v1.6.0

func WithDeadlineCauseWithKey(ctx context.Context, d time.Time, cause error, key any) (context.Context, context.CancelFunc)

WithDeadlineCauseWithKey returns a copy of parent with a new Done channel and a doneGroup.

func WithDeadlineWithKey added in v1.6.0

func WithDeadlineWithKey(ctx context.Context, d time.Time, key any) (context.Context, context.CancelFunc)

WithDeadlineWithKey returns a copy of parent with a new Done channel and a doneGroup.

func WithTimeout added in v1.6.0

func WithTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc)

WithTimeout returns a copy of parent with a new Done channel and a doneGroup. If the timeout is exceeded, the cause is set to context.DeadlineExceeded.

func WithTimeoutCause added in v1.6.0

func WithTimeoutCause(ctx context.Context, timeout time.Duration, cause error) (context.Context, context.CancelFunc)

WithTimeoutCause returns a copy of parent with a new Done channel and a doneGroup.

func WithTimeoutCauseWithKey added in v1.6.0

func WithTimeoutCauseWithKey(ctx context.Context, timeout time.Duration, cause error, key any) (context.Context, context.CancelFunc)

WithTimeoutCauseWithKey returns a copy of parent with a new Done channel and a doneGroup.

func WithTimeoutWithKey added in v1.6.0

func WithTimeoutWithKey(ctx context.Context, timeout time.Duration, key any) (context.Context, context.CancelFunc)

WithTimeoutWithKey returns a copy of parent with a new Done channel and a doneGroup.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL