From 696463a1ff0837edd9e1d41597b8a82634cda048 Mon Sep 17 00:00:00 2001 From: openhands Date: Mon, 27 Jan 2025 12:57:49 +0000 Subject: [PATCH 1/2] TBS-2755: Improve ETCD timeout handling - Add graceful timeout handling in Connection module - Return {:error, :timeout} instead of crashing - Update documentation with timeout behavior - Set default timeout to 5 seconds - Add error handling sections in docs --- lib/etcd_ex.ex | 34 ++++++++++++++++++++++++++++------ lib/etcd_ex/connection.ex | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/lib/etcd_ex.ex b/lib/etcd_ex.ex index 7136efe..538ed16 100644 --- a/lib/etcd_ex.ex +++ b/lib/etcd_ex.ex @@ -55,6 +55,7 @@ defmodule EtcdEx do @type watch_ref :: reference @type watching_process :: pid + # Default timeout for all operations. After this time, operations will return {:error, :timeout} @default_timeout :timer.seconds(5) @doc """ @@ -158,8 +159,9 @@ defmodule EtcdEx do filters out lesser create revisions. * `:max_create_revision` - the upper bound for key create revisions; filters out greater create revisions. - * `:timeout` - indicates max time to wait for a response. Defaults to - `:infinity`. + * `:timeout` - indicates max time to wait for a response. Defaults to 5 seconds. + If the operation takes longer than the specified timeout, it will return + `{:error, :timeout}` instead of crashing. ## Response @@ -214,6 +216,12 @@ defmodule EtcdEx do * `:mod_revision` - revision of the last modification on the key. * `:lease` - the ID of the lease attached to the key. If lease is 0, then no lease is attached to the key. + + ## Error Handling + + The function will return `{:error, :timeout}` if the operation takes longer than + the specified timeout value. This is a graceful error handling mechanism that + prevents the process from crashing on timeouts. """ @spec get(conn, Types.key(), [Types.get_opt()], timeout) :: {:ok, any} | {:error, Types.error()} @@ -239,8 +247,15 @@ defmodule EtcdEx do value. Returns an error if the key does not exist. * `:ignore_lease` - when set, update the key without changing its current lease. Returns an error if the key does not exist. - * `:timeout` - indicates max time to wait for a response. Defaults to - `:infinity`. + * `:timeout` - indicates max time to wait for a response. Defaults to 5 seconds. + If the operation takes longer than the specified timeout, it will return + `{:error, :timeout}` instead of crashing. + + ## Error Handling + + The function will return `{:error, :timeout}` if the operation takes longer than + the specified timeout value. This is a graceful error handling mechanism that + prevents the process from crashing on timeouts. """ @spec put(conn, Types.key(), Types.value(), [Types.put_opt()], timeout) :: {:ok, any} | {:error, Types.error()} @@ -262,8 +277,15 @@ defmodule EtcdEx do keys after `key` argument. * `:prev_kv` - when set, return the contents of the deleted key-value pairs. - * `:timeout` - indicates max time to wait for a response. Defaults to - `:infinity`. + * `:timeout` - indicates max time to wait for a response. Defaults to 5 seconds. + If the operation takes longer than the specified timeout, it will return + `{:error, :timeout}` instead of crashing. + + ## Error Handling + + The function will return `{:error, :timeout}` if the operation takes longer than + the specified timeout value. This is a graceful error handling mechanism that + prevents the process from crashing on timeouts. """ @spec delete(conn, Types.key(), [Types.delete_opt()], timeout) :: {:ok, any} | {:error, Types.error()} diff --git a/lib/etcd_ex/connection.ex b/lib/etcd_ex/connection.ex index 16cd629..1884d27 100644 --- a/lib/etcd_ex/connection.ex +++ b/lib/etcd_ex/connection.ex @@ -43,20 +43,40 @@ defmodule EtcdEx.Connection do ] @doc false - def unary(conn, fun, args, timeout), - do: Connection.call(conn, {:unary, fun, args}, timeout) + def unary(conn, fun, args, timeout) do + try do + Connection.call(conn, {:unary, fun, args}, timeout) + catch + :exit, {:timeout, _} -> {:error, :timeout} + end + end @doc false - def watch(conn, watching_process, key, opts, timeout), - do: Connection.call(conn, {:watch, watching_process, key, opts}, timeout) + def watch(conn, watching_process, key, opts, timeout) do + try do + Connection.call(conn, {:watch, watching_process, key, opts}, timeout) + catch + :exit, {:timeout, _} -> {:error, :timeout} + end + end @doc false - def cancel_watch(conn, watching_process, timeout), - do: Connection.call(conn, {:cancel_watch, watching_process}, timeout) + def cancel_watch(conn, watching_process, timeout) do + try do + Connection.call(conn, {:cancel_watch, watching_process}, timeout) + catch + :exit, {:timeout, _} -> {:error, :timeout} + end + end @doc false - def list_watches(conn, watching_process, timeout), - do: Connection.call(conn, {:list_watches, watching_process}, timeout) + def list_watches(conn, watching_process, timeout) do + try do + Connection.call(conn, {:list_watches, watching_process}, timeout) + catch + :exit, {:timeout, _} -> {:error, :timeout} + end + end @doc false def child_spec(options) do From 2e4d3d8ab7459748830164e0f660327e53d7ae3a Mon Sep 17 00:00:00 2001 From: zaher Date: Mon, 27 Jan 2025 15:05:55 +0200 Subject: [PATCH 2/2] bump version --- CHANGELOG.md | 4 ++++ mix.exs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 149124b..31e9038 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # Changelog +## 2.0.0 + +* `ETCD` oprations no longer crash on timeout +* `ETCD` oprations now return an error tuple `{:error, :timeout}` on timeout ## 1.3.0 diff --git a/mix.exs b/mix.exs index f721e20..55972b4 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule EtcdEx.MixProject do def project do [ app: :etcdex, - version: "1.3.0", + version: "2.0.0", elixir: "~> 1.10", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod,