Open Nocturnhabeo opened 8 years ago
If I call sys.exit(0) it shouldn't freeze Sublime Text 3.
Well, what was your intention when doing that anyway?
Edit: Oh, this also happens on Windows and has since ST2.
Calling sys.exit is instructing the Python interpreter in plugin_host
to die… which probably seems like a bad idea.
Technically, calling sys.exit
should never happen in plugin code for any reason, but if it does, maybe ST could prevent itself from freezing/hanging up? Otherwise I call "wontfix".
I just had a package calling sys.exit()
, and Sublime Text hanged. I tried to reproduce the hang again, but I could not. Running the command:
sublime.active_window().active_view().run_command( "example" )
import sys
import sublime
import sublime_plugin
class ExampleCommand(sublime_plugin.TextCommand):
def run(self, edit):
sys.exit()
Opens the window on Sublime Text build 3142:
Also, if I put this on plugin_loaded()
:
import sys
import sublime
import sublime_plugin
def plugin_loaded():
pass
sys.exit()
Sublime Text outputs this:
Traceback (most recent call last):
File "D:\SublimeText\v3142\sublime_plugin.py", line 210, in on_api_ready
m.plugin_loaded()
File "C:\Users\Professional\AppData\Roaming\Sublime Text 3\Packages\User\bug.py", line 8, in plugin_loaded
sys.exit()
SystemExit
``` DPI scale: 1 startup, version: 3142 windows x32 channel: dev executable: /D/User/Dropbox/Applications/SoftwareVersioning/SublimeText/v3142/sublime_text.exe working dir: /D/User/Dropbox/Applications/SoftwareVersioning/SublimeText/v3142 packages path: /C/Users/Professional/AppData/Roaming/Sublime Text 3/Packages state path: /C/Users/Professional/AppData/Roaming/Sublime Text 3/Local zip path: /D/User/Dropbox/Applications/SoftwareVersioning/SublimeText/v3142/Packages zip path: /C/Users/Professional/AppData/Roaming/Sublime Text 3/Installed Packages ignored_packages: ["Vintage"] pre session restore time: 0.341594 startup time: 0.506594 first paint time: 0.526594 reloading plugin Default.auto_indent_tag reloading plugin Default.block reloading plugin Default.comment reloading plugin Default.convert_syntax reloading plugin Default.copy_path reloading plugin Default.delete_word reloading plugin Default.detect_indentation reloading plugin Default.duplicate_line reloading plugin Default.echo reloading plugin Default.exec reloading plugin Default.fold reloading plugin Default.font reloading plugin Default.goto_line reloading plugin Default.history_list reloading plugin Default.indentation reloading plugin Default.install_package_control reloading plugin Default.kill_ring reloading plugin Default.mark reloading plugin Default.new_templates reloading plugin Default.open_context_url reloading plugin Default.open_in_browser reloading plugin Default.pane reloading plugin Default.paragraph reloading plugin Default.paste_from_history reloading plugin Default.profile reloading plugin Default.quick_panel reloading plugin Default.run_syntax_tests reloading plugin Default.save_on_focus_lost reloading plugin Default.scroll reloading plugin Default.set_unsaved_view_name reloading plugin Default.settings reloading plugin Default.show_scope_name reloading plugin Default.side_bar reloading plugin Default.sort reloading plugin Default.swap_line reloading plugin Default.switch_file reloading plugin Default.symbol reloading plugin Default.transform reloading plugin Default.transpose reloading plugin Default.trim_trailing_white_space reloading plugin Default.ui reloading plugin CSS.css_completions reloading plugin Diff.diff reloading plugin HTML.encode_html_entities reloading plugin HTML.html_completions reloading plugin bug reloading plugin User.bug plugins loaded Traceback (most recent call last): File "D:\SublimeText\v3142\sublime_plugin.py", line 210, in on_api_ready m.plugin_loaded() File "C:\Users\Professional\AppData\Roaming\Sublime Text 3\Packages\User\bug.py", line 8, in plugin_loaded sys.exit() SystemExit ```
This is one of those things that you obviously shouldn't do, but it'd be nice if ST could prevent it anyhow. Apparently, sys.exit just throws a SystemExit exception.
This exception is raised by the sys.exit() function. It inherits from BaseException instead of Exception so that it is not accidentally caught by code that catches Exception. This allows the exception to properly propagate up and cause the interpreter to exit. When it is not handled, the Python interpreter exits; no stack traceback is printed. The constructor accepts the same optional argument passed to sys.exit(). If the value is an integer, it specifies the system exit status (passed to C’s exit() function); if it is None, the exit status is zero; if it has another type (such as a string), the object’s value is printed and the exit status is one.
That said, you can still shoot yourself in the foot if you do
import os
os._exit(1)
I was trying to stop the package from loading the remaining instructions, as I as testing somethings. But I found this question:
I can just call raise ValueError()
instead of sys.exit()
to stop a python script loading.
Well, modern python packages provide command line interfaces. A plugin may decide to run a python package's command via CLI interface for compatibility reasons.
If the invoked CLI command calls sys.exit()
to terminate script execution due to wrong inputs or any precondition not being met, plugin_host is closed.
A real life example is:
from pip._internal.cli.main import main
main(["--help"])
A possible workaround or solution would be to monkey patch sys.exit
.
import sys
from pip._internal.cli.main import main
class ReturnError(Exception):
pass
def exit(code=0):
raise ReturnError(f"Got exit code {code}.")
sys.exit = exit
try:
main(["--help"])
except Exception as e:
print(str(e))
Maybe that's something which should be handled by ST core.
import sys; sys.exit(1)
enter
A running function or script is terminated as if return
was programmed, but plugin_host continues running.
>>> import sys
>>> sys.exit(1)
error: plugin_host-3.8 has exited unexpectedly, some plugin functionality won't be available until Sublime Text has been restarted
4175
I would consider this expected behavior. You could just as well raise SystemExit
, os._exit()
or os.kill(os.getpid())
. Sublime Text shouldn't lock-up when doing this though.
ST4175 doesn't lock up anymore. It's just the plugin_host which exists. I find that a bit weird in a plugin environment. Maybe we should redirect it to sublime.run_command("exit")
to really close the app :) instead of breaking plugin functionality.
I was able to reproduce the locking up when running in the console still.
Note that the best way to prevent this from occurring when invoking third-party libraries within the plugin host in a controlled invocation, e.g. with pip, is to except SystemExit: pass
.
Maybe we should redirect it to
sublime.run_command("exit")
to really close the app
It would be difficult to figure out why Sublime Text was exiting, as I would lose the console output (unless Sublime saved it before). Of course, this is only valid if Sublime Text does not hang completely, as reported in the first post.
Note that the best way to prevent this from occurring when invoking third-party libraries within the plugin host in a controlled invocation, e.g. with pip, is to
except SystemExit: pass
.
Nice catch. After researching, I see the following:
sys.exit()
is identical toraise SystemExit()
. It raises a Python exception, which may be caught by the caller.
Calling os._exit(1)
does not have this behavior, and the Python interpreter is closed.
os._exit
calls the C function_exit()
which does an immediate program termination. Exit the process with status n, without calling cleanup handlers, flushing stdio buffers, etc.A possible workaround or solution would be to monkey patch
sys.exit
.
I think it would not be nice to patch sys.exit
as it already raises SystemExit and the application can catch it. The other possibilities of os._exit()
and os.kill(os.getpid())
should not be the standard way to exit, so I do think there are not many packages calling them, unless doing something nasty.
It would be difficult to figure out why Sublime Text was exiting,
Sure, this was rather a sarcastic statement as it doesn't make sense for any plugin to kill its plugin_host while keeping the whole app running.
e.g. with pip, is to except SystemExit: pass.
I've actually expected SystemExit to also be caught by catch Exception
, but that doesn't seem to be true.
os._exit()
is probably rather unimportant as
a) it seems to be a protected function not intendet for general purpose public use (following its name starting with _
)
b) all cli like scripts I've seen so far just use sys.exit
to terminate script execution and return a value to its caller (the shell).
I've actually expected SystemExit to also be caught by
catch Exception
, but that doesn't seem to be true.
Indeed, SystemExit
inherits BaseException
but not Exception
(same as KeyboardInterrupt
btw). This is also why linters complain about bare excepts because those occasionally and unexpectedly catch these two exceptions when the userdeveloper probably didn't want to.
Makes sense to separate possible errors from a designed system exit signal. Being able to catch SystemExit is a sufficient solution to handle os.exit
calls.
@deathaxe wrote:
A real life example is: from pip._internal.cli.main import main main(["--help"])
I would say: just catch SystemExit
. Monkey patching by default is rarely a good idea. I would be surprised if sys.exit
raised RuntimeError
or something.
That's conclusion of this issue's discussion, yes.
Summary
In Sublime Text 3 on Ubuntu 14.04 running
import sys
sys.exit(0)
breaks ST3Expected behavior
If I call sys.exit(0) it shouldn't freeze Sublime Text 3.
Actual behavior
The application locks up and you have to kill it manually
Steps to reproduce
import sys
sys.exit(0)
Environment