Skip to content

Commit 237202e

Browse files
REPL: use an Event to orchestrate prompt entry in precompile script
1 parent b332132 commit 237202e

File tree

3 files changed

+66
-66
lines changed

3 files changed

+66
-66
lines changed

stdlib/REPL/src/LineEdit.jl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,11 @@ mutable struct MIState
8585
line_modify_lock::Base.ReentrantLock
8686
hint_generation_lock::Base.ReentrantLock
8787
n_keys_pressed::Int
88+
# Optional event that gets notified each time the prompt is ready for input
89+
prompt_ready_event::Union{Nothing, Base.Event}
8890
end
8991

90-
MIState(i, mod, c, a, m) = MIState(i, mod, mod, c, a, m, String[], 0, Char[], 0, :none, :none, Channel{Function}(), Base.ReentrantLock(), Base.ReentrantLock(), 0)
92+
MIState(i, mod, c, a, m) = MIState(i, mod, mod, c, a, m, String[], 0, Char[], 0, :none, :none, Channel{Function}(), Base.ReentrantLock(), Base.ReentrantLock(), 0, nothing)
9193

9294
const BufferLike = Union{MIState,ModeState,IOBuffer}
9395
const State = Union{MIState,ModeState}
@@ -2977,6 +2979,10 @@ function prompt!(term::TextTerminal, prompt::ModalInterface, s::MIState = init_s
29772979
enable_bracketed_paste(term)
29782980
try
29792981
activate(prompt, s, term, term)
2982+
# Notify that prompt is ready for input
2983+
if s.prompt_ready_event !== nothing
2984+
notify(s.prompt_ready_event)
2985+
end
29802986
old_state = mode(s)
29812987
# spawn this because the main repl task is sticky (due to use of @async and _wait2)
29822988
# and we want to not block typing when the repl task thread is busy

stdlib/REPL/src/REPL.jl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,14 +779,18 @@ mutable struct LineEditREPL <: AbstractREPL
779779
interface::ModalInterface
780780
backendref::REPLBackendRef
781781
frontend_task::Task
782+
# Optional event to notify when the prompt is ready (used by precompilation)
783+
prompt_ready_event::Union{Nothing, Base.Event}
782784
function LineEditREPL(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,in_help,envcolors)
783785
opts = Options()
784786
opts.hascolor = hascolor
785787
if !hascolor
786788
opts.beep_colors = [""]
787789
end
788-
new(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,
790+
r = new(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,pkg_color,history_file,in_shell,
789791
in_help,envcolors,false,nothing, opts, nothing, Tuple{String,Int}[])
792+
r.prompt_ready_event = nothing
793+
r
790794
end
791795
end
792796
outstream(r::LineEditREPL) = (t = r.t; t isa TTYTerminal ? t.out_stream : t)
@@ -1665,6 +1669,10 @@ function run_frontend(repl::LineEditREPL, backend::REPLBackendRef)
16651669
end
16661670
repl.backendref = backend
16671671
repl.mistate = LineEdit.init_state(terminal(repl), interface)
1672+
# Copy prompt_ready_event from repl to mistate (used by precompilation)
1673+
if isdefined(repl, :prompt_ready_event) && repl.prompt_ready_event !== nothing
1674+
repl.mistate.prompt_ready_event = repl.prompt_ready_event
1675+
end
16681676
run_interface(terminal(repl), interface, repl.mistate)
16691677
# Terminate Backend
16701678
put!(backend.repl_channel, (nothing, -1))

stdlib/REPL/src/precompile.jl

Lines changed: 50 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -15,42 +15,56 @@ finally
1515
end
1616

1717
function repl_workload()
18-
# these are intentionally triggered
18+
# Capture debug output to show if something goes wrong
19+
debug_output = IOBuffer()
20+
21+
# Errors that are intentionally triggered by the script
1922
allowed_errors = [
2023
"BoundsError: attempt to access 0-element Vector{Any} at index [1]",
2124
"MethodError: no method matching f(::$Int, ::$Int)",
2225
"Padding of type", # reinterpret docstring has ERROR examples
2326
]
24-
function check_errors(out)
25-
str = String(out)
26-
if occursin("ERROR:", str) && !any(occursin(e, str) for e in allowed_errors)
27-
@error "Unexpected error (Review REPL precompilation with debug_output on):\n$str" exception=(
28-
Base.PrecompilableError(), Base.backtrace())
29-
exit(1)
27+
28+
function check_output()
29+
str = String(take!(copy(debug_output)))
30+
for line in eachline(IOBuffer(str))
31+
if occursin("ERROR:", line) && !any(e -> occursin(e, line), allowed_errors)
32+
println(stderr, """
33+
========================================================================
34+
ERROR: Unexpected error during REPL precompilation
35+
========================================================================
36+
Debug output:
37+
------------------------------------------------------------------------
38+
""")
39+
println(stderr, str)
40+
println(stderr, "========================================================================")
41+
error("REPL precompilation encountered unexpected error: $line")
42+
end
3043
end
3144
end
32-
## Debugging options
33-
# View the code sent to the repl by setting this to `stdout`
34-
debug_output = devnull # or stdout
3545

3646
CTRL_C = '\x03'
3747
CTRL_D = '\x04'
3848
CTRL_R = '\x12'
3949
UP_ARROW = "\e[A"
4050
DOWN_ARROW = "\e[B"
4151

42-
# This is notified as soon as the first prompt appears
43-
repl_init_event = Base.Event()
44-
repl_init_done_event = Base.Event()
52+
# Event that REPL notifies each time it's ready for input
53+
prompt_ready = Base.Event()
54+
# Event to signal that REPL.activate has been called
55+
activate_done = Base.Event()
4556

4657
atreplinit() do repl
47-
# Main is closed so we can't evaluate in it, but atreplinit runs at
48-
# a time that repl.mistate === nothing so REPL.activate fails. So do
49-
# it async and wait for the first prompt to know its ready.
58+
# Set the prompt_ready_event on the repl - run_frontend will copy it to mistate
59+
if repl isa REPL.LineEditREPL
60+
repl.prompt_ready_event = prompt_ready
61+
end
62+
# Start async task to wait for first prompt then activate the module
5063
t = @async begin
51-
wait(repl_init_event)
64+
wait(prompt_ready)
65+
Base.reset(prompt_ready)
5266
REPL.activate(REPL.Precompile; interactive_utils=false)
53-
notify(repl_init_done_event)
67+
notify(activate_done)
5468
end
5569
Base.errormonitor(t)
5670
end
@@ -83,14 +97,6 @@ function repl_workload()
8397
println("done")
8498
"""
8599

86-
JULIA_PROMPT = "julia> "
87-
# The help text for `reinterpret` has example `julia>` prompts in it,
88-
# so use the longer prompt to avoid desychronization.
89-
ACTIVATED_JULIA_PROMPT = "(REPL.Precompile) julia> "
90-
PKG_PROMPT = "pkg> "
91-
SHELL_PROMPT = "shell> "
92-
HELP_PROMPT = "help?> "
93-
94100
tmphistfile = tempname()
95101
write(tmphistfile, """
96102
# time: 2020-10-31 13:16:39 AWST
@@ -120,20 +126,16 @@ function repl_workload()
120126
Base._fd(pts) == rawpts || Base.close_stdio(rawpts)
121127
end
122128
# Prepare a background process to copy output from `ptm` until `pts` is closed
123-
output_copy = Base.BufferStream()
124129
tee = @async try
125130
while !eof(ptm)
126131
l = readavailable(ptm)
127132
write(debug_output, l)
128-
write(output_copy, l)
129133
end
130-
write(debug_output, "\n#### EOF ####\n")
131134
catch ex
132135
if !(ex isa Base.IOError && ex.code == Base.UV_EIO)
133136
rethrow() # ignore EIO on ptm after pts dies
134137
end
135138
finally
136-
close(output_copy)
137139
close(ptm)
138140
end
139141
Base.errormonitor(tee)
@@ -159,46 +161,29 @@ function repl_workload()
159161
redirect_stderr(isopen(orig_stderr) ? orig_stderr : devnull)
160162
end
161163
schedule(repltask)
162-
# wait for the definitive prompt before start writing to the TTY
163-
check_errors(readuntil(output_copy, JULIA_PROMPT, keep=true))
164-
165-
# Switch to the activated prompt
166-
notify(repl_init_event)
167-
wait(repl_init_done_event)
164+
# Wait for the first prompt, then for activate to complete
165+
wait(activate_done)
166+
# Send a newline to get the activated prompt
168167
write(ptm, "\n")
169-
# The prompt prints twice - once for the restatement of the input, once
170-
# to indicate ready for the new prompt.
171-
check_errors(readuntil(output_copy, ACTIVATED_JULIA_PROMPT, keep=true))
172-
check_errors(readuntil(output_copy, ACTIVATED_JULIA_PROMPT, keep=true))
168+
# Wait for the new prompt to be ready
169+
wait(prompt_ready)
170+
Base.reset(prompt_ready)
173171

174-
write(debug_output, "\n#### REPL STARTED ####\n")
175172
# Input our script
176173
precompile_lines = split(repl_script::String, '\n'; keepempty=false)
177-
curr = 0
178174
for l in precompile_lines
179-
sleep(0.01) # try to let a bit of output accumulate before reading again
180-
curr += 1
181-
# push our input
182-
write(debug_output, "\n#### inputting statement: ####\n$(repr(l))\n####\n")
183-
# If the line ends with a CTRL_C, don't write an extra newline, which would
184-
# cause a second empty prompt. Our code below expects one new prompt per
185-
# input line and can race out of sync with the unexpected second line.
186-
endswith(l, CTRL_C) ? write(ptm, l) : write(ptm, l, "\n")
187-
check_errors(readuntil(output_copy, "\n"))
188-
# wait for the next prompt-like to appear
189-
check_errors(readuntil(output_copy, "\n"))
190-
strbuf = ""
191-
while !eof(output_copy)
192-
strbuf *= String(readavailable(output_copy))
193-
occursin(ACTIVATED_JULIA_PROMPT, strbuf) && break
194-
occursin(PKG_PROMPT, strbuf) && break
195-
occursin(SHELL_PROMPT, strbuf) && break
196-
occursin(HELP_PROMPT, strbuf) && break
197-
sleep(0.01) # try to let a bit of output accumulate before reading again
175+
# If the line ends with a CTRL_C, don't write an extra newline
176+
# CTRL_C cancels input but doesn't print a new prompt, so don't wait
177+
if endswith(l, CTRL_C)
178+
write(ptm, l)
179+
sleep(0.1) # Brief pause to let CTRL_C be processed
180+
else
181+
write(ptm, l, "\n")
182+
# Wait for REPL to signal it's ready for next input
183+
wait(prompt_ready)
184+
Base.reset(prompt_ready)
198185
end
199-
check_errors(strbuf)
200186
end
201-
write(debug_output, "\n#### COMPLETED - Closing REPL ####\n")
202187
write(ptm, "$CTRL_D")
203188
wait(repltask)
204189
finally
@@ -208,7 +193,8 @@ function repl_workload()
208193
end
209194
wait(tee)
210195
end
211-
write(debug_output, "\n#### FINISHED ####\n")
196+
# Check for any unexpected errors in the output
197+
check_output()
212198
nothing
213199
end
214200

0 commit comments

Comments
 (0)