Skip to content

Commit c91876a

Browse files
authored
Revert "stop using raw libuv API" (#156)
1 parent dbb0625 commit c91876a

File tree

4 files changed

+146
-76
lines changed

4 files changed

+146
-76
lines changed

Project.toml

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ version = "1.5.1"
55

66
[deps]
77
ArgTools = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
8-
FileWatching = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
98
LibCURL = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
109
NetworkOptions = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
1110

src/Curl/Curl.jl

+2-25
Original file line numberDiff line numberDiff line change
@@ -26,38 +26,15 @@ export
2626
remove_handle
2727

2828
using LibCURL
29-
using LibCURL: curl_off_t, libcurl
29+
using LibCURL: curl_off_t
3030
# not exported: https://github.com/JuliaWeb/LibCURL.jl/issues/87
3131

3232
# constants that LibCURL should have but doesn't
3333
const CURLE_PEER_FAILED_VERIFICATION = 60
3434
const CURLSSLOPT_REVOKE_BEST_EFFORT = 1 << 3
3535

36-
# these are incorrectly defined on Windows by LibCURL:
37-
if Sys.iswindows()
38-
const curl_socket_t = Base.OS_HANDLE
39-
const CURL_SOCKET_TIMEOUT = Base.INVALID_OS_HANDLE
40-
else
41-
const curl_socket_t = Cint
42-
const CURL_SOCKET_TIMEOUT = -1
43-
end
44-
45-
# definitions affected by incorrect curl_socket_t (copied verbatim):
46-
function curl_multi_socket_action(multi_handle, s, ev_bitmask, running_handles)
47-
ccall((:curl_multi_socket_action, libcurl), CURLMcode, (Ptr{CURLM}, curl_socket_t, Cint, Ptr{Cint}), multi_handle, s, ev_bitmask, running_handles)
48-
end
49-
function curl_multi_assign(multi_handle, sockfd, sockp)
50-
ccall((:curl_multi_assign, libcurl), CURLMcode, (Ptr{CURLM}, curl_socket_t, Ptr{Cvoid}), multi_handle, sockfd, sockp)
51-
end
52-
53-
# additional curl_multi_socket_action method
54-
function curl_multi_socket_action(multi_handle, s, ev_bitmask)
55-
curl_multi_socket_action(multi_handle, s, ev_bitmask, Ref{Cint}())
56-
end
57-
58-
using FileWatching
5936
using NetworkOptions
60-
using Base: OS_HANDLE, preserve_handle, unpreserve_handle
37+
using Base: preserve_handle, unpreserve_handle
6138

6239
include("utils.jl")
6340

src/Curl/Multi.jl

+77-45
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
mutable struct Multi
22
lock :: ReentrantLock
33
handle :: Ptr{Cvoid}
4-
timer :: Timer
4+
timer :: Ptr{Cvoid}
55
easies :: Vector{Easy}
66
grace :: UInt64
77

88
function Multi(grace::Integer = typemax(UInt64))
9-
multi = new(ReentrantLock(), C_NULL, Timer(0), Easy[], grace)
9+
timer = jl_malloc(Base._sizeof_uv_timer)
10+
uv_timer_init(timer)
11+
multi = new(ReentrantLock(), C_NULL, timer, Easy[], grace)
1012
finalizer(multi) do multi
11-
close(multi.timer)
13+
uv_timer_stop(multi.timer)
14+
uv_close(multi.timer, cglobal(:jl_free))
1215
done!(multi)
1316
end
1417
end
@@ -29,11 +32,19 @@ end
2932

3033
# adding & removing easy handles
3134

35+
function cleanup_callback(uv_timer_p::Ptr{Cvoid})::Cvoid
36+
## TODO: use a member access API
37+
multi_p = unsafe_load(convert(Ptr{Ptr{Cvoid}}, uv_timer_p))
38+
multi = unsafe_pointer_to_objref(multi_p)::Multi
39+
done!(multi)
40+
return
41+
end
42+
3243
function add_handle(multi::Multi, easy::Easy)
3344
lock(multi.lock) do
3445
if isempty(multi.easies)
3546
preserve_handle(multi)
36-
close(multi.timer) # stop grace timer
47+
uv_timer_stop(multi.timer) # stop grace timer
3748
end
3849
push!(multi.easies, easy)
3950
init!(multi)
@@ -46,14 +57,11 @@ function remove_handle(multi::Multi, easy::Easy)
4657
@check curl_multi_remove_handle(multi.handle, easy.handle)
4758
deleteat!(multi.easies, findlast(==(easy), multi.easies)::Int)
4859
!isempty(multi.easies) && return
60+
cleanup_cb = @cfunction(cleanup_callback, Cvoid, (Ptr{Cvoid},))
4961
if multi.grace <= 0
5062
done!(multi)
5163
elseif 0 < multi.grace < typemax(multi.grace)
52-
multi.timer = Timer(multi.grace/1000)
53-
@async begin
54-
wait(multi.timer)
55-
isopen(multi.timer) && done!(multi)
56-
end
64+
uv_timer_start(multi.timer, cleanup_cb, multi.grace, 0)
5765
end
5866
unpreserve_handle(multi)
5967
end
@@ -65,14 +73,15 @@ function set_defaults(multi::Multi)
6573
# currently no defaults
6674
end
6775

68-
# multi-socket handle state updates
76+
# libuv callbacks
6977

7078
struct CURLMsg
7179
msg :: CURLMSG
7280
easy :: Ptr{Cvoid}
7381
code :: CURLcode
7482
end
7583

84+
# should already be locked
7685
function check_multi_info(multi::Multi)
7786
while true
7887
p = curl_multi_info_read(multi.handle, Ref{Cint}())
@@ -95,15 +104,37 @@ function check_multi_info(multi::Multi)
95104
end
96105
end
97106

98-
# curl callbacks
107+
function event_callback(
108+
uv_poll_p :: Ptr{Cvoid},
109+
status :: Cint,
110+
events :: Cint,
111+
)::Cvoid
112+
## TODO: use a member access API
113+
multi_p = unsafe_load(convert(Ptr{Ptr{Cvoid}}, uv_poll_p))
114+
multi = unsafe_pointer_to_objref(multi_p)::Multi
115+
sock_p = uv_poll_p + Base._sizeof_uv_poll
116+
sock = unsafe_load(convert(Ptr{curl_socket_t}, sock_p))
117+
flags = 0
118+
events & UV_READABLE != 0 && (flags |= CURL_CSELECT_IN)
119+
events & UV_WRITABLE != 0 && (flags |= CURL_CSELECT_OUT)
120+
lock(multi.lock) do
121+
@check curl_multi_socket_action(multi.handle, sock, flags)
122+
check_multi_info(multi)
123+
end
124+
end
99125

100-
function do_multi(multi::Multi)
126+
function timeout_callback(uv_timer_p::Ptr{Cvoid})::Cvoid
127+
## TODO: use a member access API
128+
multi_p = unsafe_load(convert(Ptr{Ptr{Cvoid}}, uv_timer_p))
129+
multi = unsafe_pointer_to_objref(multi_p)::Multi
101130
lock(multi.lock) do
102131
@check curl_multi_socket_action(multi.handle, CURL_SOCKET_TIMEOUT, 0)
103132
check_multi_info(multi)
104133
end
105134
end
106135

136+
# curl callbacks
137+
107138
function timer_callback(
108139
multi_h :: Ptr{Cvoid},
109140
timeout_ms :: Clong,
@@ -112,13 +143,15 @@ function timer_callback(
112143
multi = unsafe_pointer_to_objref(multi_p)::Multi
113144
@assert multi_h == multi.handle
114145
if timeout_ms == 0
115-
do_multi(multi)
116-
elseif timeout_ms >= 0
117-
multi.timer = Timer(timeout_ms/1000) do timer
118-
do_multi(multi)
146+
lock(multi.lock) do
147+
@check curl_multi_socket_action(multi.handle, CURL_SOCKET_TIMEOUT, 0)
148+
check_multi_info(multi)
119149
end
150+
elseif timeout_ms >= 0
151+
timeout_cb = @cfunction(timeout_callback, Cvoid, (Ptr{Cvoid},))
152+
uv_timer_start(multi.timer, timeout_cb, max(1, timeout_ms), 0)
120153
elseif timeout_ms == -1
121-
close(multi.timer)
154+
uv_timer_stop(multi.timer)
122155
else
123156
@async @error("timer_callback: invalid timeout value", timeout_ms)
124157
return -1
@@ -131,47 +164,46 @@ function socket_callback(
131164
sock :: curl_socket_t,
132165
action :: Cint,
133166
multi_p :: Ptr{Cvoid},
134-
watcher_p :: Ptr{Cvoid},
167+
uv_poll_p :: Ptr{Cvoid},
135168
)::Cint
136-
if action (CURL_POLL_IN, CURL_POLL_OUT, CURL_POLL_INOUT, CURL_POLL_REMOVE)
137-
@async @error("socket_callback: unexpected action", action)
138-
return -1
139-
end
140169
multi = unsafe_pointer_to_objref(multi_p)::Multi
141-
if watcher_p != C_NULL
142-
old_watcher = unsafe_pointer_to_objref(watcher_p)::FDWatcher
143-
@check curl_multi_assign(multi.handle, sock, C_NULL)
144-
unpreserve_handle(old_watcher)
145-
end
146170
if action in (CURL_POLL_IN, CURL_POLL_OUT, CURL_POLL_INOUT)
147-
readable = action in (CURL_POLL_IN, CURL_POLL_INOUT)
148-
writable = action in (CURL_POLL_OUT, CURL_POLL_INOUT)
149-
watcher = FDWatcher(OS_HANDLE(sock), readable, writable)
150-
preserve_handle(watcher)
151-
watcher_p = pointer_from_objref(watcher)
152-
@check curl_multi_assign(multi.handle, sock, watcher_p)
153-
task = @async while true
154-
events = try wait(watcher)
155-
catch err
156-
err isa EOFError && break
157-
rethrow()
171+
if uv_poll_p == C_NULL
172+
uv_poll_p = uv_poll_alloc()
173+
uv_poll_init(uv_poll_p, sock)
174+
## TODO: use a member access API
175+
unsafe_store!(convert(Ptr{Ptr{Cvoid}}, uv_poll_p), multi_p)
176+
sock_p = uv_poll_p + Base._sizeof_uv_poll
177+
unsafe_store!(convert(Ptr{curl_socket_t}, sock_p), sock)
178+
lock(multi.lock) do
179+
@check curl_multi_assign(multi.handle, sock, uv_poll_p)
158180
end
159-
flags = CURL_CSELECT_IN * isreadable(events) +
160-
CURL_CSELECT_OUT * iswritable(events) +
161-
CURL_CSELECT_ERR * events.disconnect
181+
end
182+
events = 0
183+
action != CURL_POLL_IN && (events |= UV_WRITABLE)
184+
action != CURL_POLL_OUT && (events |= UV_READABLE)
185+
event_cb = @cfunction(event_callback, Cvoid, (Ptr{Cvoid}, Cint, Cint))
186+
uv_poll_start(uv_poll_p, events, event_cb)
187+
elseif action == CURL_POLL_REMOVE
188+
if uv_poll_p != C_NULL
189+
uv_poll_stop(uv_poll_p)
190+
uv_close(uv_poll_p, cglobal(:jl_free))
162191
lock(multi.lock) do
163-
@check curl_multi_socket_action(multi.handle, sock, flags)
164-
check_multi_info(multi)
192+
@check curl_multi_assign(multi.handle, sock, C_NULL)
165193
end
166194
end
167-
@isdefined(errormonitor) && errormonitor(task)
195+
else
196+
@async @error("socket_callback: unexpected action", action)
197+
return -1
168198
end
169-
@isdefined(old_watcher) && close(old_watcher)
170199
return 0
171200
end
172201

173202
function add_callbacks(multi::Multi)
203+
# stash multi handle pointer in timer
174204
multi_p = pointer_from_objref(multi)
205+
## TODO: use a member access API
206+
unsafe_store!(convert(Ptr{Ptr{Cvoid}}, multi.timer), multi_p)
175207

176208
# set timer callback
177209
timer_cb = @cfunction(timer_callback, Cint, (Ptr{Cvoid}, Clong, Ptr{Cvoid}))

src/Curl/utils.jl

+67-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1+
# basic C stuff
2+
13
if !@isdefined(contains)
24
contains(haystack, needle) = occursin(needle, haystack)
35
export contains
46
end
57

6-
# basic C stuff
7-
88
puts(s::Union{String,SubString{String}}) = ccall(:puts, Cint, (Ptr{Cchar},), s)
99

1010
jl_malloc(n::Integer) = ccall(:jl_malloc, Ptr{Cvoid}, (Csize_t,), n)
1111

12-
# check if a call failed
12+
# check if a function or C call failed
1313

14-
macro check(ex::Expr)
14+
function check(ex::Expr, lock::Bool)
1515
ex.head == :call ||
1616
error("@check: not a call: $ex")
1717
arg1 = ex.args[1] :: Symbol
@@ -24,13 +24,75 @@ macro check(ex::Expr)
2424
f = arg1
2525
end
2626
prefix = "$f: "
27+
ex = esc(ex)
28+
if lock
29+
ex = quote
30+
Base.iolock_begin()
31+
value = $ex
32+
Base.iolock_end()
33+
value
34+
end
35+
end
2736
quote
28-
r = $(esc(ex))
37+
r = $ex
2938
iszero(r) || @async @error($prefix * string(r))
3039
r
3140
end
3241
end
3342

43+
macro check(ex::Expr) check(ex, false) end
44+
macro check_iolock(ex::Expr) check(ex, true) end
45+
46+
# some libuv wrappers
47+
48+
const UV_READABLE = 1
49+
const UV_WRITABLE = 2
50+
51+
function uv_poll_alloc()
52+
# allocate memory for: uv_poll_t struct + extra for curl_socket_t
53+
jl_malloc(Base._sizeof_uv_poll + sizeof(curl_socket_t))
54+
end
55+
56+
function uv_poll_init(p::Ptr{Cvoid}, sock::curl_socket_t)
57+
@check_iolock ccall(:uv_poll_init, Cint,
58+
(Ptr{Cvoid}, Ptr{Cvoid}, curl_socket_t), Base.eventloop(), p, sock)
59+
end
60+
61+
function uv_poll_start(p::Ptr{Cvoid}, events::Integer, cb::Ptr{Cvoid})
62+
@check_iolock ccall(:uv_poll_start, Cint,
63+
(Ptr{Cvoid}, Cint, Ptr{Cvoid}), p, events, cb)
64+
end
65+
66+
function uv_poll_stop(p::Ptr{Cvoid})
67+
@check_iolock ccall(:uv_poll_stop, Cint, (Ptr{Cvoid},), p)
68+
end
69+
70+
function uv_close(p::Ptr{Cvoid}, cb::Ptr{Cvoid})
71+
Base.iolock_begin()
72+
ccall(:uv_close, Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}), p, cb)
73+
Base.iolock_end()
74+
end
75+
76+
function uv_timer_init(p::Ptr{Cvoid})
77+
@check_iolock ccall(:uv_timer_init, Cint,
78+
(Ptr{Cvoid}, Ptr{Cvoid}), Base.eventloop(), p)
79+
end
80+
81+
function uv_timer_start(p::Ptr{Cvoid}, cb::Ptr{Cvoid}, t::Integer, r::Integer)
82+
@check_iolock ccall(:uv_timer_start, Cint,
83+
(Ptr{Cvoid}, Ptr{Cvoid}, UInt64, UInt64), p, cb, t, r)
84+
end
85+
86+
function uv_timer_stop(p::Ptr{Cvoid})
87+
@check_iolock ccall(:uv_timer_stop, Cint, (Ptr{Cvoid},), p)
88+
end
89+
90+
# additional libcurl methods
91+
92+
function curl_multi_socket_action(multi_handle, s, ev_bitmask)
93+
LibCURL.curl_multi_socket_action(multi_handle, s, ev_bitmask, Ref{Cint}())
94+
end
95+
3496
# curl string list structure
3597

3698
struct curl_slist_t

0 commit comments

Comments
 (0)