GithubHelp home page GithubHelp logo

devnw / stream Goto Github PK

View Code? Open in Web Editor NEW
65.0 1.0 3.0 282 KB

stream is a concurrency pattern implementation using Go generics. a.k.a. my accidentally awesome library

License: Apache License 2.0

Go 92.17% Makefile 7.83%
golang concurrency go stream-processing concurrency-patterns generics templates generic-programming

stream's Introduction

stream

-- import "."

Package stream provides a set of generic functions for working concurrent design patterns in Go.

Build & Test Action Status Go Report Card codecov Go Reference License: Apache 2.0 PRs Welcome

Installation

To install the package, run:

go get -u go.atomizer.io/stream@latest

Usage

import "go.atomizer.io/stream"

Benchmarks

To execute the benchmarks, run the following command:

go test -bench=. ./...

To view benchmarks over time for the main branch of the repository they can be seen on our Benchmark Report Card.

Usage

const MinLife = time.Millisecond

MinLife is the minimum life time for the scaler. This is used to prevent the scaler from exiting too quickly, and causing too small of a lifetime.

const MinWait = time.Millisecond

MinWait is the absolute minimum wait time for the ticker. This is used to prevent the ticker from firing too often and causing too small of a wait time.

var ErrFnRequired = fmt.Errorf("nil InterceptFunc, Fn is required")

func Any

func Any[T any](ctx context.Context, in <-chan T) <-chan any

Any accepts an incoming data channel and converts the channel to a readonly channel of the any type.

func Distribute

func Distribute[T any](
	ctx context.Context, in <-chan T, out ...chan<- T,
)

Distribute accepts an incoming data channel and distributes the data among the supplied outgoing data channels using a dynamic select statement.

NOTE: Execute the Distribute function in a goroutine if parallel execution is desired. Canceling the context or closing the incoming channel is important to ensure that the goroutine is properly terminated.

func Drain

func Drain[T any](ctx context.Context, in <-chan T)

Drain accepts a channel and drains the channel until the channel is closed or the context is canceled.

func FanIn

func FanIn[T any](ctx context.Context, in ...<-chan T) <-chan T

FanIn accepts incoming data channels and forwards returns a single channel that receives all the data from the supplied channels.

NOTE: The transfer takes place in a goroutine for each channel so ensuring that the context is canceled or the incoming channels are closed is important to ensure that the goroutine is terminated.

func FanOut

func FanOut[T any](
	ctx context.Context, in <-chan T, out ...chan<- T,
)

FanOut accepts an incoming data channel and copies the data to each of the supplied outgoing data channels.

NOTE: Execute the FanOut function in a goroutine if parallel execution is desired. Canceling the context or closing the incoming channel is important to ensure that the goroutine is properly terminated.

func Intercept

func Intercept[T, U any](
	ctx context.Context,
	in <-chan T,
	fn InterceptFunc[T, U],
) <-chan U

Intercept accepts an incoming data channel and a function literal that accepts the incoming data and returns data of the same type and a boolean indicating whether the data should be forwarded to the output channel. The function is executed for each data item in the incoming channel as long as the context is not canceled or the incoming channel remains open.

func Pipe

func Pipe[T any](
	ctx context.Context, in <-chan T, out chan<- T,
)

Pipe accepts an incoming data channel and pipes it to the supplied outgoing data channel.

NOTE: Execute the Pipe function in a goroutine if parallel execution is desired. Canceling the context or closing the incoming channel is important to ensure that the goroutine is properly terminated.

type DurationScaler

type DurationScaler struct {
	// Interval is the number the current step must be divisible by in order
	// to modify the time.Duration.
	Interval int

	// ScalingFactor is a value between -1 and 1 that is used to modify the
	// time.Duration of a ticker or timer. The value is multiplied by
	// the ScalingFactor is multiplied by the duration for scaling.
	//
	// For example, if the ScalingFactor is 0.5, then the duration will be
	// multiplied by 0.5. If the ScalingFactor is -0.5, then the duration will
	// be divided by 0.5. If the ScalingFactor is 0, then the duration will
	// not be modified.
	//
	// A negative ScalingFactor will cause the duration to decrease as the
	// step value increases causing the ticker or timer to fire more often
	// and create more routines. A positive ScalingFactor will cause the
	// duration to increase as the step value increases causing the ticker
	// or timer to fire less often and create less routines.
	ScalingFactor float64
}

DurationScaler is used to modify the time.Duration of a ticker or timer based on a configured step value and modifier (between -1 and 1) value.

type InterceptFunc

type InterceptFunc[T, U any] func(context.Context, T) (U, bool)

type Scaler

type Scaler[T, U any] struct {
	Wait time.Duration
	Life time.Duration
	Fn   InterceptFunc[T, U]

	// WaitModifier is used to modify the Wait time based on the number of
	// times the Scaler has scaled up. This is useful for systems
	// that are CPU bound and need to scale up more/less quickly.
	WaitModifier DurationScaler

	// Max is the maximum number of layer2 routines that will be spawned.
	// If Max is set to 0, then there is no limit.
	Max uint
}

Scaler implements generic auto-scaling logic which starts with a net-zero set of processing routines (with the exception of the channel listener) and then scales up and down based on the CPU contention of a system and the speed at which the InterceptionFunc is able to process data. Once the incoming channel becomes blocked (due to nothing being sent) each of the spawned routines will finish out their execution of Fn and then the internal timer will collapse brining the routine count back to zero until there is more to be done.

To use Scalar, simply create a new Scaler[T, U], configuring the Wait, Life, and InterceptFunc fields. These fields are what configure the functionality of the Scaler.

NOTE: Fn is REQUIRED! Defaults: Wait = 1ns, Life = 1ยตs

After creating the Scaler instance and configuring it, call the Exec method passing the appropriate context and input channel.

Internally the Scaler implementation will wait for data on the incoming channel and attempt to send it to a layer2 channel. If the layer2 channel is blocking and the Wait time has been reached, then the Scaler will spawn a new layer2 which will increase throughput for the Scaler, and Scaler will attempt to send the data to the layer2 channel once more. This process will repeat until a successful send occurs. (This should only loop twice).

func (Scaler[T, U]) Exec

func (s Scaler[T, U]) Exec(ctx context.Context, in <-chan T) (<-chan U, error)

Exec starts the internal Scaler routine (the first layer of processing) and returns the output channel where the resulting data from the Fn function will be sent.

stream's People

Contributors

benjivesterby avatar dependabot[bot] avatar ghjm avatar github-actions[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

stream's Issues

[Bug]: Silently swallowing panics is bad

๐Ÿž Describe the bug

The Intercept(), FanOut() and Distribute() functions use defer recover() to silently ignore panics. The intent, as stated in a comment, is to ignore sends to closed channels. There are several problems with this:

  • Go semantics are that sends to closed channels produce panics. If this library is intended to work and feel like a Go native library, it ought to work the way Go works, without surprises for library users.
  • If a library user wants to close channels and cause panics, but then trap them and recover from the panics, then that's the user's business, not the library's. The user can perfectly well recover from the panic in their own code where they send to the channel, if they want to.
  • A library user who doesn't intend to cause panics by sending to closed channels, but is doing so due to a bug in their code, will be lulled into complacency by the library's failure to indicate any problem. Sending to a closed channel panics for good reason - it indicates a fundamental problem with the application's code, which ought to be detected and looked at.
  • There is no guarantee that this code, or some future modification to this code, won't panic in some different way, which will then not be surfaced at all, making debugging more difficult.

I suggest that the recover() calls simply be removed, and that doing so will improve the library.

[Bug]: Possible issue with Nil data or Channel in Fan-Out

๐Ÿž Describe the bug

Panic occurred in Stream possibly due to nil channel or nil data on channel when using fan-out

panic: reflect.Select: SendDir case missing Send value

goroutine 817 [running]:
reflect.Select({0xc0002aca80?, 0x2, 0x0?})
        /usr/local/go/src/reflect/value.go:2922 +0x6f2
go.atomizer.io/stream.FanOut[...]({0x55f8d88, 0xc009086000?}, 0xc009088120?, {0xc009090000, 0x1, 0x2})

๐Ÿ“š To Reproduce

๐Ÿ’ก Expected behavior

Should not panic on nil channel or nil data

โš™๏ธ Environment

  • Release Version: v1.0.7
  • OS: Linux
  • Architecture: x86_64
  • Go Version: 1.19

[Feat]: Add configuration for max routines

This would track the total number of routines that are issued and limit new instantiations to the configured maximum. This will likely be a buffered channel that is used to set the max and blocks new creations when the buffer is empty.

This will require a good amount of testing and benchmarking to ensure proper function

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.