Skip to content

Commit 3f3b85d

Browse files
committed
Disable connection reuse and harden error handling
1 parent 9258ee2 commit 3f3b85d

2 files changed

Lines changed: 41 additions & 3 deletions

File tree

src/api.jl

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,20 @@ uri_v3(mlf::MLFlow, endpoint::String;
4343
"""
4444
headers(mlf::MLFlow,custom_headers::AbstractDict)
4545
46-
Retrieves HTTP headers based on `mlf` and merges with user-provided `custom_headers`
46+
Retrieves HTTP headers based on `mlf` and merges with user-provided `custom_headers`.
47+
48+
`Connection: close` is set by default so each request uses a fresh connection. MLflow's
49+
server (gunicorn) closes idle keep-alive connections after a couple of seconds, while the
50+
HTTP client keeps them pooled for much longer; reusing such a stale connection surfaces as
51+
`unexpected EOF while reading HTTP/1 data`. Disabling reuse avoids that class of error.
4752
4853
# Examples
4954
```@example
5055
headers(mlf,Dict("Content-Type"=>"application/json"))
5156
```
5257
"""
53-
headers(mlf::MLFlow, custom_headers::AbstractDict) = merge(mlf.headers, custom_headers)
58+
headers(mlf::MLFlow, custom_headers::AbstractDict) =
59+
merge(Dict("Connection" => "close"), mlf.headers, custom_headers)
5460

5561
"""
5662
mlfget(mlf, endpoint; kwargs...)
@@ -66,6 +72,10 @@ function mlfget(mlf, endpoint; kwargs...)
6672
response = HTTP.get(apiuri, apiheaders)
6773
return response.body |> String |> JSON.parse
6874
catch e
75+
# Only HTTP error responses (4xx/5xx) carry a `.response` with an MLflow error
76+
# body; connection/parse errors (e.g. HTTP.ParseError) do not, so rethrow those
77+
# instead of masking them with a FieldError on `e.response`.
78+
e isa HTTP.StatusError || rethrow()
6979
error_response = e.response.body |> String |> JSON.parse
7080
error_message = "$(error_response["error_code"]) - $(error_response["message"])"
7181
@error error_message
@@ -88,6 +98,10 @@ function mlfpost(mlf, endpoint; kwargs...)
8898
response = HTTP.post(apiuri, apiheaders, body)
8999
return response.body |> String |> JSON.parse
90100
catch e
101+
# Only HTTP error responses (4xx/5xx) carry a `.response` with an MLflow error
102+
# body; connection/parse errors (e.g. HTTP.ParseError) do not, so rethrow those
103+
# instead of masking them with a FieldError on `e.response`.
104+
e isa HTTP.StatusError || rethrow()
91105
error_response = e.response.body |> String |> JSON.parse
92106
error_message = "$(error_response["error_code"]) - $(error_response["message"])"
93107
@error error_message
@@ -110,6 +124,10 @@ function mlfpatch(mlf, endpoint; kwargs...)
110124
response = HTTP.patch(apiuri, apiheaders, body)
111125
return response.body |> String |> JSON.parse
112126
catch e
127+
# Only HTTP error responses (4xx/5xx) carry a `.response` with an MLflow error
128+
# body; connection/parse errors (e.g. HTTP.ParseError) do not, so rethrow those
129+
# instead of masking them with a FieldError on `e.response`.
130+
e isa HTTP.StatusError || rethrow()
113131
error_response = e.response.body |> String |> JSON.parse
114132
error_message = "$(error_response["error_code"]) - $(error_response["message"])"
115133
@error error_message
@@ -135,6 +153,10 @@ function mlfdelete(mlf, endpoint; kwargs...)
135153
# Some v3 endpoints (e.g. workspaces/delete) return an empty body on success.
136154
return isempty(strip(response_body)) ? Dict{String,Any}() : JSON.parse(response_body)
137155
catch e
156+
# Only HTTP error responses (4xx/5xx) carry a `.response` with an MLflow error
157+
# body; connection/parse errors (e.g. HTTP.ParseError) do not, so rethrow those
158+
# instead of masking them with a FieldError on `e.response`.
159+
e isa HTTP.StatusError || rethrow()
138160
error_response = e.response.body |> String |> JSON.parse
139161
error_message = "$(error_response["error_code"]) - $(error_response["message"])"
140162
@error error_message
@@ -156,6 +178,10 @@ function mlfget_v3(mlf, endpoint; kwargs...)
156178
response = HTTP.get(apiuri, apiheaders)
157179
return response.body |> String |> JSON.parse
158180
catch e
181+
# Only HTTP error responses (4xx/5xx) carry a `.response` with an MLflow error
182+
# body; connection/parse errors (e.g. HTTP.ParseError) do not, so rethrow those
183+
# instead of masking them with a FieldError on `e.response`.
184+
e isa HTTP.StatusError || rethrow()
159185
error_response = e.response.body |> String |> JSON.parse
160186
error_message = "$(error_response["error_code"]) - $(error_response["message"])"
161187
@error error_message
@@ -178,6 +204,10 @@ function mlfpost_v3(mlf, endpoint; kwargs...)
178204
response = HTTP.post(apiuri, apiheaders, body)
179205
return response.body |> String |> JSON.parse
180206
catch e
207+
# Only HTTP error responses (4xx/5xx) carry a `.response` with an MLflow error
208+
# body; connection/parse errors (e.g. HTTP.ParseError) do not, so rethrow those
209+
# instead of masking them with a FieldError on `e.response`.
210+
e isa HTTP.StatusError || rethrow()
181211
error_response = e.response.body |> String |> JSON.parse
182212
error_message = "$(error_response["error_code"]) - $(error_response["message"])"
183213
@error error_message
@@ -200,6 +230,10 @@ function mlfpatch_v3(mlf, endpoint; kwargs...)
200230
response = HTTP.patch(apiuri, apiheaders, body)
201231
return response.body |> String |> JSON.parse
202232
catch e
233+
# Only HTTP error responses (4xx/5xx) carry a `.response` with an MLflow error
234+
# body; connection/parse errors (e.g. HTTP.ParseError) do not, so rethrow those
235+
# instead of masking them with a FieldError on `e.response`.
236+
e isa HTTP.StatusError || rethrow()
203237
error_response = e.response.body |> String |> JSON.parse
204238
error_message = "$(error_response["error_code"]) - $(error_response["message"])"
205239
@error error_message
@@ -225,6 +259,10 @@ function mlfdelete_v3(mlf, endpoint; kwargs...)
225259
# Some v3 endpoints (e.g. workspaces/delete) return an empty body on success.
226260
return isempty(strip(response_body)) ? Dict{String,Any}() : JSON.parse(response_body)
227261
catch e
262+
# Only HTTP error responses (4xx/5xx) carry a `.response` with an MLflow error
263+
# body; connection/parse errors (e.g. HTTP.ParseError) do not, so rethrow those
264+
# instead of masking them with a FieldError on `e.response`.
265+
e isa HTTP.StatusError || rethrow()
228266
error_response = e.response.body |> String |> JSON.parse
229267
error_message = "$(error_response["error_code"]) - $(error_response["message"])"
230268
@error error_message

test/server.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ function wait_until_ready(process::Base.Process, uri::String, workdir::String, t
6666
end
6767
try
6868
response = HTTP.get("$(uri)/health"; status_exception=false, retry=false,
69-
connect_timeout=2, readtimeout=2)
69+
connect_timeout=2, read_idle_timeout=2)
7070
response.status == 200 && return nothing
7171
catch
7272
# Server still starting up; keep polling until the deadline.

0 commit comments

Comments
 (0)