Skip to content

Commit 8adc1b2

Browse files
committed
fix: timed_out in net._http_response is always NULL
Closes #180. Before: ```sql select net.http_get('http://localhost:8080/pathological?status=200&delay=6', timeout_milliseconds := 1000); http_get ---------- 1 (1 row) postgres=# select timed_out, error_msg from net._http_response ; timed_out | error_msg -----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------- | Timeout of 1000 ms reached. Total time: 1001.461000 ms (DNS time: 0.052000 ms, TCP/SSL handshake time: 0.258000 ms, HTTP Request/Response time: 1001.095000 ms) (1 row) ``` Now: ```sql timed_out | error_msg -----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------- t | Timeout of 1000 ms reached. Total time: 1001.106000 ms (DNS time: 0.038000 ms, TCP/SSL handshake time: 0.176000 ms, HTTP Request/Response time: 1000.856000 ms) (1 row) ``` add polyfill
1 parent a7792bf commit 8adc1b2

File tree

3 files changed

+96
-75
lines changed

3 files changed

+96
-75
lines changed

src/core.c

Lines changed: 73 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -169,61 +169,6 @@ uint64 delete_expired_responses(char *ttl, int batch_size){
169169
return affected_rows;
170170
}
171171

172-
static void insert_failure_response(CURL *ez_handle, CURLcode return_code, int64 id, int32 timeout_milliseconds){
173-
SPI_connect();
174-
175-
const char* error_msg;
176-
if (return_code == CURLE_OPERATION_TIMEDOUT){
177-
error_msg = detailed_timeout_strerror(ez_handle, timeout_milliseconds).msg;
178-
} else {
179-
error_msg = curl_easy_strerror(return_code);
180-
}
181-
182-
int ret_code = SPI_execute_with_args("\
183-
insert into net._http_response(id, error_msg) values ($1, $2)",
184-
2,
185-
(Oid[]){INT8OID, CSTRINGOID},
186-
(Datum[]){Int64GetDatum(id), CStringGetDatum(error_msg)},
187-
NULL, false, 1);
188-
189-
if (ret_code != SPI_OK_INSERT)
190-
{
191-
ereport(ERROR, errmsg("Error when inserting failed response: %s", SPI_result_code_string(ret_code)));
192-
}
193-
194-
SPI_finish();
195-
}
196-
197-
static void insert_success_response(CurlData *cdata, long http_status_code, char *contentType, Jsonb *jsonb_headers){
198-
SPI_connect();
199-
200-
int ret_code = SPI_execute_with_args("\
201-
insert into net._http_response(id, status_code, content, headers, content_type, timed_out) values ($1, $2, $3, $4, $5, $6)",
202-
6,
203-
(Oid[]){INT8OID, INT4OID, CSTRINGOID, JSONBOID, CSTRINGOID, BOOLOID},
204-
(Datum[]){
205-
Int64GetDatum(cdata->id)
206-
, Int32GetDatum(http_status_code)
207-
, CStringGetDatum(cdata->body->data)
208-
, JsonbPGetDatum(jsonb_headers)
209-
, CStringGetDatum(contentType)
210-
, BoolGetDatum(false) // timed_out is false here as it's a success
211-
},
212-
(char[6]){
213-
' '
214-
, [2] = cdata->body->data[0] == '\0'? 'n' : ' '
215-
, [4] = !contentType? 'n' :' '
216-
},
217-
false, 1);
218-
219-
if (ret_code != SPI_OK_INSERT)
220-
{
221-
ereport(ERROR, errmsg("Error when inserting successful response: %s", SPI_result_code_string(ret_code)));
222-
}
223-
224-
SPI_finish();
225-
}
226-
227172
uint64 consume_request_queue(CURLM *curl_mhandle, int batch_size, MemoryContext curl_memctx){
228173
SPI_connect();
229174

@@ -282,8 +227,9 @@ uint64 consume_request_queue(CURLM *curl_mhandle, int batch_size, MemoryContext
282227
}
283228

284229
static void pfree_curl_data(CurlData *cdata){
285-
pfree(cdata->body->data);
286-
pfree(cdata->body);
230+
if(cdata->body){
231+
destroyStringInfo(cdata->body);
232+
}
287233
if(cdata->request_headers) //curl_slist_free_all already handles the NULL case, but be explicit about it
288234
curl_slist_free_all(cdata->request_headers);
289235
}
@@ -307,6 +253,71 @@ static Jsonb *jsonb_headers_from_curl_handle(CURL *ez_handle){
307253
return jsonb_headers;
308254
}
309255

256+
static void insert_response(CURL *ez_handle, CurlData *cdata, CURLcode curl_return_code){
257+
enum { nparams = 7 }; // using an enum because const size_t nparams doesn't compile
258+
Datum vals[nparams];
259+
char nulls[nparams]; MemSet(nulls, 'n', nparams);
260+
261+
vals[0] = Int64GetDatum(cdata->id);
262+
nulls[0] = ' ';
263+
264+
if (curl_return_code == CURLE_OK) {
265+
Jsonb *jsonb_headers = jsonb_headers_from_curl_handle(ez_handle);
266+
long res_http_status_code = 0;
267+
268+
EREPORT_CURL_GETINFO(ez_handle, CURLINFO_RESPONSE_CODE, &res_http_status_code);
269+
270+
vals[1] = Int32GetDatum(res_http_status_code);
271+
nulls[1] = ' ';
272+
273+
if (cdata->body && cdata->body->data[0] != '\0'){
274+
vals[2] = CStringGetTextDatum(cdata->body->data);
275+
nulls[2] = ' ';
276+
}
277+
278+
vals[3] = JsonbPGetDatum(jsonb_headers);
279+
nulls[3] = ' ';
280+
281+
struct curl_header *hdr;
282+
if (curl_easy_header(ez_handle, "content-type", 0, CURLH_HEADER, -1, &hdr) == CURLHE_OK){
283+
vals[4] = CStringGetTextDatum(hdr->value);
284+
nulls[4] = ' ';
285+
}
286+
287+
vals[5] = BoolGetDatum(false);
288+
nulls[5] = ' ';
289+
} else {
290+
bool timed_out = curl_return_code == CURLE_OPERATION_TIMEDOUT;
291+
char *error_msg = NULL;
292+
293+
if (timed_out){
294+
error_msg = detailed_timeout_strerror(ez_handle, cdata->timeout_milliseconds).msg;
295+
} else {
296+
error_msg = (char *) curl_easy_strerror(curl_return_code);
297+
}
298+
299+
vals[5] = BoolGetDatum(timed_out);
300+
nulls[5] = ' ';
301+
302+
if (error_msg){
303+
vals[6] = CStringGetTextDatum(error_msg);
304+
nulls[6] = ' ';
305+
}
306+
}
307+
308+
int ret_code = SPI_execute_with_args("\
309+
insert into net._http_response(id, status_code, content, headers, content_type, timed_out, error_msg) values ($1, $2, $3, $4, $5, $6, $7)",
310+
nparams,
311+
(Oid[nparams]){INT8OID, INT4OID, TEXTOID, JSONBOID, TEXTOID, BOOLOID, TEXTOID},
312+
vals, nulls,
313+
false, 1);
314+
315+
if (ret_code != SPI_OK_INSERT)
316+
{
317+
ereport(ERROR, errmsg("Error when inserting response: %s", SPI_result_code_string(ret_code)));
318+
}
319+
}
320+
310321
// Switch back to the curl memory context, which has the curl handles stored
311322
void insert_curl_responses(WorkerState *wstate, MemoryContext curl_memctx){
312323
MemoryContext old_ctx = MemoryContextSwitchTo(curl_memctx);
@@ -321,21 +332,11 @@ void insert_curl_responses(WorkerState *wstate, MemoryContext curl_memctx){
321332
CurlData *cdata = NULL;
322333
EREPORT_CURL_GETINFO(ez_handle, CURLINFO_PRIVATE, &cdata);
323334

324-
if (return_code != CURLE_OK) {
325-
insert_failure_response(ez_handle, return_code, cdata->id, cdata->timeout_milliseconds);
326-
} else {
327-
char *contentType;
328-
EREPORT_CURL_GETINFO(ez_handle, CURLINFO_CONTENT_TYPE, &contentType);
329-
330-
long http_status_code;
331-
EREPORT_CURL_GETINFO(ez_handle, CURLINFO_RESPONSE_CODE, &http_status_code);
335+
SPI_connect();
336+
insert_response(ez_handle, cdata, return_code);
337+
SPI_finish();
332338

333-
Jsonb *jsonb_headers = jsonb_headers_from_curl_handle(ez_handle);
334-
335-
insert_success_response(cdata, http_status_code, contentType, jsonb_headers);
336-
337-
pfree_curl_data(cdata);
338-
}
339+
pfree_curl_data(cdata);
339340

340341
int res = curl_multi_remove_handle(curl_mhandle, ez_handle);
341342
if(res != CURLM_OK)
@@ -349,3 +350,4 @@ void insert_curl_responses(WorkerState *wstate, MemoryContext curl_memctx){
349350

350351
MemoryContextSwitchTo(old_ctx);
351352
}
353+

src/pg_prelude.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,14 @@
4848
#pragma GCC diagnostic pop
4949

5050
#define PG15_GTE (PG_VERSION_NUM >= 150000)
51+
#define PG17_LT (PG_VERSION_NUM < 170000)
5152

5253
const char *xact_event_name(XactEvent event);
54+
55+
#if PG17_LT
56+
void destroyStringInfo(StringInfo str);
57+
#endif
58+
5359
#endif /* PG_PRELUDE_H */
5460

5561
#ifdef PG_PRELUDE_IMPL
@@ -68,4 +74,15 @@ const char *xact_event_name(XactEvent event){
6874
}
6975
}
7076

77+
78+
#if PG17_LT
79+
// Polyfill for pg < 17
80+
// see https://github.com/postgres/postgres/blob/3c4e26a62c31ebe296e3aedb13ac51a7a35103bd/src/common/stringinfo.c#L402-L416
81+
void destroyStringInfo(StringInfo str) {
82+
Assert(str->maxlen != 0);
83+
pfree(str->data);
84+
pfree(str);
85+
}
86+
#endif
87+
7188
#endif /* PG_PRELUDE_IMPL */

test/test_http_timeout.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,16 @@ def test_http_get_timeout_reached(sess):
1818
# wait for timeout
1919
time.sleep(7)
2020

21-
(response,) = sess.execute(
21+
(response,timed_out) = sess.execute(
2222
text(
2323
"""
24-
select error_msg from net._http_response where id = :request_id;
24+
select error_msg, timed_out from net._http_response where id = :request_id;
2525
"""
2626
),
2727
{"request_id": request_id},
2828
).fetchone()
2929

30+
assert timed_out == True
3031
assert response.startswith("Timeout of 5000 ms reached")
3132

3233

@@ -66,10 +67,10 @@ def test_http_detailed_timeout(sess):
6667
# wait for timeout
6768
time.sleep(2.1)
6869

69-
(response,) = sess.execute(
70+
(response, timed_out) = sess.execute(
7071
text(
7172
"""
72-
select error_msg from net._http_response where id = :request_id;
73+
select error_msg, timed_out from net._http_response where id = :request_id;
7374
"""
7475
),
7576
{"request_id": request_id},
@@ -82,6 +83,7 @@ def test_http_detailed_timeout(sess):
8283
tcp_ssl_time = float(match.group('C'))
8384
http_time = float(match.group('D'))
8485

86+
assert timed_out == True
8587
assert total_time > 0
8688
assert dns_time > 0
8789
assert tcp_ssl_time > 0

0 commit comments

Comments
 (0)