Skip to content

Commit cdb9e36

Browse files
authored
fix: proper supervision cleanup (#35)
* fix: proper supervision cleanup WIP * fix: everything now supervised under :kadabra app Was the original setup a while back. Supervisor was quitting with :shutdown during cleanup, which would shutdown the process that originally start_link'd it. * fix: timeout on stream WindowUpdate Replies immediately so connection can continue on with its life. Call timeout was previously crashing connection on occasion. * refactor: minor refactoring * feat: CONTINUATION for sending large headers WIP Chunks correctly, but remotes are still terminating with a COMPRESSION_ERROR. Possible ideas why: 1. Hpack is encoding the fragment wrong 2. Frames are being sent out of order 3. Continuation frames are being serialized wrong 4. Remotes aren't equipped to handle such large headers 5. Sending a 20mb header is just a shitty thing to do * fix: don't crash Connection if Socket closed Fixed by not shutting down Kadabra.Socket if the socket closes. Bins sent to a closed socket are simply ignored. Sending CONTINUATION frames is mostly there. Fairly certain the issue is with hpack, so I'll need to figure something out. * chore: bump version and update changelog * test: increase test coverage
1 parent 322ccb3 commit cdb9e36

22 files changed

+411
-197
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## v0.4.2
4+
- Fixed `{:closed, pid}` task race condition during connection cleanup
5+
- Everything is supervised under `Kadabra.Application` again, instead of
6+
handling supervision yourself
7+
38
## v0.4.1
49
- Send exactly number of allowed bytes on initial connection WINDOW_UPDATE
510
- Default settings use maximum values for MAX_FRAME_SIZE and INITIAL_WINDOW_SIZE

lib/application.ex

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,40 @@ defmodule Kadabra.Application do
44
use Application
55
import Supervisor.Spec
66

7+
alias Kadabra.Connection
8+
9+
@app :kadabra
10+
711
def start(_type, _args) do
812
children = [
913
supervisor(Registry, [:unique, Registry.Kadabra]),
1014
supervisor(Task.Supervisor, [[name: Kadabra.Tasks]])
1115
]
1216

13-
Supervisor.start_link(children, strategy: :one_for_one, name: :kadabra)
17+
Supervisor.start_link(children, strategy: :one_for_one, name: @app)
18+
end
19+
20+
def start_connection(uri, pid, opts) do
21+
Supervisor.start_child(
22+
@app,
23+
supervisor(Kadabra.Supervisor, [uri, pid, opts], spec_opts())
24+
)
25+
end
26+
27+
defp spec_opts do
28+
ref = :erlang.make_ref()
29+
[id: ref, restart: :transient]
30+
end
31+
32+
def ping(pid) do
33+
pid
34+
|> Connection.via_tuple()
35+
|> Connection.ping()
36+
end
37+
38+
def close(pid) do
39+
pid
40+
|> Connection.via_tuple()
41+
|> Connection.close()
1442
end
1543
end

lib/connection.ex

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ defmodule Kadabra.Connection do
2121
Error,
2222
Frame,
2323
Socket,
24-
Tasks
24+
StreamSupervisor
2525
}
2626

2727
alias Kadabra.Frame.{Goaway, Ping}
@@ -51,6 +51,7 @@ defmodule Kadabra.Connection do
5151
state = initial_state(config)
5252
Kernel.send(self(), :start)
5353
Process.flag(:trap_exit, true)
54+
5455
{:consumer, state, subscribe_to: [queue]}
5556
end
5657

@@ -101,13 +102,12 @@ defmodule Kadabra.Connection do
101102
config: config
102103
} = state
103104

105+
StreamSupervisor.stop(state.config.ref)
106+
104107
bin = flow.stream_set.stream_id |> Goaway.new() |> Encodable.to_bin()
105108
Socket.send(config.socket, bin)
106109

107-
Kernel.send(config.client, {:closed, config.supervisor})
108-
Tasks.run(fn -> Kadabra.Supervisor.stop(config.supervisor) end)
109-
110-
{:reply, :ok, [], state}
110+
{:stop, :shutdown, :ok, state}
111111
end
112112

113113
# sendf
@@ -148,19 +148,16 @@ defmodule Kadabra.Connection do
148148
{:noreply, [], state}
149149
end
150150

151-
def handle_info({:closed, _pid}, %{config: config} = state) do
152-
Kernel.send(config.client, {:closed, config.supervisor})
153-
Tasks.run(fn -> Kadabra.Supervisor.stop(config.supervisor) end)
154-
155-
{:noreply, [], state}
151+
def handle_info({:closed, _pid}, state) do
152+
{:stop, :shutdown, state}
156153
end
157154

158-
def handle_info({:EXIT, _pid, {:shutdown, {:finished, stream_id}}}, state) do
155+
def handle_info({:DOWN, _, _, _pid, {:shutdown, {:finished, sid}}}, state) do
159156
GenStage.ask(state.queue, 1)
160157

161158
flow =
162159
state.flow_control
163-
|> FlowControl.finish_stream(stream_id)
160+
|> FlowControl.finish_stream(sid)
164161
|> FlowControl.process(state.config)
165162

166163
{:noreply, [], %{state | flow_control: flow}}
@@ -178,28 +175,23 @@ defmodule Kadabra.Connection do
178175

179176
{:connection_error, error, reason, state} ->
180177
handle_connection_error(state, error, reason)
181-
{:noreply, [], state}
178+
{:stop, {:shutdown, :connection_error}, state}
182179
end
183180
end
184181

185182
defp handle_connection_error(%{config: config} = state, error, reason) do
186-
code = Error.code(error)
183+
code = <<Error.code(error)::32>>
187184

188185
bin =
189186
state.flow_control.stream_set.stream_id
190187
|> Goaway.new(code, reason)
191188
|> Encodable.to_bin()
192189

193190
Socket.send(config.socket, bin)
194-
Tasks.run(fn -> Kadabra.Supervisor.stop(config.supervisor) end)
195191
end
196192

197-
def send_goaway(%{config: config, flow_control: flow}, error) do
198-
bin =
199-
flow.stream_set.stream_id
200-
|> Frame.Goaway.new(Error.code(error))
201-
|> Encodable.to_bin()
202-
203-
Socket.send(config.socket, bin)
193+
def terminate(_reason, %{config: config}) do
194+
Kernel.send(config.client, {:closed, config.supervisor})
195+
:ok
204196
end
205197
end

lib/connection/flow_control.ex

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ defmodule Kadabra.Connection.FlowControl do
1313
max_frame_size: @default_max_frame_size,
1414
window: @default_window_size
1515

16-
alias Kadabra.{Config, Stream, StreamSet}
16+
alias Kadabra.{Config, StreamSet, StreamSupervisor}
1717

1818
@type t :: %__MODULE__{
1919
queue: :queue.queue(),
@@ -86,8 +86,8 @@ defmodule Kadabra.Connection.FlowControl do
8686
%{flow_control | queue: queue}
8787
end
8888

89-
def add_active(%{stream_set: set} = flow_control, stream_id, pid) do
90-
new_set = StreamSet.add_active(set, stream_id, pid)
89+
def add_active(%{stream_set: set} = flow_control, stream_id) do
90+
new_set = StreamSet.add_active(set, stream_id)
9191
%{flow_control | stream_set: new_set}
9292
end
9393

@@ -102,14 +102,14 @@ defmodule Kadabra.Connection.FlowControl do
102102
max_frame_size: max_frame
103103
} = flow
104104

105-
stream = Stream.new(config, stream_id, window, max_frame)
106-
107-
case Stream.start_link(stream) do
105+
case StreamSupervisor.start_stream(config, stream_id, window, max_frame) do
108106
{:ok, pid} ->
107+
Process.monitor(pid)
108+
109109
size = byte_size(request.body || <<>>)
110110
:gen_statem.call(pid, {:send_headers, request})
111111

112-
updated_set = add_stream(stream_set, stream_id, pid)
112+
updated_set = add_stream(stream_set, stream_id)
113113

114114
flow
115115
|> Map.put(:queue, queue)
@@ -127,9 +127,9 @@ defmodule Kadabra.Connection.FlowControl do
127127
end
128128
end
129129

130-
defp add_stream(stream_set, stream_id, pid) do
130+
defp add_stream(stream_set, stream_id) do
131131
stream_set
132-
|> StreamSet.add_active(stream_id, pid)
132+
|> StreamSet.add_active(stream_id)
133133
|> StreamSet.increment_active_stream_count()
134134
|> StreamSet.increment_stream_id()
135135
end

lib/connection/processor.ex

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule Kadabra.Connection.Processor do
1010
Frame,
1111
Hpack,
1212
Socket,
13-
Stream
13+
StreamSupervisor
1414
}
1515

1616
alias Kadabra.Connection.FlowControl
@@ -58,14 +58,14 @@ defmodule Kadabra.Connection.Processor do
5858
send_window_update(config.socket, 0, size)
5959
send_window_update(config.socket, stream_id, bin_size)
6060

61-
process_on_stream(state, stream_id, frame)
61+
StreamSupervisor.send_frame(config.ref, stream_id, frame)
6262

6363
{:ok, %{state | remote_window: state.remote_window + size}}
6464
end
6565

6666
def process(%Headers{stream_id: stream_id} = frame, state) do
67-
state
68-
|> process_on_stream(stream_id, frame)
67+
state.config.ref
68+
|> StreamSupervisor.send_frame(stream_id, frame)
6969
|> case do
7070
:ok ->
7171
{:ok, state}
@@ -81,7 +81,7 @@ defmodule Kadabra.Connection.Processor do
8181
end
8282

8383
def process(%RstStream{stream_id: stream_id} = frame, state) do
84-
process_on_stream(state, stream_id, frame)
84+
StreamSupervisor.send_frame(state.config.ref, stream_id, frame)
8585

8686
{:ok, state}
8787
end
@@ -121,7 +121,7 @@ defmodule Kadabra.Connection.Processor do
121121
notify_settings_change(old_settings, settings, flow)
122122

123123
config.ref
124-
|> Hpack.via_tuple(:encoder)
124+
|> Hpack.via_tuple(:decoder)
125125
|> Hpack.update_max_table_size(settings.max_header_list_size)
126126

127127
bin = Frame.Settings.ack() |> Encodable.to_bin()
@@ -136,7 +136,16 @@ defmodule Kadabra.Connection.Processor do
136136
end
137137

138138
def process(%Frame.Settings{ack: true}, %{config: c} = state) do
139+
c.ref
140+
|> Hpack.via_tuple(:encoder)
141+
|> Hpack.update_max_table_size(state.local_settings.max_header_list_size)
142+
143+
c.ref
144+
|> Hpack.via_tuple(:decoder)
145+
|> Hpack.update_max_table_size(state.local_settings.max_header_list_size)
146+
139147
send_huge_window_update(c.socket, state.remote_window)
148+
140149
{:ok, %{state | remote_window: FlowControl.window_max()}}
141150
end
142151

@@ -148,15 +157,11 @@ defmodule Kadabra.Connection.Processor do
148157
max_frame_size: max_frame
149158
} = flow_control
150159

151-
stream = Stream.new(config, stream_id, window, max_frame)
152-
153-
case Stream.start_link(stream) do
154-
{:ok, pid} ->
155-
Stream.call_recv(pid, frame)
156-
157-
flow = FlowControl.add_active(flow_control, stream_id, pid)
158-
159-
{:ok, %{state | flow_control: flow}}
160+
case StreamSupervisor.start_stream(config, stream_id, window, max_frame) do
161+
{:ok, _pid} ->
162+
StreamSupervisor.send_frame(config.ref, stream_id, frame)
163+
state = add_active(state, stream_id)
164+
{:ok, state}
160165

161166
error ->
162167
raise "#{inspect(error)}"
@@ -200,12 +205,12 @@ defmodule Kadabra.Connection.Processor do
200205
end
201206

202207
def process(%WindowUpdate{stream_id: stream_id} = frame, state) do
203-
process_on_stream(state, stream_id, frame)
208+
StreamSupervisor.send_frame(state.config.ref, stream_id, frame)
204209
{:ok, state}
205210
end
206211

207212
def process(%Continuation{stream_id: stream_id} = frame, state) do
208-
process_on_stream(state, stream_id, frame)
213+
StreamSupervisor.send_frame(state.config.ref, stream_id, frame)
209214
{:ok, state}
210215
end
211216

@@ -220,6 +225,11 @@ defmodule Kadabra.Connection.Processor do
220225
{:ok, state}
221226
end
222227

228+
def add_active(state, stream_id) do
229+
flow = FlowControl.add_active(state.flow_control, stream_id)
230+
%{state | flow_control: flow}
231+
end
232+
223233
def log_goaway(%Goaway{last_stream_id: id, error_code: c, debug_data: b}) do
224234
error = Error.parse(c)
225235
Logger.error("Got GOAWAY, #{error}, Last Stream: #{id}, Rest: #{b}")
@@ -256,10 +266,4 @@ defmodule Kadabra.Connection.Processor do
256266
send(pid, {:settings_change, window_diff, max_frame_size})
257267
end
258268
end
259-
260-
def process_on_stream(state, stream_id, frame) do
261-
state.flow_control.stream_set.active_streams
262-
|> Map.get(stream_id)
263-
|> Stream.call_recv(frame)
264-
end
265269
end

lib/encodable.ex

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ defprotocol Kadabra.Encodable do
1616
<<0, 5, 0, 0, 64, 0, 0, 4, 0, 0, 255, 255, 0, 1, 0, 0, 16, 0, 0, 2,
1717
0, 0, 0, 0>>
1818
19+
iex> %Kadabra.Frame.Continuation{end_headers: true,
20+
...> stream_id: 1, header_block_fragment: <<255, 255, 255>>} |> to_bin()
21+
<<0, 0, 3, 9, 4, 0, 0, 0, 1, 255, 255, 255>>
22+
1923
iex> Kadabra.Encodable.to_bin(:any_non_frame_term)
2024
:error
2125
"""
@@ -24,13 +28,5 @@ defprotocol Kadabra.Encodable do
2428
end
2529

2630
defimpl Kadabra.Encodable, for: Any do
27-
@doc ~S"""
28-
Encodes to binary.
29-
30-
## Examples
31-
32-
iex> Kadabra.Encodable.to_bin(1234)
33-
:error
34-
"""
3531
def to_bin(_), do: :error
3632
end

lib/frame/continuation.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,12 @@ defmodule Kadabra.Frame.Continuation do
3131
}
3232
end
3333
end
34+
35+
defimpl Kadabra.Encodable, for: Kadabra.Frame.Continuation do
36+
alias Kadabra.Frame
37+
38+
def to_bin(frame) do
39+
f = if frame.end_headers, do: 0x4, else: 0x0
40+
Frame.binary_frame(0x9, f, frame.stream_id, frame.header_block_fragment)
41+
end
42+
end

0 commit comments

Comments
 (0)