Skip to content

Commit 3a0b498

Browse files
author
José Valim
committed
Shut down GenFSM on unknown messages
1 parent b7bbe13 commit 3a0b498

File tree

6 files changed

+123
-78
lines changed

6 files changed

+123
-78
lines changed

lib/elixir/lib/gen_event/behaviour.ex

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ defmodule GenEvent.Behaviour do
2525
def handle_call(:notifications, notifications) do
2626
{:ok, Enum.reverse(notifications), []}
2727
end
28-
2928
end
3029
3130
{ :ok, pid } = :gen_event.start_link

lib/elixir/lib/gen_fsm/behaviour.ex

Lines changed: 54 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -21,78 +21,67 @@ defmodule GenFSM.Behaviour do
2121
works - we hereby declare a full disclaimer for any lawsuits that the
2222
behaviour of the CVM in its original state might encur.
2323
24-
2524
defmodule MyFsm do
2625
use GenFSM.Behaviour
2726
28-
# keeping track of what is going on inside the CVM.
29-
# 3 is the target price for a cup of coffee
30-
defrecord StateData, coins: 0, price: 3
31-
32-
#
33-
# API functions
34-
#
35-
36-
def start_link() do
37-
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
38-
end
39-
40-
def insert_coin() do
41-
:gen_fsm.send_event(:cvm, :coin)
42-
end
43-
44-
def request_coffee() do
45-
:gen_fsm.send_event(:cvm, :request_coffee)
46-
end
27+
# keeping track of what is going on inside the CVM.
28+
# 3 is the target price for a cup of coffee
29+
defrecord StateData, coins: 0, price: 3
4730
48-
#
49-
# Callbacks
50-
#
31+
# API functions
5132
52-
def init(_args) do
53-
{ :ok, :short_paid, StateData.new }
54-
end
33+
def start_link() do
34+
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
35+
end
5536
37+
def insert_coin() do
38+
:gen_fsm.send_event(:cvm, :coin)
39+
end
5640
57-
def short_paid(:coin, state_data = StateData[coins: c, price: p])
58-
when c + 1 < p do
59-
{ :next_state, :short_paid, state_data.coins(c + 1) }
60-
end
41+
def request_coffee() do
42+
:gen_fsm.send_event(:cvm, :request_coffee)
43+
end
6144
62-
def short_paid(:coin, state_data) do
63-
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
64-
end
45+
# Callbacks
6546
66-
def short_paid(:request_coffee, state_data) do
67-
{ :next_state, :requested_short_paid, state_data }
68-
end
47+
def init(_args) do
48+
{ :ok, :short_paid, StateData.new }
49+
end
6950
51+
def short_paid(:coin, state_data = StateData[coins: c, price: p]) when c + 1 < p do
52+
{ :next_state, :short_paid, state_data.coins(c + 1) }
53+
end
7054
71-
def requested_short_paid(:request_coffee, state_data) do
72-
{:next_state, :requested_short_paid, state_data }
73-
end
55+
def short_paid(:coin, state_data) do
56+
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
57+
end
7458
75-
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p])
76-
when c+1 < p do
77-
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
78-
end
59+
def short_paid(:request_coffee, state_data) do
60+
{ :next_state, :requested_short_paid, state_data }
61+
end
7962
80-
def requested_short_paid(:coin, _state_data) do
81-
IO.puts "Here's your coffee!"
82-
{ :next_state, :short_paid, StateData.new }
83-
end
63+
def requested_short_paid(:request_coffee, state_data) do
64+
{ :next_state, :requested_short_paid, state_data }
65+
end
8466
67+
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p]) when c+1 < p do
68+
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
69+
end
8570
86-
def paid_in_full(:coin, state_data) do
87-
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
88-
end
71+
def requested_short_paid(:coin, _state_data) do
72+
IO.puts "Here's your coffee!"
73+
{ :next_state, :short_paid, StateData.new }
74+
end
8975
90-
def paid_in_full(:request_coffee, _state_data) do
91-
IO.puts "Here's your coffee!"
92-
{ :next_state, :short_paid, StateData.new }
93-
end
76+
def paid_in_full(:coin, state_data) do
77+
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
9478
end
9579
80+
def paid_in_full(:request_coffee, _state_data) do
81+
IO.puts "Here's your coffee!"
82+
{ :next_state, :short_paid, StateData.new }
83+
end
84+
end
9685
9786
{ :ok, _pid } = MyFsm.start_link()
9887
@@ -138,25 +127,25 @@ defmodule GenFSM.Behaviour do
138127
for you. The list of callbacks are:
139128
140129
* `init(args)` - invoked when the FSM is started;
141-
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
130+
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
142131
handle `sync_send_all_state_event` messages;
143-
* `handle_event(event, state_name, state_data)` - invoked to handle
132+
* `handle_event(event, state_name, state_data)` - invoked to handle
144133
`send_all_state_event` messages;
145-
* `handle_info(msg, state_name, state_data)` - handle all other
134+
* `handle_info(msg, state_name, state_data)` - handle all other
146135
messages which are normally received by processes;
147-
* `terminate(reason, state_name, state_data)` - called when the FSM
136+
* `terminate(reason, state_name, state_data)` - called when the FSM
148137
is about to terminate, useful for cleaning up;
149-
* `code_change(old_vsn, state, extra)` - called when the application
138+
* `code_change(old_vsn, state, extra)` - called when the application
150139
code is being upgraded live (hot code swap);
151140
152141
Unlike `GenServer` and `GenEvent`, the callback `init/1` is not
153142
implemented by default, as it requires the next state to be returned.
154143
155144
For each state you need to define either or both of these:
156145
157-
* `state_name(event, state_data)` - invoked to handle
146+
* `state_name(event, state_data)` - invoked to handle
158147
`send_event` messages;
159-
* `state_name(event, from, state_data)`- invoked to handle
148+
* `state_name(event, from, state_data)`- invoked to handle
160149
`sync_send_event` messages;
161150
162151
If you send asynchronous events you only need to implement the
@@ -180,13 +169,13 @@ defmodule GenFSM.Behaviour do
180169
@behavior :gen_fsm
181170

182171
@doc false
183-
def handle_event(_event, state_name, state_data) do
184-
{ :next_state, state_name, state_data }
172+
def handle_event(event, state_name, state_data) do
173+
{ :stop, {:bad_event, state_name, event}, state_data }
185174
end
186175

187176
@doc false
188-
def handle_sync_event(_event, from, state_name, state_data) do
189-
{ :reply, :ok, state_name, state_data }
177+
def handle_sync_event(event, from, state_name, state_data) do
178+
{ :stop, {:bad_sync_event, state_name, event}, state_data }
190179
end
191180

192181
@doc false

lib/elixir/lib/gen_server/behaviour.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ defmodule GenServer.Behaviour do
105105

106106
@doc false
107107
def handle_cast(msg, state) do
108-
{ :stop, { :bad_call, msg }, state }
108+
{ :stop, { :bad_cast, msg }, state }
109109
end
110110

111111
@doc false

lib/elixir/test/elixir/gen_event/behaviour_test.exs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@ defmodule GenEvent.BehaviourTest do
1919
def handle_call(:notifications, notifications) do
2020
{:ok, Enum.reverse(notifications), []}
2121
end
22-
2322
end
2423

25-
test :using do
24+
test "using defines callbacks" do
2625
{ :ok, pid } = :gen_event.start_link
2726
:gen_event.add_handler(pid, MyEventHandler, [])
2827

@@ -32,5 +31,4 @@ defmodule GenEvent.BehaviourTest do
3231
assert :gen_event.call(pid, MyEventHandler, :notifications) == [1, 2]
3332
assert :gen_event.call(pid, MyEventHandler, :notifications) == []
3433
end
35-
3634
end
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Code.require_file "../test_helper.exs", __DIR__
2+
3+
defmodule GenFSM.BehaviourTest do
4+
use ExUnit.Case
5+
6+
setup_all do
7+
:error_logger.tty(false)
8+
:ok
9+
end
10+
11+
teardown_all do
12+
:error_logger.tty(true)
13+
:ok
14+
end
15+
16+
defmodule Sample do
17+
use GenFSM.Behaviour
18+
19+
def init(args) do
20+
{ :ok, :sample, args }
21+
end
22+
end
23+
24+
test "sync event stops server on unknown requests" do
25+
Process.flag(:trap_exit, true)
26+
assert { :ok, pid } = :gen_fsm.start_link(Sample, [:hello], [])
27+
28+
catch_exit(:gen_fsm.sync_send_all_state_event(pid, :unknown_request))
29+
assert_receive { :EXIT, ^pid, {:bad_sync_event, :sample, :unknown_request} }
30+
after
31+
Process.flag(:trap_exit, false)
32+
end
33+
34+
test "event stops server on unknown requests" do
35+
Process.flag(:trap_exit, true)
36+
assert { :ok, pid } = :gen_fsm.start_link(Sample, [:hello], [])
37+
38+
:gen_fsm.send_all_state_event(pid, :unknown_request)
39+
assert_receive { :EXIT, ^pid, {:bad_event, :sample, :unknown_request} }
40+
after
41+
Process.flag(:trap_exit, false)
42+
end
43+
end

lib/elixir/test/elixir/gen_server/behaviour_test.exs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
Code.require_file "../test_helper.exs", __DIR__
22

33
defmodule GenServer.BehaviourTest do
4-
use ExUnit.Case, async: true
4+
use ExUnit.Case
5+
6+
setup_all do
7+
:error_logger.tty(false)
8+
:ok
9+
end
10+
11+
teardown_all do
12+
:error_logger.tty(true)
13+
:ok
14+
end
515

616
defmodule Sample do
717
use GenServer.Behaviour
@@ -29,24 +39,30 @@ defmodule GenServer.BehaviourTest do
2939
end
3040
end
3141

32-
test :using do
42+
test "using defines callbacks" do
3343
assert { :ok, pid } = :gen_server.start_link(Sample, [:hello], [])
3444
assert :gen_server.call(pid, :pop) == :hello
3545
assert :gen_server.cast(pid, { :push, :world }) == :ok
3646
assert :gen_server.call(pid, :pop) == :world
3747
end
3848

3949
test "call stops server on unknown requests" do
50+
Process.flag(:trap_exit, true)
4051
assert { :ok, pid } = :gen_server.start_link(Sample, [:hello], [])
41-
Process.unlink(pid)
42-
assert {{:bad_call, :unknown_request}, _} = catch_exit(:gen_server.call(pid, :unknown_request))
52+
53+
catch_exit(:gen_server.call(pid, :unknown_request))
54+
assert_receive { :EXIT, ^pid, {:bad_call, :unknown_request} }
55+
after
56+
Process.flag(:trap_exit, false)
4357
end
4458

4559
test "cast stops server on unknown requests" do
60+
Process.flag(:trap_exit, true)
4661
assert { :ok, pid } = :gen_server.start_link(Sample, [:hello], [])
47-
Process.unlink(pid)
48-
# Won't notice the server is stopped till we next send it a (valid) message
49-
assert :gen_server.cast(pid, :unknown_request) == :ok
50-
assert {{:bad_call, :unknown_request}, _} = catch_exit(:gen_server.call(pid, :pop))
62+
63+
:gen_server.cast(pid, :unknown_request)
64+
assert_receive { :EXIT, ^pid, {:bad_cast, :unknown_request} }
65+
after
66+
Process.flag(:trap_exit, false)
5167
end
5268
end

0 commit comments

Comments
 (0)