Skip to content

Commit 0b6e7be

Browse files
committed
Safely stop the heartbeat thread
Previously we were not ensuring that the heartbeat thread had stopped before exiting, and were closing the context in an unsafe way. This was causing hangs on Mac and segfaults on Windows.
1 parent 11a3438 commit 0b6e7be

File tree

3 files changed

+28
-5
lines changed

3 files changed

+28
-5
lines changed

src/IJulia.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,13 @@ function Base.close(kernel::Kernel)
175175
popdisplay()
176176

177177
# Close all sockets
178-
close(kernel.publish[])
179-
close(kernel.raw_input[])
180178
close(kernel.requests[])
181179
close(kernel.control[])
182-
close(kernel.heartbeat_context[])
180+
close(kernel.publish[])
181+
close(kernel.raw_input[])
182+
183+
# Wait for the heartbeat thread
184+
stop_heartbeat(kernel)
183185

184186
# The waitloop should now be ready to exit
185187
kernel.inited = false

src/handlers.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,8 @@ request](https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-s
214214
sending the reply this will exit the process.
215215
"""
216216
function shutdown_request(socket, kernel, msg)
217-
# stop heartbeat thread by closing the context
218-
close(kernel.heartbeat_context[])
217+
# stop heartbeat thread
218+
stop_heartbeat(kernel)
219219

220220
# In protocol 5.4 the shutdown reply moved to the control socket
221221
shutdown_socket = VersionNumber(msg) >= v"5.4" ? kernel.control[] : kernel.requests[]

src/heartbeat.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,28 @@ function heartbeat_thread(heartbeat::Ptr{Cvoid})
2323
end
2424

2525
function start_heartbeat(heartbeat, kernel)
26+
heartbeat.linger = 0
2627
heartbeat_c = @cfunction(heartbeat_thread, Cint, (Ptr{Cvoid},))
2728
ccall(:uv_thread_create, Cint, (Ptr{Int}, Ptr{Cvoid}, Ptr{Cvoid}),
2829
kernel.heartbeat_threadid, heartbeat_c, heartbeat)
2930
end
31+
32+
function stop_heartbeat(kernel)
33+
if !isopen(kernel.heartbeat_context[])
34+
# Do nothing if it has already been stopped (which can happen in the tests)
35+
return
36+
end
37+
38+
# First we call zmq_ctx_shutdown() to ensure that the zmq_proxy() call
39+
# returns. We don't call ZMQ.close(::Context) directly because that
40+
# currently isn't threadsafe:
41+
# https://github.com/JuliaInterop/ZMQ.jl/issues/256
42+
ZMQ.lib.zmq_ctx_shutdown(kernel.heartbeat_context[])
43+
@ccall uv_thread_join(kernel.heartbeat_threadid::Ptr{Int})::Cint
44+
45+
# Now that the heartbeat thread has joined and its guaranteed to no longer
46+
# be working on the heartbeat socket, we can safely close it and then the
47+
# context.
48+
close(kernel.heartbeat[])
49+
close(kernel.heartbeat_context[])
50+
end

0 commit comments

Comments
 (0)