503 + Retry-After

This commit is contained in:
Marcin Kulik 2017-05-21 17:43:45 +02:00
parent e0511053a4
commit a754c0fdc2
4 changed files with 52 additions and 26 deletions

View File

@ -45,9 +45,9 @@ else
end end
config :asciinema, :png_generator, Asciinema.PngGenerator.A2png config :asciinema, :png_generator, Asciinema.PngGenerator.A2png
config :asciinema, Asciinema.PngGenerator.A2png, bin_path: "./a2png/a2png.sh" config :asciinema, Asciinema.PngGenerator.A2png,
bin_path: System.get_env("A2PNG_BIN_PATH") || "./a2png/a2png.sh",
config :porcelain, goon_warn_if_missing: false pool_size: String.to_integer(System.get_env("A2PNG_POOL_SIZE") || "2")
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.

View File

@ -14,7 +14,7 @@ defmodule Asciinema do
supervisor(Asciinema.Endpoint, []), supervisor(Asciinema.Endpoint, []),
# Start your own worker by calling: Asciinema.Worker.start_link(arg1, arg2, arg3) # Start your own worker by calling: Asciinema.Worker.start_link(arg1, arg2, arg3)
# worker(Asciinema.Worker, [arg1, arg2, arg3]), # worker(Asciinema.Worker, [arg1, arg2, arg3]),
:poolboy.child_spec(:worker, poolboy_config(), []), :poolboy.child_spec(:worker, Asciinema.PngGenerator.A2png.poolboy_config(), []),
] ]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
@ -29,11 +29,4 @@ defmodule Asciinema do
Asciinema.Endpoint.config_change(changed, removed) Asciinema.Endpoint.config_change(changed, removed)
:ok :ok
end end
defp poolboy_config do
[{:name, {:local, :worker}},
{:worker_module, Asciinema.PngGenerator.A2png},
{:size, 2},
{:max_overflow, 0}]
end
end end

View File

@ -3,16 +3,31 @@ defmodule Asciinema.PngGenerator.A2png do
use GenServer use GenServer
alias Asciinema.Asciicast alias Asciinema.Asciicast
@result_timeout 30000 @pool_name :worker
@acquire_timeout 5000 @acquire_timeout 5000
@a2png_timeout 30000
@result_timeout 35000
def generate(%Asciicast{} = asciicast) do def generate(%Asciicast{} = asciicast) do
{:ok, tmp_dir_path} = Briefly.create(directory: true) {:ok, tmp_dir_path} = Briefly.create(directory: true)
:poolboy.transaction( try do
:worker, :poolboy.transaction(
&GenServer.call(&1, {:gen_png, asciicast, tmp_dir_path}, @result_timeout), @acquire_timeout @pool_name,
) (fn pid ->
try do
GenServer.call(pid, {:generate, asciicast, tmp_dir_path}, @result_timeout)
catch
:exit, {:timeout, _} ->
{:error, :timeout}
end
end),
@acquire_timeout
)
catch
:exit, {:timeout, _} ->
{:error, :busy}
end
end end
# GenServer API # GenServer API
@ -25,11 +40,18 @@ defmodule Asciinema.PngGenerator.A2png do
{:ok, nil} {:ok, nil}
end end
def handle_call({:gen_png, asciicast, tmp_dir_path}, _from, state) do def handle_call({:generate, asciicast, tmp_dir_path}, _from, state) do
{:reply, do_gen(asciicast, tmp_dir_path), state} {:reply, do_generate(asciicast, tmp_dir_path), state}
end end
defp do_gen(asciicast, tmp_dir_path) do def poolboy_config do
[{:name, {:local, @pool_name}},
{:worker_module, __MODULE__},
{:size, pool_size()},
{:max_overflow, 0}]
end
defp do_generate(asciicast, tmp_dir_path) do
path = Asciicast.json_store_path(asciicast) path = Asciicast.json_store_path(asciicast)
json_path = Path.join(tmp_dir_path, "tmp.json") json_path = Path.join(tmp_dir_path, "tmp.json")
png_path = Path.join(tmp_dir_path, "tmp.png") png_path = Path.join(tmp_dir_path, "tmp.png")
@ -37,7 +59,8 @@ defmodule Asciinema.PngGenerator.A2png do
with {:ok, file} <- file_store().open(path), with {:ok, file} <- file_store().open(path),
{:ok, _} <- :file.copy(file, json_path), {:ok, _} <- :file.copy(file, json_path),
%{status: 0} <- Porcelain.exec(bin_path(), [json_path, png_path, snapshot_at]) do process <- Porcelain.spawn(bin_path(), [json_path, png_path, snapshot_at]),
{:ok, %{status: 0}} <- Porcelain.Process.await(process, @a2png_timeout) do
{:ok, png_path} {:ok, png_path}
else else
otherwise -> otherwise ->
@ -45,10 +68,14 @@ defmodule Asciinema.PngGenerator.A2png do
end end
end end
def bin_path do defp bin_path do
Keyword.get(Application.get_env(:asciinema, __MODULE__), :bin_path) Keyword.get(Application.get_env(:asciinema, __MODULE__), :bin_path)
end end
defp pool_size do
Keyword.get(Application.get_env(:asciinema, __MODULE__), :pool_size)
end
defp file_store do defp file_store do
Application.get_env(:asciinema, :file_store) Application.get_env(:asciinema, :file_store)
end end

View File

@ -5,11 +5,17 @@ defmodule Asciinema.AsciicastImageController do
def show(conn, %{"id" => id} = _params) do def show(conn, %{"id" => id} = _params) do
asciicast = Repo.one!(Asciicast.by_id_or_secret_token(id)) asciicast = Repo.one!(Asciicast.by_id_or_secret_token(id))
{:ok, png_path} = PngGenerator.generate(asciicast)
conn case PngGenerator.generate(asciicast) do
|> put_resp_header("content-type", MIME.path(png_path)) {:ok, png_path} ->
|> send_file(200, png_path) conn
|> halt |> put_resp_header("content-type", MIME.path(png_path))
|> send_file(200, png_path)
|> halt
{:error, :busy} ->
conn
|> put_resp_header("retry-after", "5")
|> send_resp(503, "")
end
end end
end end