Skip to content

Commit 2164bfc

Browse files
Refactor heartbeat to shutdown cleanly (#1135)
* Refactor heartbeat to shutdown cleanly From ZMQ docs: "zmq_proxy() runs in the current thread and returns only if/when the current context is closed." The heartbeat socket doesn't need to be global, as nothing else touches it. BUT, if we create the heartbeat socket in a `Context` that has a global ref, we can close the context, which will cause zmq_proxy to return and then that thread to end/finish. Doing that before shutting down helps avoid a segfault on shutdown. * Update src/heartbeat.jl Co-authored-by: Steven G. Johnson <[email protected]> * Create heartbeat and context in `init.jl` --------- Co-authored-by: Steven G. Johnson <[email protected]>
1 parent eac46ab commit 2164bfc

File tree

4 files changed

+18
-12
lines changed

4 files changed

+18
-12
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@ Conda = "1"
2626
JSON = "0.18,0.19,0.20,0.21,1"
2727
MbedTLS = "0.5,0.6,0.7,1"
2828
SoftGlobalScope = "1"
29-
ZMQ = "1"
29+
ZMQ = "1.3"
3030
julia = "1.6"

src/handlers.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ function connect_request(socket, msg)
189189
end
190190

191191
function shutdown_request(socket, msg)
192+
# stop heartbeat thread by closing the context
193+
close(heartbeat_context[])
194+
192195
send_ipython(requests[], msg_reply(msg, "shutdown_reply",
193196
msg.content))
194197
sleep(0.1) # short delay (like in ipykernel), to hopefully ensure shutdown_reply is sent

src/heartbeat.jl

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@
77
import Libdl
88

99
const threadid = zeros(Int, 128) # sizeof(uv_thread_t) <= 8 on Linux, OSX, Win
10-
const zmq_proxy = Ref(C_NULL)
1110

1211
# entry point for new thread
13-
function heartbeat_thread(sock::Ptr{Cvoid})
12+
function heartbeat_thread(heartbeat::Ptr{Cvoid})
1413
@static if VERSION v"1.9.0-DEV.1588" # julia#46609
1514
# julia automatically "adopts" this thread because
1615
# we entered a Julia cfunction. We then have to enable
@@ -19,14 +18,16 @@ function heartbeat_thread(sock::Ptr{Cvoid})
1918
# (see julia#47196)
2019
ccall(:jl_gc_safe_enter, Int8, ())
2120
end
22-
ccall(zmq_proxy[], Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}),
23-
sock, sock, C_NULL)
24-
nothing
21+
ret = ZMQ.lib.zmq_proxy(heartbeat, heartbeat, C_NULL)
22+
@static if VERSION v"1.9.0-DEV.1588" # julia#46609
23+
# leave safe region if zmq_proxy returns (when context is closed)
24+
ccall(:jl_gc_safe_leave, Int8, ())
25+
end
26+
return ret
2527
end
2628

27-
function start_heartbeat(sock)
28-
zmq_proxy[] = Libdl.dlsym(Libdl.dlopen(ZMQ.libzmq), :zmq_proxy)
29-
heartbeat_c = @cfunction(heartbeat_thread, Cvoid, (Ptr{Cvoid},))
29+
function start_heartbeat(heartbeat)
30+
heartbeat_c = @cfunction(heartbeat_thread, Cint, (Ptr{Cvoid},))
3031
ccall(:uv_thread_create, Cint, (Ptr{Int}, Ptr{Cvoid}, Ptr{Cvoid}),
31-
threadid, heartbeat_c, sock)
32+
threadid, heartbeat_c, heartbeat)
3233
end

src/init.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const raw_input = Ref{Socket}()
2525
const requests = Ref{Socket}()
2626
const control = Ref{Socket}()
2727
const heartbeat = Ref{Socket}()
28+
const heartbeat_context = Ref{Context}()
2829
const profile = Dict{String,Any}()
2930
const read_stdout = Ref{Base.PipeEndpoint}()
3031
const read_stderr = Ref{Base.PipeEndpoint}()
@@ -87,7 +88,8 @@ function init(args)
8788
raw_input[] = Socket(ROUTER)
8889
requests[] = Socket(ROUTER)
8990
control[] = Socket(ROUTER)
90-
heartbeat[] = Socket(ROUTER)
91+
heartbeat_context[] = Context()
92+
heartbeat = Socket(heartbeat_context[], ROUTER)
9193
sep = profile["transport"]=="ipc" ? "-" : ":"
9294
bind(publish[], "$(profile["transport"])://$(profile["ip"])$(sep)$(profile["iopub_port"])")
9395
bind(requests[], "$(profile["transport"])://$(profile["ip"])$(sep)$(profile["shell_port"])")
@@ -97,7 +99,7 @@ function init(args)
9799

98100
# associate a lock with each socket so that multi-part messages
99101
# on a given socket don't get inter-mingled between tasks.
100-
for s in (publish[], raw_input[], requests[], control[], heartbeat[])
102+
for s in (publish[], raw_input[], requests[], control[])
101103
socket_locks[s] = ReentrantLock()
102104
end
103105

0 commit comments

Comments
 (0)