coro

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jan 24, 2020 License: MIT Imports: 5 Imported by: 2

README

coro Build Status GoDoc

Package coro implements cooperative coroutines on top of goroutines.

It then implements iterators on top of that.

resume := coro.New(func(yield func()) {
	for i := 1; i <= 3; i++ {
		fmt.Println("coroutine:", i)
		yield()
	}
	fmt.Println("coroutine: done")
})

fmt.Println("not started yet")
for resume() {
	fmt.Println("yielded")
}
fmt.Println("returned")

// Output:
// not started yet
// coroutine: 1
// yielded
// coroutine: 2
// yielded
// coroutine: 3
// yielded
// coroutine: done
// returned
iter := NewStringIterator(func(yield func(string)) error {
	for _, foo := range []string{"foo", "bar", "baz"} {
		yield(foo)
	}
	return errors.New("done")
})

for iter.Next() {
	fmt.Println("yielded:", iter.Yielded)
}
fmt.Println("returned:", iter.Returned)

// Output:
// yielded: foo
// yielded: bar
// yielded: baz
// returned: done

Documentation

Overview

Package coro implements cooperative coroutines on top of goroutines.

coro implements a concurrency model similar to Lua's coroutines, Python's generators or Ruby's fibers, in which several concurrent processes exist at the same time, but whose executions don't overlap; instead, they explicitly yield control to each other.

The coroutine protocol

coro provides a base protocol for coordinating goroutines. In this protocol, there is a goroutine whose execution only proceeds when other goroutines decide so; in turn, those goroutines block until the former goroutine either finishes or "yields".

Such goroutine, which we call "coroutine", is created with the New function. To New, you pass a function that defines the coroutine's execution much like you pass a function to the 'go' statement that defines the goroutine's execution. The difference is that the coroutine doesn't start right away; instead, another goroutine must call the Resume function returned by New.

The Resume function blocks the calling goroutine while the coroutine is executing. The coroutine may then call the 'yield' function which is passed to its defining function. 'yield', in turn, blocks the coroutine until the Resume function is called again.

Thus, while a goroutine is blocked on calling a Resume func, the coroutine is executing; and, while the coroutine is blocked on calling its 'yield' func, the other goroutine is executing.

Resume is also called when the coroutine's defining function returns, in which case it returns false.

Since the participating goroutine's executions never overlap and have a well-defined order, they are synchronized.

Killing and cancelling coroutines

To help prevent goroutine leaks, when a coroutine is blocked on a 'yield' and the library detects that no other goroutine will ever resume it, the call to 'yield' will panic with an ErrKilled error wrapping an ErrLeak.

Additionally, you can tie the coroutine's lifetime to a context by passing the KillOnContextDone option. When the context is cancelled or reaches its deadline, the coroutine is killed.

This kind of panic is recovered by the library. The coroutine's function may intercept such panics in its own deferred recovery code.

The killed coroutine's Resume func, if ever called, will return false, as if the coroutine had exited normally.

Behavior on panics

If the coroutine's goroutine panics, its Resume func returns false, as if the coroutine had exited normally.

Example
package main

import (
	"fmt"

	"github.com/tcard/coro"
)

func main() {
	resume := coro.New(func(yield func()) {
		for i := 1; i <= 3; i++ {
			fmt.Println("coroutine:", i)
			yield()
		}
		fmt.Println("coroutine: done")
	})

	fmt.Println("not started yet")
	for resume() {
		fmt.Println("yielded")
	}
	fmt.Println("returned")

}
Output:

not started yet
coroutine: 1
yielded
coroutine: 2
yielded
coroutine: 3
yielded
coroutine: done
returned

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrLeak = errors.New("coro: coroutine leaked")

ErrLeak is the error with which a coroutine is killed when it's detected to be stuck forever.

Currently, this means that the coroutine's associated Resume function has been garbage-collected.

Functions

This section is empty.

Types

type ErrKilled

type ErrKilled struct {
	By error
}

An ErrKilled is the error with which the library kills a goroutine.

See package-level documentation for details.

func (ErrKilled) Error

func (err ErrKilled) Error() string

func (ErrKilled) Unwrap

func (err ErrKilled) Unwrap() error

type GoFunc

type GoFunc func(func())

A GoFunc spawns goroutines.

type Options

type Options struct {
	// contains filtered or unexported fields
}

Options is an internal configuration type. It's configured via SetOptions provided when creating a coroutine with New.

type Resume

type Resume = func() (alive bool)

Resume is an alias for a function that yields control to a coroutine, blocking until the coroutine either yields control back or returns.

func New

func New(f func(yield func()), setOptions ...SetOption) Resume

New creates a coroutine.

See package-level documentation for details on how coroutines work.

func NewIterator

func NewIterator(yielded, returned interface{}, f func(yield func(interface{})) interface{}, setOption ...SetOption) Resume

NewIterator implements an iterator protocol on top of a raw coroutine.

When the coroutine yields, it calls a yield function with a value. This value is set on the 'yielded' parameter, which must be a pointer to a value settable to the yielded value.

When the coroutine returns, the return value is set in the same way on the 'returned' parameter.

See package exampleiterator for an example of a type-safe wrapper to this function.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/tcard/coro"
)

func main() {
	var yielded int
	var returned error
	next := coro.NewIterator(&yielded, &returned, func(yield func(interface{})) interface{} {
		for i := 1; i <= 3; i++ {
			yield(i)
		}
		return errors.New("done")
	})

	for next() {
		fmt.Println("yielded:", yielded)
	}
	fmt.Println("returned:", returned)

}
Output:

yielded: 1
yielded: 2
yielded: 3
returned: done

type SetOption

type SetOption func(*Options)

A SetOption sets an option on the

func KillOnContextDone

func KillOnContextDone(ctx context.Context) SetOption

KillOnContextDone configures a coroutine to be killed when the provided context is done.

func WithGoFunc

func WithGoFunc(g GoFunc) SetOption

WithGoFunc sets a custom GoFunc to spawn goroutines.

Directories

Path Synopsis
Package exampleiterator is an example type-safe wrapper of coro.NewIterator.
Package exampleiterator is an example type-safe wrapper of coro.NewIterator.

Jump to

Keyboard shortcuts

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