diff --git a/.formatter.exs b/.formatter.exs index 2bed17c..d2cda26 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,3 +1,4 @@ +# Used by "mix format" [ - inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] ] diff --git a/.gitignore b/.gitignore index 632d0cc..e04498b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,17 @@ -# vscode elixir_ls plugin artifacts -/.elixir_ls - # The directory Mix will write compiled artifacts to. -/_build +/_build/ # If you run "mix test --cover", coverage assets end up here. -/cover +/cover/ # The directory Mix downloads your dependencies sources to. -/deps +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ -# Where 3rd-party dependencies like ExDoc output generated docs. -/doc +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch # If the VM crashes, it generates a dump, let's ignore it too. erl_crash.dump @@ -19,6 +19,12 @@ erl_crash.dump # Also ignore archive artifacts (built via "mix archive.build"). *.ez +# Ignore package tarball (built via "mix hex.build"). +swarm-*.tar + +# Temporary files for e.g. tests. +/tmp/ + # Intellij Files /.idea/ -/swarm.iml \ No newline at end of file +/swarm.iml diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c73ceb..96bf4e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ -## Next Release +# Changelog + +## 3.2.0 (2018-02-01) ### Changed @@ -16,7 +18,7 @@ N/A Don't attempt to hand-off or restart processes started with `Swarm.register_name/2` ([#63](https://github.com/bitwalker/swarm/pull/63)). Fixes #62. -## 3.1 +## 3.1.0 (2017-11-15) ### Changed @@ -37,7 +39,7 @@ Don't attempt to hand-off or restart processes started with `Swarm.register_name - Add local registration when restarted named process is already started but unknown locally ([#46](https://github.com/bitwalker/swarm/pull/46)). - Retry starting remote process when module not yet available on target node ([#56](https://github.com/bitwalker/swarm/pull/56)). -## 2.0 +## 2.0.0 (2016-10-01) ### Removed diff --git a/README.md b/README.md index 390782c..a71b861 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,18 @@ # Swarm -[](https://hex.pm/packages/swarm) [](https://travis-ci.com/bitwalker/swarm) - -**NOTE**: If you are upgrading from 1.0, be aware that the autoclustering functionality has been extracted -to its own package, which you will need to depend on if you use that feature. -The package is [libcluster](http://github.com/bitwalker/libcluster) and is available on -[Hex](https://hex.pm/packages/libcluster). Please be sure to read over the README to make sure your -config is properly updated. +[](https://travis-ci.com/bitwalker/swarm) +[](https://hex.pm/packages/swarm) +[](https://hexdocs.pm/swarm/) +[](https://hex.pm/packages/swarm) +[](https://github.com/bitwalker/swarm/blob/master/LICENSE) +[](https://github.com/bitwalker/swarm/commits/master) + +> **NOTE**: If you are upgrading from 1.0, be aware that the autoclustering +> functionality has been extracted to its own package, which you will need to +> depend on if you use that feature. The package is +> [libcluster](http://github.com/bitwalker/libcluster) and is available on +> [Hex](https://hex.pm/packages/libcluster). Please be sure to read over the +> README to make sure your config is properly updated. Swarm is a global distributed registry, offering a feature set similar to that of `gproc`, but architected to handle dynamic node membership and large volumes of process registrations @@ -31,19 +37,27 @@ for you! View the docs [here](https://hexdocs.pm/swarm). -**PLEASE READ**: If you are giving Swarm a spin, it is important to understand that you can concoct scenarios whereby -the registry appears to be out of sync temporarily, this is a side effect of an eventually consistent model and does not mean that -Swarm is not working correctly, rather you need to ensure that applications you build on top of Swarm are written to embrace eventual -consistency, such that periods of inconsistency are tolerated. For the most part though, the registry replicates extremely -quickly, so noticeable inconsistency is more of an exception than a rule, but a proper distributed system should always be designed to -tolerate the exceptions, as they become more and more common as you scale up. If however you notice extreme inconsistency or delayed -replication, then it is possible it may be a bug, or performance issue, so feel free to open an issue if you are unsure, and we will gladly look into it. +> **PLEASE READ**: If you are giving Swarm a spin, it is important to +> understand that you can concoct scenarios whereby the registry appears to be +> out of sync temporarily, this is a side effect of an eventually consistent +> model and does not mean that Swarm is not working correctly, rather you need +> to ensure that applications you build on top of Swarm are written to embrace +> eventual consistency, such that periods of inconsistency are tolerated. For +> the most part though, the registry replicates extremely quickly, so +> noticeable inconsistency is more of an exception than a rule, but a proper +> distributed system should always be designed to tolerate the exceptions, as +> they become more and more common as you scale up. If however you notice +> extreme inconsistency or delayed replication, then it is possible it may be a +> bug, or performance issue, so feel free to open an issue if you are unsure, +> and we will gladly look into it. ## Installation ```elixir defp deps do - [{:swarm, "~> 3.0"}] + [ + {:swarm, "~> 3.0"} + ] end ``` @@ -320,10 +334,6 @@ defmodule MyApp.ExampleUsage do end ``` -## License - -MIT - ## Testing `mix test` runs a variety of tests, most of them use a cluster of @@ -343,8 +353,13 @@ If you don't have `epmd` running you can start it using the following command: epmd -daemon - ## TODO - automated testing (some are present) - QuickCheck model + +## Copyright and License + +Copyright (c) 2016 Paul Schoenfelder + +Released under the MIT License, which can be found in the repository in [`LICENSE.md`](https://github.com/bitwalker/swarm/blob/master/LICENSE.md). diff --git a/lib/swarm/distribution/strategy.ex b/lib/swarm/distribution/strategy.ex index 2a458af..541aca4 100644 --- a/lib/swarm/distribution/strategy.ex +++ b/lib/swarm/distribution/strategy.ex @@ -5,13 +5,15 @@ defmodule Swarm.Distribution.Strategy do via the `libring` library. Custom strategies are expected to return a datastructure or pid which will be - passed along to any functions which need to manipulate the current distribution state. - This can be either a plain datastructure (as is the case with the libring-based strategy), - or a pid which your strategy module then uses to call a process in your own supervision tree. + passed along to any functions which need to manipulate the current + distribution state. This can be either a plain datastructure (as is the case + with the libring-based strategy), or a pid which your strategy module then + uses to call a process in your own supervision tree. - For efficiency reasons, it is highly recommended to use plain datastructures rather than a - process for storing the distribution state, because it has the potential to become a bottleneck otherwise, - however this is really up to the needs of your situation, just know that you can go either way. + For efficiency reasons, it is highly recommended to use plain datastructures + rather than a process for storing the distribution state, because it has the + potential to become a bottleneck otherwise, however this is really up to the + needs of your situation, just know that you can go either way. """ alias Swarm.Distribution.Ring, as: RingStrategy @@ -56,6 +58,7 @@ defmodule Swarm.Distribution.Strategy do @doc """ Adds a list of nodes to the state of the current distribution strategy. + The node list can be composed of both node names (atoms) or tuples containing a node name and a weight for that node. """ diff --git a/lib/swarm/logger.ex b/lib/swarm/logger.ex index e91c502..e4c18bf 100644 --- a/lib/swarm/logger.ex +++ b/lib/swarm/logger.ex @@ -2,7 +2,7 @@ defmodule Swarm.Logger do @moduledoc false @doc """ - Formats a log message to include info on which node swarm is running on + Formats a log message to include info on which node swarm is running on. """ @spec format(String.t()) :: String.t() def format(message), do: "[swarm on #{Node.self()}] #{message}" diff --git a/lib/swarm/registry.ex b/lib/swarm/registry.ex index 2a4a98f..b466fdf 100644 --- a/lib/swarm/registry.ex +++ b/lib/swarm/registry.ex @@ -111,7 +111,7 @@ defmodule Swarm.Registry do end @doc """ - Inserts a new registration, and returns true if successful, or false if not + Inserts a new registration, and returns true if successful, or false if not. """ @spec new(Entry.entry()) :: boolean def new(entry() = reg) do diff --git a/lib/swarm/tracker/crdt.ex b/lib/swarm/tracker/crdt.ex index b687a63..b3f9cc4 100644 --- a/lib/swarm/tracker/crdt.ex +++ b/lib/swarm/tracker/crdt.ex @@ -16,7 +16,7 @@ defmodule Swarm.IntervalTreeClock do | {int_tuple, int_tuple} @doc """ - Creates a new interval tree clock + Creates a new interval tree clock. """ @spec seed() :: __MODULE__.t() def seed(), do: {1, 0} @@ -45,7 +45,7 @@ defmodule Swarm.IntervalTreeClock do def peek({_i, e}), do: {0, e} @doc """ - Records an event on the given clock + Records an event on the given clock. """ @spec event(__MODULE__.t()) :: __MODULE__.t() def event({i, e}) do @@ -69,10 +69,10 @@ defmodule Swarm.IntervalTreeClock do @doc """ Compares two clocks. - If :eq is returned, the two clocks are causally equivalent - If :lt is returned, the first clock is causally dominated by the second - If :gt is returned, the second clock is causally dominated by the first - If :concurrent is returned, the two clocks are concurrent (conflicting) + If :eq is returned, the two clocks are causally equivalent. + If :lt is returned, the first clock is causally dominated by the second. + If :gt is returned, the second clock is causally dominated by the first. + If :concurrent is returned, the two clocks are concurrent (conflicting). """ @spec compare(__MODULE__.t(), __MODULE__.t()) :: :lt | :gt | :eq | :concurrent def compare(a, b) do @@ -88,13 +88,13 @@ defmodule Swarm.IntervalTreeClock do end @doc """ - Encodes the clock as a binary + Encodes the clock as a binary. """ @spec encode(__MODULE__.t()) :: binary def encode({i, e}), do: :erlang.term_to_binary({i, e}) @doc """ - Decodes the clock from a binary + Decodes the clock from a binary. """ @spec decode(binary) :: {:ok, __MODULE__.t()} | {:error, {:invalid_clock, term}} def decode(b) when is_binary(b) do @@ -108,7 +108,7 @@ defmodule Swarm.IntervalTreeClock do end @doc """ - Returns the length of the encoded binary representation of the clock + Returns the length of the encoded binary representation of the clock. """ @spec len(__MODULE__.t()) :: non_neg_integer def len(d), do: :erlang.size(encode(d)) diff --git a/lib/swarm/tracker/tracker.ex b/lib/swarm/tracker/tracker.ex index 920a912..b3833c7 100644 --- a/lib/swarm/tracker/tracker.ex +++ b/lib/swarm/tracker/tracker.ex @@ -60,7 +60,8 @@ defmodule Swarm.Tracker do @doc """ Hand off all the processes running on the given worker to the remaining nodes in the cluster. This can be used to gracefully shut down a node. - Note that if you don't shut down the node after the handoff a rebalance can lead to processes being scheduled on it again. + Note that if you don't shut down the node after the handoff a rebalance can + lead to processes being scheduled on it again. In other words the handoff doesn't blacklist the node for further rebalances. """ def handoff(worker_name, state), @@ -68,6 +69,7 @@ defmodule Swarm.Tracker do @doc """ Tracks a process (pid) with the given name. + Tracking processes with this function will *not* restart the process when its parent node goes down, or shift the process to other nodes if the cluster topology changes. It is strictly for global name registration. @@ -77,12 +79,21 @@ defmodule Swarm.Tracker do @doc """ Tracks a process created via the provided module/function/args with the given name. - The process will be distributed on the cluster based on the implementation of the configured distribution strategy. - If the process' parent node goes down, it will be restarted on the new node which owns its keyspace. + + The process will be distributed on the cluster based on the implementation of + the configured distribution strategy. + + If the process' parent node goes down, it will be restarted on the new node + which owns its keyspace. + If the cluster topology changes, and the owner of its keyspace changes, it will be shifted to the new owner, after initiating the handoff process as described in the documentation. - A track call will return an error tagged tuple, `{:error, :no_node_available}`, if there is no node available to start the process. - Provide a timeout value to limit the track call duration. A value of `:infinity` can be used to block indefinitely. + + A track call will return an error tagged tuple, `{:error, + :no_node_available}`, if there is no node available to start the process. + + Provide a timeout value to limit the track call duration. A value of + `:infinity` can be used to block indefinitely. """ def track(name, m, f, a, timeout) when is_atom(m) and is_atom(f) and is_list(a), do: GenStateMachine.call(__MODULE__, {:track, name, %{mfa: {m, f, a}}}, timeout) @@ -94,7 +105,8 @@ defmodule Swarm.Tracker do do: GenStateMachine.call(__MODULE__, {:untrack, pid}, :infinity) @doc """ - Adds some metadata to the given process (pid). This is primarily used for tracking group membership. + Adds some metadata to the given process (pid). This is primarily used for + tracking group membership. """ def add_meta(key, value, pid) when is_pid(pid), do: GenStateMachine.call(__MODULE__, {:add_meta, key, value, pid}, :infinity) diff --git a/mix.exs b/mix.exs index 5ee05b7..aa8df9d 100644 --- a/mix.exs +++ b/mix.exs @@ -12,35 +12,35 @@ end defmodule Swarm.Mixfile do use Mix.Project + @source_url "https://github.com/bitwalker/swarm" + @version "3.4.0" + def project do [ app: :swarm, - version: "3.4.0", - elixir: "~> 1.3", + version: @version, + elixir: "~> 1.6", elixirc_paths: elixirc_paths(Mix.env()), build_embedded: Mix.env() == :prod, start_permanent: Mix.env() == :prod, - description: - "A fast, multi-master, distributed global process registry, with automatic distribution of worker processes.", package: package(), docs: docs(), deps: deps(), aliases: aliases(), - dialyzer: [ - plt_add_apps: [:inets], - plt_add_deps: :transitive, - flags: ~w(-Wunmatched_returns -Werror_handling -Wrace_conditions -Wunderspecs) - ] + dialyzer: dialyzer() ] end def application do - [extra_applications: [:logger, :crypto], mod: {Swarm, []}] + [ + extra_applications: [:logger, :crypto], + mod: {Swarm, []} + ] end defp deps do [ - {:ex_doc, "~> 0.13", only: :dev}, + {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, {:dialyxir, "~> 0.3", only: :dev}, {:benchee, "~> 0.4", only: :dev}, {:porcelain, "~> 2.0", only: [:dev, :test]}, @@ -51,24 +51,32 @@ defmodule Swarm.Mixfile do defp package do [ - files: ["lib", "src", "mix.exs", "README.md", "LICENSE.md"], + description: """ + A fast, multi-master, distributed global process registry, with + automatic distribution of worker processes. + """, + files: ["lib", "src", "mix.exs", "README.md", "LICENSE.md", "CHANGELOG.md"], maintainers: ["Paul Schoenfelder"], licenses: ["MIT"], - links: %{Github: "https://github.com/bitwalker/swarm"} + links: %{ + Changelog: "https:hexdocs.pm/swarm/changelog.html", + GitHub: @source_url + } ] end defp docs do [ + extras: ["CHANGELOG.md", "README.md"], main: "readme", + source_url: @source_url, + source_ref: @version, formatter_opts: [gfm: true], - extras: [ - "README.md" - ] + formatters: ["html"] ] end - defp aliases() do + defp aliases do if System.get_env("SWARM_TEST_DEBUG") do [test: "test --no-start --trace"] else @@ -76,6 +84,14 @@ defmodule Swarm.Mixfile do end end + defp dialyzer do + [ + plt_add_apps: [:inets], + plt_add_deps: :transitive, + flags: ~w(-Wunmatched_returns -Werror_handling -Wrace_conditions -Wunderspecs) + ] + end + defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(_), do: ["lib"] end diff --git a/mix.lock b/mix.lock index 1d9e9bc..f391beb 100644 --- a/mix.lock +++ b/mix.lock @@ -1,14 +1,16 @@ %{ - "benchee": {:hex, :benchee, "0.13.2", "30cd4ff5f593fdd218a9b26f3c24d580274f297d88ad43383afe525b1543b165", [:mix], [{:deep_merge, "~> 0.1", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"}, - "deep_merge": {:hex, :deep_merge, "0.2.0", "c1050fa2edf4848b9f556fba1b75afc66608a4219659e3311d9c9427b5b680b3", [:mix], [], "hexpm"}, - "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"}, - "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, - "gen_state_machine": {:hex, :gen_state_machine, "2.0.3", "477ea51b466a749ab23a0d6090e9e84073f41f9aa28c7efc40eac18f3d4a9f77", [:mix], [], "hexpm"}, - "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"}, - "makeup": {:hex, :makeup, "0.5.1", "966c5c2296da272d42f1de178c1d135e432662eca795d6dc12e5e8787514edf7", [:mix], [{:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.8.0", "1204a2f5b4f181775a0e456154830524cf2207cf4f9112215c05e0b76e4eca8b", [:mix], [{:makeup, "~> 0.5.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, - "nimble_parsec": {:hex, :nimble_parsec, "0.2.2", "d526b23bdceb04c7ad15b33c57c4526bf5f50aaa70c7c141b4b4624555c68259", [:mix], [], "hexpm"}, + "benchee": {:hex, :benchee, "0.13.2", "30cd4ff5f593fdd218a9b26f3c24d580274f297d88ad43383afe525b1543b165", [:mix], [{:deep_merge, "~> 0.1", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "d8b3f1720073413c36a21e56a1d1112a4d67a9ad0ec900437efed08b39e515b2"}, + "deep_merge": {:hex, :deep_merge, "0.2.0", "c1050fa2edf4848b9f556fba1b75afc66608a4219659e3311d9c9427b5b680b3", [:mix], [], "hexpm", "e3bf435a54ed27b0ba3a01eb117ae017988804e136edcbe8a6a14c310daa966e"}, + "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm", "6c32a70ed5d452c6650916555b1f96c79af5fc4bf286997f8b15f213de786f73"}, + "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm", "b42a23e9bd92d65d16db2f75553982e58519054095356a418bb8320bbacb58b1"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"}, + "ex_doc": {:hex, :ex_doc, "0.24.0", "2df14354835afaabdf87cb2971ea9485d8a36ff590e4b6c250b4f60c8fdf9143", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "a0f4bcff21ceebea48414e49885d2a3e542200f76a2facf3f8faa54935eeb721"}, + "gen_state_machine": {:hex, :gen_state_machine, "2.0.3", "477ea51b466a749ab23a0d6090e9e84073f41f9aa28c7efc40eac18f3d4a9f77", [:mix], [], "hexpm", "0418b2c2b2da3118349fa344497c37e58c46dd8ef865c1ea86e9c1831471fc28"}, + "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm", "1feaf05ee886815ad047cad7ede17d6910710986148ae09cf73eee2989717b81"}, + "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []}, - "porcelain": {:hex, :porcelain, "2.0.3", "2d77b17d1f21fed875b8c5ecba72a01533db2013bd2e5e62c6d286c029150fdc", [:mix], []}, + "porcelain": {:hex, :porcelain, "2.0.3", "2d77b17d1f21fed875b8c5ecba72a01533db2013bd2e5e62c6d286c029150fdc", [:mix], [], "hexpm", "dc996ab8fadbc09912c787c7ab8673065e50ea1a6245177b0c24569013d23620"}, }