Skip to content

Commit 0f7f9fd

Browse files
authored
Merge pull request #1163 from JuliaLang/shutdown-deadlock
Fix a deadlock in `shutdown_requests`
2 parents 774a2b9 + 23b97ca commit 0f7f9fd

File tree

5 files changed

+30
-6
lines changed

5 files changed

+30
-6
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "IJulia"
22
uuid = "7073ff75-c697-5162-941a-fcdaad2a7d2a"
3-
version = "1.28.0"
3+
version = "1.28.1"
44

55
[deps]
66
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

docs/src/_changelog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ CurrentModule = IJulia
77
This documents notable changes in IJulia.jl. The format is based on [Keep a
88
Changelog](https://keepachangelog.com).
99

10+
## [v1.28.1]
11+
12+
### Fixed
13+
14+
- Fixed a deadlock in the `shutdown_request` handler that would cause the kernel
15+
to hang when exiting ([#1163]).
16+
1017
## [v1.28.0]
1118

1219
### Added

src/IJulia.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ const capture_stderr = !IJULIA_DEBUG
9393
set_current_module(m::Module) = current_module[] = m
9494
const current_module = Ref{Module}(Main)
9595

96+
_shutting_down::Threads.Atomic{Bool} = Threads.Atomic{Bool}(false)
97+
9698
#######################################################################
9799
include("jupyter.jl")
98100
#######################################################################

src/eventloop.jl

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@ function eventloop(socket)
1313
send_status("busy", msg)
1414
invokelatest(get(handlers, msg.header["msg_type"], unknown_request), socket, msg)
1515
catch e
16-
# Try to keep going if we get an exception, but
17-
# send the exception traceback to the front-ends.
18-
# (Ignore SIGINT since this may just be a user-requested
19-
# kernel interruption to interrupt long calculations.)
20-
if !isa(e, InterruptException)
16+
if e isa InterruptException && _shutting_down[]
17+
# If we're shutting down, just return immediately
18+
return
19+
elseif !isa(e, InterruptException)
20+
# Try to keep going if we get an exception, but
21+
# send the exception traceback to the front-ends.
22+
# (Ignore SIGINT since this may just be a user-requested
23+
# kernel interruption to interrupt long calculations.)
2124
content = error_content(e, msg="KERNEL EXCEPTION")
2225
map(s -> println(orig_stderr[], s), content["traceback"])
2326
send_ipython(publish[], msg_pub(execute_msg, "error", content))
@@ -28,6 +31,10 @@ function eventloop(socket)
2831
end
2932
end
3033
catch e
34+
if _shutting_down[]
35+
return
36+
end
37+
3138
# the Jupyter manager may send us a SIGINT if the user
3239
# chooses to interrupt the kernel; don't crash on this
3340
if isa(e, InterruptException)

src/handlers.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,14 @@ function shutdown_request(socket, msg)
229229
# stop heartbeat thread
230230
stop_heartbeat(heartbeat[], heartbeat_context[])
231231

232+
# Shutdown the `requests` socket handler before sending any messages. This
233+
# is necessary because otherwise the event loop will be calling
234+
# `recv_ipython()` and holding a lock on `requests`, which will cause a
235+
# deadlock when we try to send a message to it from the `control` socket
236+
# handler.
237+
global _shutting_down[] = true
238+
@async Base.throwto(requests_task[], InterruptException())
239+
232240
send_ipython(requests[], msg_reply(msg, "shutdown_reply",
233241
msg.content))
234242
sleep(0.1) # short delay (like in ipykernel), to hopefully ensure shutdown_reply is sent

0 commit comments

Comments
 (0)