Shut down GenFSM on unknown messages

This commit is contained in:
José Valim
2013-12-31 18:35:22 +01:00
parent b7bbe13c91
commit 3a0b4988fc
6 changed files with 134 additions and 89 deletions
-1
View File
@@ -25,7 +25,6 @@ defmodule GenEvent.Behaviour do
def handle_call(:notifications, notifications) do
{:ok, Enum.reverse(notifications), []}
end
end
{ :ok, pid } = :gen_event.start_link
+65 -76
View File
@@ -21,78 +21,67 @@ defmodule GenFSM.Behaviour do
works - we hereby declare a full disclaimer for any lawsuits that the
behaviour of the CVM in its original state might encur.
defmodule MyFsm do
use GenFSM.Behaviour
# keeping track of what is going on inside the CVM.
# 3 is the target price for a cup of coffee
defrecord StateData, coins: 0, price: 3
# keeping track of what is going on inside the CVM.
# 3 is the target price for a cup of coffee
defrecord StateData, coins: 0, price: 3
#
# API functions
#
# API functions
def start_link() do
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
end
def insert_coin() do
:gen_fsm.send_event(:cvm, :coin)
end
def request_coffee() do
:gen_fsm.send_event(:cvm, :request_coffee)
end
#
# Callbacks
#
def init(_args) do
{ :ok, :short_paid, StateData.new }
end
def short_paid(:coin, state_data = StateData[coins: c, price: p])
when c + 1 < p do
{ :next_state, :short_paid, state_data.coins(c + 1) }
end
def short_paid(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def short_paid(:request_coffee, state_data) do
{ :next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:request_coffee, state_data) do
{:next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p])
when c+1 < p do
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
end
def requested_short_paid(:coin, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
def paid_in_full(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def paid_in_full(:request_coffee, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
def start_link() do
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
end
def insert_coin() do
:gen_fsm.send_event(:cvm, :coin)
end
def request_coffee() do
:gen_fsm.send_event(:cvm, :request_coffee)
end
# Callbacks
def init(_args) do
{ :ok, :short_paid, StateData.new }
end
def short_paid(:coin, state_data = StateData[coins: c, price: p]) when c + 1 < p do
{ :next_state, :short_paid, state_data.coins(c + 1) }
end
def short_paid(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def short_paid(:request_coffee, state_data) do
{ :next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:request_coffee, state_data) do
{ :next_state, :requested_short_paid, state_data }
end
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p]) when c+1 < p do
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
end
def requested_short_paid(:coin, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
def paid_in_full(:coin, state_data) do
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
end
def paid_in_full(:request_coffee, _state_data) do
IO.puts "Here's your coffee!"
{ :next_state, :short_paid, StateData.new }
end
end
{ :ok, _pid } = MyFsm.start_link()
@@ -138,15 +127,15 @@ defmodule GenFSM.Behaviour do
for you. The list of callbacks are:
* `init(args)` - invoked when the FSM is started;
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
handle `sync_send_all_state_event` messages;
* `handle_event(event, state_name, state_data)` - invoked to handle
* `handle_event(event, state_name, state_data)` - invoked to handle
`send_all_state_event` messages;
* `handle_info(msg, state_name, state_data)` - handle all other
* `handle_info(msg, state_name, state_data)` - handle all other
messages which are normally received by processes;
* `terminate(reason, state_name, state_data)` - called when the FSM
* `terminate(reason, state_name, state_data)` - called when the FSM
is about to terminate, useful for cleaning up;
* `code_change(old_vsn, state, extra)` - called when the application
* `code_change(old_vsn, state, extra)` - called when the application
code is being upgraded live (hot code swap);
Unlike `GenServer` and `GenEvent`, the callback `init/1` is not
@@ -154,9 +143,9 @@ defmodule GenFSM.Behaviour do
For each state you need to define either or both of these:
* `state_name(event, state_data)` - invoked to handle
* `state_name(event, state_data)` - invoked to handle
`send_event` messages;
* `state_name(event, from, state_data)`- invoked to handle
* `state_name(event, from, state_data)`- invoked to handle
`sync_send_event` messages;
If you send asynchronous events you only need to implement the
@@ -180,13 +169,13 @@ defmodule GenFSM.Behaviour do
@behavior :gen_fsm
@doc false
def handle_event(_event, state_name, state_data) do
{ :next_state, state_name, state_data }
def handle_event(event, state_name, state_data) do
{ :stop, {:bad_event, state_name, event}, state_data }
end
@doc false
def handle_sync_event(_event, from, state_name, state_data) do
{ :reply, :ok, state_name, state_data }
def handle_sync_event(event, from, state_name, state_data) do
{ :stop, {:bad_sync_event, state_name, event}, state_data }
end
@doc false
+1 -1
View File
@@ -105,7 +105,7 @@ defmodule GenServer.Behaviour do
@doc false
def handle_cast(msg, state) do
{ :stop, { :bad_call, msg }, state }
{ :stop, { :bad_cast, msg }, state }
end
@doc false
@@ -19,10 +19,9 @@ defmodule GenEvent.BehaviourTest do
def handle_call(:notifications, notifications) do
{:ok, Enum.reverse(notifications), []}
end
end
test :using do
test "using defines callbacks" do
{ :ok, pid } = :gen_event.start_link
:gen_event.add_handler(pid, MyEventHandler, [])
@@ -32,5 +31,4 @@ defmodule GenEvent.BehaviourTest do
assert :gen_event.call(pid, MyEventHandler, :notifications) == [1, 2]
assert :gen_event.call(pid, MyEventHandler, :notifications) == []
end
end
@@ -0,0 +1,43 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule GenFSM.BehaviourTest do
use ExUnit.Case
setup_all do
:error_logger.tty(false)
:ok
end
teardown_all do
:error_logger.tty(true)
:ok
end
defmodule Sample do
use GenFSM.Behaviour
def init(args) do
{ :ok, :sample, args }
end
end
test "sync event stops server on unknown requests" do
Process.flag(:trap_exit, true)
assert { :ok, pid } = :gen_fsm.start_link(Sample, [:hello], [])
catch_exit(:gen_fsm.sync_send_all_state_event(pid, :unknown_request))
assert_receive { :EXIT, ^pid, {:bad_sync_event, :sample, :unknown_request} }
after
Process.flag(:trap_exit, false)
end
test "event stops server on unknown requests" do
Process.flag(:trap_exit, true)
assert { :ok, pid } = :gen_fsm.start_link(Sample, [:hello], [])
:gen_fsm.send_all_state_event(pid, :unknown_request)
assert_receive { :EXIT, ^pid, {:bad_event, :sample, :unknown_request} }
after
Process.flag(:trap_exit, false)
end
end
@@ -1,7 +1,17 @@
Code.require_file "../test_helper.exs", __DIR__
defmodule GenServer.BehaviourTest do
use ExUnit.Case, async: true
use ExUnit.Case
setup_all do
:error_logger.tty(false)
:ok
end
teardown_all do
:error_logger.tty(true)
:ok
end
defmodule Sample do
use GenServer.Behaviour
@@ -29,7 +39,7 @@ defmodule GenServer.BehaviourTest do
end
end
test :using do
test "using defines callbacks" do
assert { :ok, pid } = :gen_server.start_link(Sample, [:hello], [])
assert :gen_server.call(pid, :pop) == :hello
assert :gen_server.cast(pid, { :push, :world }) == :ok
@@ -37,16 +47,22 @@ defmodule GenServer.BehaviourTest do
end
test "call stops server on unknown requests" do
Process.flag(:trap_exit, true)
assert { :ok, pid } = :gen_server.start_link(Sample, [:hello], [])
Process.unlink(pid)
assert {{:bad_call, :unknown_request}, _} = catch_exit(:gen_server.call(pid, :unknown_request))
catch_exit(:gen_server.call(pid, :unknown_request))
assert_receive { :EXIT, ^pid, {:bad_call, :unknown_request} }
after
Process.flag(:trap_exit, false)
end
test "cast stops server on unknown requests" do
Process.flag(:trap_exit, true)
assert { :ok, pid } = :gen_server.start_link(Sample, [:hello], [])
Process.unlink(pid)
# Won't notice the server is stopped till we next send it a (valid) message
assert :gen_server.cast(pid, :unknown_request) == :ok
assert {{:bad_call, :unknown_request}, _} = catch_exit(:gen_server.call(pid, :pop))
:gen_server.cast(pid, :unknown_request)
assert_receive { :EXIT, ^pid, {:bad_cast, :unknown_request} }
after
Process.flag(:trap_exit, false)
end
end