Skip to content

Commit ac80772

Browse files
add concurrent connection semaphore to prevent DNS resolve races (#205)
The libcurl spawns a new thread for each DNS resolution that it needs to do, which can cause problems when too many connections are started at the same time. This adds a semaphore which is acqured when a connection starts and gets released once the actual request starts. This limits the number of concurrent requests that are in the connecting state to the size of the semaphore (16). Hopefully this prevents the "getaddrinfo() thread failed" errors that occur sometimes.
1 parent 0733701 commit ac80772

File tree

3 files changed

+49
-0
lines changed

3 files changed

+49
-0
lines changed

src/Curl/Curl.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ using LibCURL: curl_off_t, libcurl
3333
# constants that LibCURL should have but doesn't
3434
const CURLE_PEER_FAILED_VERIFICATION = 60
3535
const CURLSSLOPT_REVOKE_BEST_EFFORT = 1 << 3
36+
const CURLOPT_PREREQFUNCTION = 20000 + 312
37+
const CURLOPT_PREREQDATA = 10000 + 313
3638

3739
# these are incorrectly defined on Windows by LibCURL:
3840
if Sys.iswindows()

src/Curl/Easy.jl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mutable struct Easy
1010
code :: CURLcode
1111
errbuf :: Vector{UInt8}
1212
debug :: Union{Function,Nothing}
13+
consem :: Bool
1314
end
1415

1516
const EMPTY_BYTE_VECTOR = UInt8[]
@@ -27,6 +28,7 @@ function Easy()
2728
typemax(CURLcode),
2829
zeros(UInt8, CURL_ERROR_SIZE),
2930
nothing,
31+
false,
3032
)
3133
finalizer(done!, easy)
3234
add_callbacks(easy)
@@ -35,13 +37,38 @@ function Easy()
3537
end
3638

3739
function done!(easy::Easy)
40+
connect_semaphore_release(easy)
3841
easy.handle == C_NULL && return
3942
curl_easy_cleanup(easy.handle)
4043
curl_slist_free_all(easy.req_hdrs)
4144
easy.handle = C_NULL
4245
return
4346
end
4447

48+
# connect semaphore
49+
50+
# This semaphore limits the number of requests that can be in the connecting
51+
# state at any given time, globally. Throttling this prevents libcurl from
52+
# trying to start too many DNS resolver threads concurrently. It also helps
53+
# ensure that not-yet-started requests get ßa chance to make some progress
54+
# before adding more events from new requests to the system's workload.
55+
56+
const CONNECT_SEMAPHORE = Base.Semaphore(16) # empirically chosen (ie guessed)
57+
58+
function connect_semaphore_acquire(easy::Easy)
59+
@assert !easy.consem
60+
Base.acquire(CONNECT_SEMAPHORE)
61+
easy.consem = true
62+
return
63+
end
64+
65+
function connect_semaphore_release(easy::Easy)
66+
easy.consem || return
67+
Base.release(CONNECT_SEMAPHORE)
68+
easy.consem = false
69+
return
70+
end
71+
4572
# request options
4673

4774
function set_defaults(easy::Easy)
@@ -309,6 +336,18 @@ end
309336

310337
# callbacks
311338

339+
function prereq_callback(
340+
easy_p :: Ptr{Cvoid},
341+
conn_remote_ip :: Ptr{Cchar},
342+
conn_local_ip :: Ptr{Cchar},
343+
conn_remote_port :: Cint,
344+
conn_local_port :: Cint,
345+
)::Cint
346+
easy = unsafe_pointer_to_objref(easy_p)::Easy
347+
connect_semaphore_release(easy)
348+
return 0
349+
end
350+
312351
function header_callback(
313352
data :: Ptr{Cchar},
314353
size :: Csize_t,
@@ -424,6 +463,12 @@ function add_callbacks(easy::Easy)
424463
errbuf_p = pointer(easy.errbuf)
425464
setopt(easy, CURLOPT_ERRORBUFFER, errbuf_p)
426465

466+
# set pre-request callback
467+
prereq_cb = @cfunction(prereq_callback,
468+
Cint, (Ptr{Cvoid}, Ptr{Cchar}, Ptr{Cchar}, Cint, Cint))
469+
setopt(easy, CURLOPT_PREREQFUNCTION, prereq_cb)
470+
setopt(easy, CURLOPT_PREREQDATA, easy_p)
471+
427472
# set header callback
428473
header_cb = @cfunction(header_callback,
429474
Csize_t, (Ptr{Cchar}, Csize_t, Csize_t, Ptr{Cvoid}))

src/Curl/Multi.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ end
4141
# adding & removing easy handles
4242

4343
function add_handle(multi::Multi, easy::Easy)
44+
connect_semaphore_acquire(easy)
4445
lock(multi.lock) do
4546
if isempty(multi.easies)
4647
preserve_handle(multi)
@@ -70,6 +71,7 @@ function remove_handle(multi::Multi, easy::Easy)
7071
end
7172
unpreserve_handle(multi)
7273
end
74+
connect_semaphore_release(easy)
7375
end
7476

7577
# multi-socket options

0 commit comments

Comments
 (0)