Skip to content

OTel - Add context propagation #568

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Apr 2, 2023
Merged
36 changes: 25 additions & 11 deletions lib/tesla/middleware/timeout.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,30 @@ defmodule Tesla.Middleware.Timeout do

## Examples

```
defmodule MyClient do
use Tesla
defmodule MyClient do
use Tesla

plug Tesla.Middleware.Timeout, timeout: 2_000
end
```
plug Tesla.Middleware.Timeout, timeout: 2_000
end

If you are using OpenTelemetry in your project, you may be interested in
using `OpentelemetryProcessPropagator.Task` to have a better integration using
the `task_module` option.

defmodule MyClient do
use Tesla

plug Tesla.Middleware.Timeout,
timeout: 2_000,
task_module: OpentelemetryProcessPropagator.Task
end

## Options

- `:timeout` - number of milliseconds a request is allowed to take (defaults to `1000`)
- `:task_module` - the `Task` module used to spawn tasks. Useful when you want
use alternatives such as `OpentelemetryProcessPropagator.Task` from OTEL
project.
"""

@behaviour Tesla.Middleware
Expand All @@ -25,22 +38,23 @@ defmodule Tesla.Middleware.Timeout do
def call(env, next, opts) do
opts = opts || []
timeout = Keyword.get(opts, :timeout, @default_timeout)
task_module = Keyword.get(opts, :task_module, Task)

task = safe_async(fn -> Tesla.run(env, next) end)
task = safe_async(task_module, fn -> Tesla.run(env, next) end)

try do
task
|> Task.await(timeout)
|> task_module.await(timeout)
|> repass_error
catch
:exit, {:timeout, _} ->
Task.shutdown(task, 0)
task_module.shutdown(task, 0)
{:error, :timeout}
end
end

defp safe_async(func) do
Task.async(fn ->
defp safe_async(task_module, func) do
task_module.async(fn ->
try do
{:ok, func.()}
rescue
Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Tesla.Mixfile do
use Mix.Project

@source_url "https://github.com/teamon/tesla"
@version "1.5.1"
@version "1.6.0"

def project do
[
Expand Down Expand Up @@ -75,6 +75,7 @@ defmodule Tesla.Mixfile do
{:telemetry, "~> 0.4 or ~> 1.0", optional: true},

# testing & docs
{:opentelemetry_process_propagator, "~> 0.1", only: [:test, :dev]},
{:excoveralls, "~> 0.8", only: :test},
{:httparrot, "~> 1.3", only: :test},
{:ex_doc, "~> 0.21", only: :dev, runtime: false},
Expand Down
3 changes: 3 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
"nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"},
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
"opentelemetry_api": {:hex, :opentelemetry_api, "1.2.1", "7b69ed4f40025c005de0b74fce8c0549625d59cb4df12d15c32fe6dc5076ff42", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "6d7a27b7cad2ad69a09cabf6670514cafcec717c8441beb5c96322bac3d05350"},
"opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.2.2", "85244a49f0c32ae1e2f3d58c477c265bd6125ee3480ade82b0fa9324b85ed3f0", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "04db13302a34bea8350a13ed9d49c22dfd32c4bc590d8aa88b6b4b7e4f346c61"},
"opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
Expand Down
43 changes: 42 additions & 1 deletion test/tesla/middleware/timeout_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ defmodule Tesla.Middleware.TimeoutTest do
end
end

defmodule OtelTimeoutClient do
use Tesla

plug Tesla.Middleware.Timeout,
timeout: 100,
task_module: OpentelemetryProcessPropagator.Task

adapter fn env ->
case env.url do
"/sleep_50ms" ->
Process.sleep(50)
{:ok, %{env | status: 200}}

"/sleep_150ms" ->
Process.sleep(150)
{:ok, %{env | status: 200}}
end
end
end

describe "using custom timeout (100ms)" do
test "should return timeout error when the stack timeout" do
assert {:error, :timeout} = Client.get("/sleep_150ms")
Expand Down Expand Up @@ -111,7 +131,7 @@ defmodule Tesla.Middleware.TimeoutTest do
[_, {timeout_module, _, _, module_file_info} | _] = __STACKTRACE__

assert Tesla.Middleware.Timeout == timeout_module
assert module_file_info == [file: 'lib/tesla/middleware/timeout.ex', line: 45]
assert module_file_info == [file: 'lib/tesla/middleware/timeout.ex', line: 59]
else
_ ->
flunk("Expected exception to be thrown")
Expand All @@ -126,4 +146,25 @@ defmodule Tesla.Middleware.TimeoutTest do
assert catch_exit(Client.get("/exit")) == :exit_value
end
end

describe "swapping task_module for OpentelemetryProcessPropagator.Task" do
test "should return timeout error when the stack timeout" do
assert {:error, :timeout} = OtelTimeoutClient.get("/sleep_150ms")
end

test "should return the response when not timeout" do
assert {:ok, %Tesla.Env{status: 200}} = OtelTimeoutClient.get("/sleep_50ms")
end

test "should not kill calling process" do
Process.flag(:trap_exit, true)

pid =
spawn_link(fn ->
assert {:error, :timeout} = Client.get("/sleep_150ms")
end)

assert_receive {:EXIT, ^pid, :normal}, 200
end
end
end