Scott Hiett commited on
Commit
bb41a4c
1 Parent(s): 691cebf

Better error handling for failure to connect to Redis server

Browse files
lib/srh/auth/token_resolver.ex CHANGED
@@ -70,11 +70,13 @@ defmodule Srh.Auth.TokenResolver do
70
  {srh_max_connections, ""} = Integer.parse(System.get_env("SRH_MAX_CONNECTIONS", "3"))
71
 
72
  # Create a config-file-like structure that the ETS layout expects, with just one entry
73
- config_file_data = Map.put(%{}, srh_token, %{
74
- "srh_id" => "env_config_connection", # Jason.parse! expects these keys to be strings, not atoms, so we need to replicate that setup
75
- "connection_string" => srh_connection_string,
76
- "max_connections" => srh_max_connections
77
- })
 
 
78
 
79
  IO.puts("Loaded config from env. #{map_size(config_file_data)} entries.")
80
  # Load this into ETS
@@ -98,17 +100,17 @@ defmodule Srh.Auth.TokenResolver do
98
  # The env strategy uses the same ETS table as the file strategy, so we can fall back on that
99
  defp do_resolve("env", token), do: do_resolve("file", token)
100
 
101
- defp do_resolve("redis", _token) do
102
- {
103
- :ok,
104
- # This is done to replicate what will eventually be API endpoints, so they keys are not atoms
105
- Jason.decode!(
106
- Jason.encode!(%{
107
- srh_id: "1000",
108
- connection_string: "redis://localhost:6379",
109
- max_connections: 10
110
- })
111
- )
112
- }
113
- end
114
  end
 
70
  {srh_max_connections, ""} = Integer.parse(System.get_env("SRH_MAX_CONNECTIONS", "3"))
71
 
72
  # Create a config-file-like structure that the ETS layout expects, with just one entry
73
+ config_file_data =
74
+ Map.put(%{}, srh_token, %{
75
+ # Jason.parse! expects these keys to be strings, not atoms, so we need to replicate that setup
76
+ "srh_id" => "env_config_connection",
77
+ "connection_string" => srh_connection_string,
78
+ "max_connections" => srh_max_connections
79
+ })
80
 
81
  IO.puts("Loaded config from env. #{map_size(config_file_data)} entries.")
82
  # Load this into ETS
 
100
  # The env strategy uses the same ETS table as the file strategy, so we can fall back on that
101
  defp do_resolve("env", token), do: do_resolve("file", token)
102
 
103
+ # defp do_resolve("redis", _token) do
104
+ # {
105
+ # :ok,
106
+ # # This is done to replicate what will eventually be API endpoints, so they keys are not atoms
107
+ # Jason.decode!(
108
+ # Jason.encode!(%{
109
+ # srh_id: "1000",
110
+ # connection_string: "redis://localhost:6379",
111
+ # max_connections: 10
112
+ # })
113
+ # )
114
+ # }
115
+ # end
116
  end
lib/srh/http/base_router.ex CHANGED
@@ -54,7 +54,8 @@ defmodule Srh.Http.BaseRouter do
54
  |> get_req_header("upstash-encoding")
55
  |> RequestValidator.validate_encoding_header() do
56
  {:ok, _encoding_enabled} -> true
57
- {:error, _} -> false # it's not required to be present
 
58
  end
59
  end
60
 
@@ -63,7 +64,9 @@ defmodule Srh.Http.BaseRouter do
63
  true ->
64
  # We need to use the encoder to
65
  ResultEncoder.encode_response(response)
66
- false -> response
 
 
67
  end
68
  end
69
 
@@ -85,6 +88,9 @@ defmodule Srh.Http.BaseRouter do
85
  {:not_authorized, message} ->
86
  %{code: 401, message: message, json: false}
87
 
 
 
 
88
  {:server_error, _} ->
89
  %{code: 500, message: "An error occurred internally", json: false}
90
 
 
54
  |> get_req_header("upstash-encoding")
55
  |> RequestValidator.validate_encoding_header() do
56
  {:ok, _encoding_enabled} -> true
57
+ # it's not required to be present
58
+ {:error, _} -> false
59
  end
60
  end
61
 
 
64
  true ->
65
  # We need to use the encoder to
66
  ResultEncoder.encode_response(response)
67
+
68
+ false ->
69
+ response
70
  end
71
  end
72
 
 
88
  {:not_authorized, message} ->
89
  %{code: 401, message: message, json: false}
90
 
91
+ {:connection_error, message} ->
92
+ %{code: 500, message: message, json: false}
93
+
94
  {:server_error, _} ->
95
  %{code: 500, message: "An error occurred internally", json: false}
96
 
lib/srh/http/command_handler.ex CHANGED
@@ -99,39 +99,43 @@ defmodule Srh.Http.CommandHandler do
99
  do_dispatch_command_transaction_array(wrapped_command_array, worker_pid, responses)
100
 
101
  # Now manually run the EXEC - this is what contains the information to form the response, not the above
102
- result = case ClientWorker.redis_command(worker_pid, ["EXEC"]) do
103
- {:ok, res} ->
104
- {
105
- :ok,
106
- res
107
- |> Enum.map(&(%{result: &1}))
108
- }
109
- # TODO: Can there be any inline errors here? Wouldn't they fail the whole tx?
110
 
111
- {:error, error} ->
112
- {:redis_error, %{error: error.message}}
113
- end
114
 
115
  Client.return_worker(client_pid, worker_pid)
116
 
117
  result
 
118
  {:error, msg} ->
119
  {:server_error, msg}
120
  end
121
  end
122
 
123
- defp do_dispatch_command_transaction_array([current | rest], worker_pid, responses) when is_pid(worker_pid) do
124
- updated_responses = case ClientWorker.redis_command(worker_pid, current) do
125
- {:ok, res} ->
126
- [%{result: res} | responses]
127
-
128
- {:error, error} ->
129
- [
130
- %{
131
- error: error.message
132
- } | responses
133
- ]
134
- end
 
 
 
135
 
136
  do_dispatch_command_transaction_array(rest, worker_pid, updated_responses)
137
  end
@@ -154,16 +158,30 @@ defmodule Srh.Http.CommandHandler do
154
  {:ok, %{result: res}}
155
 
156
  {:error, error} ->
157
- {
158
- :redis_error,
159
- %{
160
- error: error.message
161
- }
162
- }
163
  end
164
 
165
  {:error, msg} ->
166
  {:server_error, msg}
167
  end
168
  end
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  end
 
99
  do_dispatch_command_transaction_array(wrapped_command_array, worker_pid, responses)
100
 
101
  # Now manually run the EXEC - this is what contains the information to form the response, not the above
102
+ result =
103
+ case ClientWorker.redis_command(worker_pid, ["EXEC"]) do
104
+ {:ok, res} ->
105
+ {
106
+ :ok,
107
+ res
108
+ |> Enum.map(&%{result: &1})
109
+ }
110
 
111
+ {:error, error} ->
112
+ decode_error(error)
113
+ end
114
 
115
  Client.return_worker(client_pid, worker_pid)
116
 
117
  result
118
+
119
  {:error, msg} ->
120
  {:server_error, msg}
121
  end
122
  end
123
 
124
+ defp do_dispatch_command_transaction_array([current | rest], worker_pid, responses)
125
+ when is_pid(worker_pid) do
126
+ updated_responses =
127
+ case ClientWorker.redis_command(worker_pid, current) do
128
+ {:ok, res} ->
129
+ [%{result: res} | responses]
130
+
131
+ {:error, error} ->
132
+ [
133
+ %{
134
+ error: error.message
135
+ }
136
+ | responses
137
+ ]
138
+ end
139
 
140
  do_dispatch_command_transaction_array(rest, worker_pid, updated_responses)
141
  end
 
158
  {:ok, %{result: res}}
159
 
160
  {:error, error} ->
161
+ decode_error(error)
 
 
 
 
 
162
  end
163
 
164
  {:error, msg} ->
165
  {:server_error, msg}
166
  end
167
  end
168
+
169
+ # Figure out if it's an actual Redis error or a Redix error
170
+ defp decode_error(error) do
171
+ case error do
172
+ %{reason: :closed} ->
173
+ {
174
+ :connection_error,
175
+ "Unable to connect to the Redis server"
176
+ }
177
+
178
+ _ ->
179
+ {
180
+ :redis_error,
181
+ %{
182
+ error: error.message
183
+ }
184
+ }
185
+ end
186
+ end
187
  end
lib/srh/redis/client_worker.ex CHANGED
@@ -52,7 +52,6 @@ defmodule Srh.Redis.ClientWorker do
52
  {:noreply, state}
53
  end
54
 
55
- # TODO: Handle host / port connections
56
  def handle_info(
57
  :create_connection,
58
  %{
@@ -62,6 +61,7 @@ defmodule Srh.Redis.ClientWorker do
62
  } = state
63
  )
64
  when is_binary(connection_string) do
 
65
  {:ok, pid} = Redix.start_link(connection_string)
66
  {:noreply, %{state | redix_pid: pid}}
67
  end
 
52
  {:noreply, state}
53
  end
54
 
 
55
  def handle_info(
56
  :create_connection,
57
  %{
 
61
  } = state
62
  )
63
  when is_binary(connection_string) do
64
+ # Will cause a crash for this genserver if the connection fails
65
  {:ok, pid} = Redix.start_link(connection_string)
66
  {:noreply, %{state | redix_pid: pid}}
67
  end