lecture 05
This commit is contained in:
19 files changed
+439
No files matched your search
@@ -0,0 +1,4 @@
|
||||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where third-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
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
dynamic_counters-*.tar
|
||||
|
||||
# Temporary files, for example, from tests.
|
||||
/tmp/
|
||||
@@ -0,0 +1,21 @@
|
||||
# DynamicCounters
|
||||
|
||||
**TODO: Add description**
|
||||
|
||||
## Installation
|
||||
|
||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||
by adding `dynamic_counters` to your list of dependencies in `mix.exs`:
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:dynamic_counters, "~> 0.1.0"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||
be found at <https://hexdocs.pm/dynamic_counters>.
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
defmodule Counter.Application do
|
||||
# See https://hexdocs.pm/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@moduledoc false
|
||||
|
||||
@registry Counter.Registry
|
||||
@table Counter.Table
|
||||
|
||||
use Application
|
||||
|
||||
@impl true
|
||||
def start(_type, _args) do
|
||||
children = [
|
||||
{Registry, name: @registry, keys: :unique}, # recall same as {Registry, [name: @registry, keys: :unique]}
|
||||
# Starts a worker by calling: Counter.Worker.start_link(arg)
|
||||
# {DynamicSupervisor, name: Counter.DynamicSupervisor, strategy: :one_for_one}
|
||||
Counter.DynamicSupervisor # no argument passed so we can just use this
|
||||
]
|
||||
|
||||
:ets.new(@table, [:named_table, :public])
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: Counter.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
defmodule Counter.DynamicSupervisor do
|
||||
use DynamicSupervisor
|
||||
|
||||
def start_link(_) do
|
||||
DynamicSupervisor.start_link(__MODULE__, nil, name: __MODULE__)
|
||||
end
|
||||
|
||||
def start_worker(name) do
|
||||
DynamicSupervisor.start_child(__MODULE__, {Counter.Worker, name})
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(_) do
|
||||
DynamicSupervisor.init(strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
defmodule Counter.Worker do
|
||||
use GenServer
|
||||
|
||||
@registry Counter.Registry
|
||||
@table Counter.Table
|
||||
|
||||
### Client API ###
|
||||
def start_link(name) do
|
||||
GenServer.start_link(__MODULE__, name, name: via(name))
|
||||
end
|
||||
|
||||
def value(name) do
|
||||
GenServer.call(via(name), :value)
|
||||
end
|
||||
|
||||
def inc(name, amt \\ 1) do
|
||||
GenServer.cast(via(name), {:inc, amt})
|
||||
end
|
||||
|
||||
# wrap the name in a triple to use the Registry to get the
|
||||
# process ID by name
|
||||
defp via(name) do
|
||||
{:via, Registry, {@registry, {__MODULE__, name}}}
|
||||
end
|
||||
|
||||
### Server Impl ###
|
||||
@impl true
|
||||
def init(arg) do
|
||||
name = {__MODULE__, arg}
|
||||
value = case :ets.lookup(@table, name) do
|
||||
[] -> 0
|
||||
[{_, v}] -> v
|
||||
end
|
||||
{:ok, {name, value}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:value, _from, {_, value} = state) do
|
||||
{:reply, value, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:inc, amt}, {name, value}) do
|
||||
{:noreply, {name, value + amt}}
|
||||
end
|
||||
|
||||
# save state to term table when process terminates
|
||||
@impl true
|
||||
def terminate(_, state) do
|
||||
:ets.insert(@table, state)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
defmodule Counter.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :dynamic_counters,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.17",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger],
|
||||
mod: {Counter.Application, []}
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||
]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
defmodule DynamicCountersTest do
|
||||
use ExUnit.Case
|
||||
doctest DynamicCounters
|
||||
|
||||
test "greets the world" do
|
||||
assert DynamicCounters.hello() == :world
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
ExUnit.start()
|
||||
@@ -0,0 +1,71 @@
|
||||
# register a process that can store key, value pairs
|
||||
Registry.start_link(name: R, keys: :duplicate)
|
||||
|
||||
# register(register_name, key, value)
|
||||
Register.register(R, "hello", "world")
|
||||
Register.lookup(R, "hello")
|
||||
|
||||
# here we can associate a name, with a process ID
|
||||
# can allow us to dynamically start any number of Workers
|
||||
Registry.register(R, {Counter.Worker, "c1"})
|
||||
|
||||
f = fn (k, v) -> spawn(fn -> Registry.register(R, k, v); Process.sleep(30_000) end) end
|
||||
pid = f.(:abc, 1)
|
||||
Registry.keys(R, pid) # get all the keys for a PID -> [:abc]
|
||||
Process.alive?(pid) # once dead i.e. returns -> false then:
|
||||
Registry.keys(R, pid) # the process ID will be gone, -> [] not [:abc]
|
||||
|
||||
|
||||
# Using our dynamic_counters implementation we have a counter server
|
||||
# that is fault tolerant, i.e. if a worker dies, they will restart
|
||||
DynamicSupervisor.start_child(Counter.DynamicSupervisor, {Counter.Worker, "c1"})
|
||||
# -> {:ok, #PID<0.147.0>}
|
||||
{_, pid} = v
|
||||
# -> {:ok, #PID<0.147.0>}
|
||||
Counter.Worker.inc("c1")
|
||||
# -> :ok
|
||||
Counter.Worker.inc("c1")
|
||||
# -> :ok
|
||||
Counter.Worker.value("c1")
|
||||
# -> 2
|
||||
|
||||
# we can crash and see it restarts
|
||||
Counter.Worker.inc("c1", "hello")
|
||||
# -> :ok
|
||||
|
||||
# 10:04:25.932 [error] GenServer {Counter.Registry, {Counter.Worker, "c1"}} terminating
|
||||
# ** (ArithmeticError) bad argument in arithmetic expression
|
||||
# :erlang.+(2, "hello")
|
||||
# (dynamic_counters 0.1.0) lib/dynamic_counters/worker.ex:37: Counter.Worker.handle_cast/2
|
||||
# (stdlib 6.2.2.5) gen_server.erl:2371: :gen_server.try_handle_cast/3
|
||||
# (stdlib 6.2.2.5) gen_server.erl:2433: :gen_server.handle_msg/6
|
||||
# (stdlib 6.2.2.5) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
|
||||
# Last message: {:"$gen_cast", {:inc, "hello"}}
|
||||
# State: 2
|
||||
Counter.Worker.value("c1")
|
||||
# -> 0 has restarted without retaining state
|
||||
Enum.each(1..100_000, fn n -> DynamicSupervisor.start_child(Counter.DynamicSupervisor, {Counter.Worker, "d#{n}"}) end )
|
||||
# -> :ok
|
||||
Enum.each(1..100_000, fn n -> Counter.Worker.inc("d#{n}", n) end)
|
||||
# -> :ok
|
||||
Enum.map(1..10, fn n -> Counter.Worker.value("d#{n}") end)
|
||||
# -> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
|
||||
|
||||
### Erlang Term Store
|
||||
t = :ets.new(T, [])
|
||||
# you can insert tuples of any number of elements, but typically just a key, value pair
|
||||
# by default you cannot have duplicate keys
|
||||
:ets.insert(t, {:a, 1})
|
||||
:ets.lookup(t, :a) # -> [a: 1] lookup only matches the first element of a tuple
|
||||
:ets.insert(t, {"hello", 2, :abc})
|
||||
|
||||
:ets.tab2list(t) # -> [{a: 2}, {"hello, 2, :abc}]
|
||||
:ets.insert(t, {"world", 3, :xyz})
|
||||
:ets.match_object(t, {"hello", :_, :_}) # -> [{"hello", 2, :abc}]
|
||||
|
||||
:ets.new(T, [:named_table, :public]) # can also use a named table and not need to assign to something like t
|
||||
:ets.insert(T, {:abc, 123})
|
||||
:ets.lookup(T, :abc);
|
||||
|
||||
System.schedulers_online()
|
||||
@@ -0,0 +1,4 @@
|
||||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where third-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
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
partition_counters-*.tar
|
||||
|
||||
# Temporary files, for example, from tests.
|
||||
/tmp/
|
||||
@@ -0,0 +1,21 @@
|
||||
# Counter
|
||||
|
||||
**TODO: Add description**
|
||||
|
||||
## Installation
|
||||
|
||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||
by adding `partition_counters` to your list of dependencies in `mix.exs`:
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:partition_counters, "~> 0.1.0"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||
be found at <https://hexdocs.pm/partition_counters>.
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
defmodule Counter.Application do
|
||||
# See https://hexdocs.pm/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@moduledoc false
|
||||
|
||||
@registry Counter.Registry
|
||||
@table Counter.Table
|
||||
|
||||
use Application
|
||||
|
||||
@impl true
|
||||
def start(_type, _args) do
|
||||
children = [
|
||||
{Registry, name: @registry, keys: :unique}, # recall same as {Registry, [name: @registry, keys: :unique]}
|
||||
# Starts a worker by calling: Counter.Worker.start_link(arg)
|
||||
# {DynamicSupervisor, name: Counter.DynamicSupervisor, strategy: :one_for_one}
|
||||
{PartitionSupervisor, name: Counter.DynamicSupervisors, child_spec: Counter.DynamicSupervisor}
|
||||
]
|
||||
|
||||
:ets.new(@table, [:named_table, :public])
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: Counter.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
defmodule Counter.DynamicSupervisor do
|
||||
use DynamicSupervisor
|
||||
|
||||
def start_link(_) do
|
||||
DynamicSupervisor.start_link(__MODULE__, nil)
|
||||
end
|
||||
|
||||
def start_worker(name) do
|
||||
DynamicSupervisor.start_child(
|
||||
{:via, PartitionSupervisor, {Counter.DynamicSupervisors, self()}},
|
||||
{Counter.Worker, name})
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(_) do
|
||||
DynamicSupervisor.init(strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
defmodule Counter.Worker do
|
||||
use GenServer
|
||||
|
||||
@registry Counter.Registry
|
||||
@table Counter.Table
|
||||
|
||||
### Client API ###
|
||||
def start_link(name) do
|
||||
GenServer.start_link(__MODULE__, name, name: via(name))
|
||||
end
|
||||
|
||||
def value(name) do
|
||||
GenServer.call(via(name), :value)
|
||||
end
|
||||
|
||||
def inc(name, amt \\ 1) do
|
||||
GenServer.cast(via(name), {:inc, amt})
|
||||
end
|
||||
|
||||
# wrap the name in a triple to use the Registry to get the
|
||||
# process ID by name
|
||||
defp via(name) do
|
||||
{:via, Registry, {@registry, {__MODULE__, name}}}
|
||||
end
|
||||
|
||||
### Server Impl ###
|
||||
@impl true
|
||||
def init(arg) do
|
||||
name = {__MODULE__, arg}
|
||||
value = case :ets.lookup(@table, name) do
|
||||
[] -> 0
|
||||
[{_, v}] -> v
|
||||
end
|
||||
{:ok, {name, value}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:value, _from, {_, value} = state) do
|
||||
{:reply, value, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:inc, amt}, {name, value}) do
|
||||
{:noreply, {name, value + amt}}
|
||||
end
|
||||
|
||||
# save state to term table when process terminates
|
||||
@impl true
|
||||
def terminate(_, state) do
|
||||
:ets.insert(@table, state)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
defmodule Counter.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :partition_counters,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.17",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger],
|
||||
mod: {Counter.Application, []}
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||
]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
defmodule CounterTest do
|
||||
use ExUnit.Case
|
||||
doctest Counter
|
||||
|
||||
test "greets the world" do
|
||||
assert Counter.hello() == :world
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
ExUnit.start()
|
||||
Reference in new issue
Block a user