CSPEx v1.1.0 CSP.Channel

Module used to create and manage channels.

Options

There are some options that may be used to change a channel behavior, but the channel's options can only be set during it's creation.

The available options are:

  • name - Registers the channel proccess with a name. Note that the naming constraints are the same applied to a GenServer.
  • buffer_type - The type of the buffer used in the channel (default: :blocking).
  • buffer_size - The maximum capacity of the channel's buffer (default: 0).

Buffer types

There are three suported buffer types:

  • :blocking - The channel never blocks until the buffer capacity is full.
  • :sliding - The channel never blocks, but when the buffer is full, it starts discarding the older values on it to make room for the newer ones.
  • :dropping - The channel never blocks, but when the buffer is full, it starts discarding any new values that are put on it, keeping the old ones.

Collections interoperability

You can use a channel just like any collection:

channel = Channel.new

pid = spawn_link(fn -> Enum.into([:some, :data], channel) end)
Process.alive?(pid) #=> true

Channel.get(channel) #=> :some
Process.alive?(pid) #=> true

Channel.get(channel) #=> :data
Process.alive?(pid) #=> false

All functions from Enum and Stream are available, but you must take into consideration the blocking operations:

channel = Channel.new

# This line will block until someone reads all the ten values.
Enum.into(1..10, channel)

# This line will block until someone puts at least 4 values on the channel.
# (More if there are more listeners on the same channel)
Enum.take(channel, 4)

# This line will block until someone closes the channel.
Enum.into(channel, [])

Example

You can use a channel in a supervision tree:

import Supervisor.Spec

children = [
  worker(Channel, [[name: MyApp.Channel, buffer_size: 10]])
]

{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)

# You can use all the functions with the registered name instead
# of the channel struct
Channel.put(MyApp.Channel, :data)
Channel.put(MyApp.Channel, :other)

Channel.get(MyApp.Channel) #=> :data
Channel.get(MyApp.Channel) #=> :other

# If you want to use it as a collection just call Channel.wrap/1
channel = Channel.wrap(MyApp.Channel)

Enum.into(1..5, channel)
Enum.count(channel) #=> 5

You can use channels in any part of list comprehensions:

channel = Enum.into(1..5, Channel.new(buffer_size: 5))

:ok = Channel.close(channel)

other_channel = for x <- channel, into: Channel.new(buffer_size: 5) do
  x * 2
end

:ok = Channel.close(other_channel)

Enum.to_list(other_channel) #=> [2, 4, 6, 8, 10]

Summary

Functions

Function responsible for closing a channel

Returns true if the channel is closed or false otherwise

Function responsible for fetching a value of the channel

Returns true or false wheter the value is present on the channel

Function responsible for creating a new channel

Function responsible for putting a value in the channel

Returns the current size of the channel

Function responsible for the starting of the channel

Wraps the PID or registered name in a Channel struct

Types

buffer_type :: :blocking | :sliding | :dropping
channel_ref :: term | t
option ::
  {:buffer_size, non_neg_integer} |
  {:buffer_type, buffer_type} |
  {:name, GenServer.name}
t :: %CSP.Channel{ref: term}

Functions

close(channel)

Specs

close(channel_ref) :: :ok

Function responsible for closing a channel.

Example

iex> channel = Channel.new
iex> Channel.closed?(channel)
false
iex> Channel.close(channel)
iex> Channel.closed?(channel)
true
closed?(channel)

Specs

closed?(channel_ref) :: boolean

Returns true if the channel is closed or false otherwise.

get(channel)

Specs

get(channel_ref) :: term

Function responsible for fetching a value of the channel.

It will block until a value is inserted in the channel or it is closed.

Always returns nil when the channel is closed.

Example

iex> channel = Channel.new
iex> spawn_link(fn -> Channel.put(channel, :data) end)
iex> Channel.get(channel)
:data
iex> Channel.close(channel)
iex> Channel.get(channel)
nil
member?(channel, value)

Specs

member?(channel_ref, term) :: boolean

Returns true or false wheter the value is present on the channel.

new(options \\ [])

Specs

new(options) :: t

Function responsible for creating a new channel.

Useful for using channels outside of a supervision tree.

Example

iex> channel = Channel.new
iex> spawn_link(fn -> Channel.put(channel, :data) end)
iex> Channel.get(channel)
:data
put(channel, item)

Specs

put(channel_ref, term) :: :ok

Function responsible for putting a value in the channel.

It may block until a value is fetched deppending on the buffer type of the channel.

Raises if trying to put nil or if trying to put anything in a closed channel.

Example

iex> channel = Channel.new(buffer_size: 5)
iex> Channel.put(channel, :data)
iex> Channel.put(channel, :other)
iex> Channel.close(channel)
iex> Enum.to_list(channel)
[:data, :other]
size(channel)

Specs

size(channel_ref) :: non_neg_integer

Returns the current size of the channel.

Remember that the size of the channel is the number of items in the buffer plus the number of pending "put" operations.

Example

iex> channel = Enum.into(1..3, Channel.new(buffer_size: 4))
iex> Channel.size(channel)
3
iex> Channel.put(channel, 4)
iex> Channel.size(channel)
4
iex> spawn_link(fn -> Channel.put(channel, 5) end)
iex> :timer.sleep(10)
iex> Channel.size(channel)
5 # 4 items in the full buffer plus one pending "put"
start(options \\ [])

Specs

Non-linking version of CSP.Channel.start_link/1

start_link(options \\ [])

Specs

start_link(options) :: GenServer.on_start

Function responsible for the starting of the channel.

Ideal for using a CSP in a supervision tree.

wrap(channel)

Specs

wrap(channel_ref) :: t

Wraps the PID or registered name in a Channel struct.

If the passed in value is already a Channel struct, return it unchanged.

Example

iex> {:ok, pid} = Channel.start_link(buffer_size: 5)
iex> channel = Channel.wrap(pid)
iex> Enum.into(1..5, channel)
iex> Channel.close(channel)
iex> Enum.to_list(channel)
[1, 2, 3, 4, 5]

iex> channel = Channel.new
iex> channel == Channel.wrap(channel)
true