Skip to content

Commit

Permalink
src/sage/misc/latex.py: fix view()
Browse files Browse the repository at this point in the history
Rewriting the view() function to use python's tempfile module instead
of sage's own tmp_dir() accidentally broke this function, because the
viewer program needs the file to be slightly less temporary than we
made it. The viewer would launch, but then view() would immediately
return, causing the file that the viewer was about to look for to be
deleted.

To fix it, we now launch the viewer program synchronously within a
helper function, but launch that helper function asynchronously in a
new thread. This allows us to clean up the temporary directory only
after the subprocess has completed, but still lets view() return
immediately.

Closes: #36526
  • Loading branch information
orlitzky committed Oct 24, 2023
1 parent 07a2afd commit a859ac2
Showing 1 changed file with 43 additions and 22 deletions.
65 changes: 43 additions & 22 deletions src/sage/misc/latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import random
import re
import shutil
from subprocess import call, PIPE
from subprocess import call, run, PIPE
from tempfile import TemporaryDirectory

from sage.misc.cachefunc import cached_function, cached_method
Expand Down Expand Up @@ -1923,27 +1923,48 @@ def view(objects, title='Sage', debug=False, sep='', tiny=False,
if pdflatex or (viewer == "pdf" and engine == "latex"):
engine = "pdflatex"
# command line or notebook with viewer
with TemporaryDirectory() as tmp:
tex_file = os.path.join(tmp, "sage.tex")
with open(tex_file, 'w') as file:
file.write(s)
suffix = _run_latex_(tex_file, debug=debug, engine=engine, png=False)
if suffix == "pdf":
from sage.misc.viewer import pdf_viewer
viewer = pdf_viewer()
elif suffix == "dvi":
from sage.misc.viewer import dvi_viewer
viewer = dvi_viewer()
else:
print("Latex error")
return
output_file = os.path.join(tmp, "sage." + suffix)
# this should get changed if we switch the stuff in misc.viewer to
# producing lists
if debug:
print('viewer: "{}"'.format(viewer))
call('%s %s' % (viewer, output_file), shell=True,
stdout=PIPE, stderr=PIPE)

# We can't automatically delete the temporary file in this case
# because we need it to live at least long enough for the viewer
# to open it. Since we're launching the viewer asynchronously,
# that would be tricky.
tmp = TemporaryDirectory()

tex_file = os.path.join(tmp.name, "sage.tex")
with open(tex_file, 'w') as file:
file.write(s)
suffix = _run_latex_(tex_file, debug=debug, engine=engine, png=False)
if suffix == "pdf":
from sage.misc.viewer import pdf_viewer
viewer = pdf_viewer()
elif suffix == "dvi":
from sage.misc.viewer import dvi_viewer
viewer = dvi_viewer()
else:
print("Latex error")
tmp.cleanup()
return
output_file = os.path.join(tmp.name, "sage." + suffix)
# this should get changed if we switch the stuff in misc.viewer to
# producing lists
if debug:
print('viewer: "{}"'.format(viewer))

# Return immediately but only clean up the temporary file after
# the viewer has closed. This function is synchronous and waits
# for the process to complete...
def run_viewer():
run([viewer, output_file], capture_output=True)
tmp.cleanup()

# ...but we execute it asynchronously so that view() completes
# immediately. The "daemon" flag is important because, without it,
# sage won't quit until the viewer does.
from threading import Thread
t = Thread(target=run_viewer)
t.daemon = True
t.start()

return


Expand Down

0 comments on commit a859ac2

Please sign in to comment.