wise-home/hub
{ "createdAt": "2017-04-13T21:47:18Z", "defaultBranch": "master", "description": "Pub-sub hub with pattern subscription", "fullName": "wise-home/hub", "homepage": "https://github.com/wise-home/hub", "language": "Elixir", "name": "hub", "pushedAt": "2024-07-08T08:10:09Z", "stargazersCount": 50, "topics": [ "elixir", "pubsub" ], "updatedAt": "2024-07-08T08:10:14Z", "url": "https://github.com/wise-home/hub"}A single_node pub-sub hub with pattern matching subscriptions.
It has no dependencies.
Example:
# In one process:Hub.subscribe("some_channel", %{name: _, age: age} when age > 42)receive do %{name: name} -> IO.puts("#{name} is older than 42")end
# In another process:Hub.publish("some_channel", %{name: "John", age: 48})Installation
Section titled “Installation”Add hub to your list of dependencies in mix.exs:
def deps do [{:hub, "~> 0.6"}]endStatus
Section titled “Status”How it works and what you should know.
Section titled “How it works and what you should know.”Behind the scenes the pattern is “decompiled” into an Elixir AST and saved with the subscription. When a message is published to a channel, the pattern of each subscription is checked against the message and the subscriptions that match receives the message.
If you have lots of messages and lots of subscribers in the same channel, this is probably not for you, since the performance cost of pattern matching each message against each subscriber could be a problem.
However, many applications with many published messages can easily split messages into multiple channels based on application specific criteria. If performance is a concern, having different channels should be used as much as possible.
A subscription is made with a quoted pattern:
Hub.subscribe_quoted("My channel", quote(do: {:some, pattern}))A convenience macro, subscribe, can be used to avoid the quote. Given the default value of the options, the
following is equivalent to the above:
require HubHub.subscribe("My channel", {:some, pattern})To publish a message in a channel, call Hub.publish/2:
Hub.publish("My channel", {:any, "valid", %{elixir: "term"}})When a message is published to the pid of a subscription, it is send directly and unmodified to that process’ mailbox.
The subscriber should receive the message:
Hub.subscribe("Channel", {:hello, name})Hub.subscribe("Channel", {:goodbye, name})
receive do {:hello, name} -> IO.puts("Hello #{name}") {:goodbye, name} -> IO.puts("Goodbye #{name}")endIf the receiver is a GenServer, and you don’t want a blocking receive, use handle_info instead:
def handle_info({:hello, name}, state) do IO.puts("Hello #{name}") {:noreply, state}endSubscribe options
Section titled “Subscribe options”subscribe and subscribe_quoted accepts these options:
pid(defaultself()) is the process that should receive published messages.count(default:infinity) is how many times a subscription can be triggered before it is auto-unsubscribed.multi(defaultfalse). Whentrue, thepatternargument must be a list of multiple patterns. This is handy if combined withcount.
Using local variables
Section titled “Using local variables”Sometimes one wish to subscribe using a pattern involving local variables.
The subscribe macro accepts a bind_quoted argument, that will replace pinned variables with the given values.
E.g.
size = 42Hub.subscribe("my channel", %{size: ^size}, bind_quoted: [size: size])is equivalent to
Hub.subscribe("my channel", %{size: 42})Unsubscribe
Section titled “Unsubscribe”To unsubscribe, use the returned reference given when subscribing:
{:ok, subscription} = Hub.subscribe("my channel", {:hello, name})Hub.unsubscribe(subscription)To avoid race conditions, it can be useful to also flush matched messages, so they don’t leak. Example:
{:ok, subscription} = Hub.subscribe("my channel", {:hello, name})
receive do {:hello, name} -> IO.puts("Hello #{name}")after 5_000 -> Hub.unsubscribe_and_flush(subscription)endExamples
Section titled “Examples”Subscribe only once to a message:
Hub.subscribe("My channel", {:hello, name}, count: 1)when is perfectly legal to use in the pattern:
Hub.subscribe("My channel", %User{age: age} when age > 42)Subscribe another process:
Hub.subscribe("My channel", {:hello, name}, pid: child_pid)Subscribe to all messages in a channel:
Hub.subscribe("My channel", _)Subscribe to the first message that matches one of the patterns:
Hub.subscribe("My channel", [{:hello, name}, {:goodbye, name}], multi: true, count: 1)Contributing
Section titled “Contributing”Tests are run with mix test. When submitting new code, make sure mix credo also passes.
Versions of Elixir and Erlang
Section titled “Versions of Elixir and Erlang”Hub will usually be tested against the two latest minor versions of Elixir and the three latest minor versions of Erlang. In all cases the latest patch version is used.
E.g. if the latest Elixir is 1.7.2 and the latest Erlang is 21.0.5, we test against:
- Elixir: 1.7.2 and 1.6.6
- Erlang: 21.0.5, 20.3.8.6 and 20.2.4
See in the github actions script, which versions are officially supported.