sublime_text
sublime_text copied to clipboard
calling sys.exit from the python console on Ubuntu causes it to freeze
Summary
In Sublime Text 3 on Ubuntu 14.04 running
import sys
sys.exit(0)
breaks ST3
Expected 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
- First step Open python console.
- Second step
Type
import sys - Third step
Type
sys.exit(0)
Environment
- Operating system and version:
- [ ] Windows ...
- [ ] Mac OS ...
- [x] Linux Ubuntu 14.04
- Sublime Text:
- Build 3103
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:
- https://stackoverflow.com/a/45403470/4934640 Stop python script without killing the python process
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.
Steps to reproduce
- Start ST in SAFE MODE
- Open ST's console
- Type
import sys; sys.exit(1) - Hit
enter
Expected behavior
A running function or script is terminated as if return was programmed, but plugin_host continues running.
Actual behavior
>>> 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
Sublime Text build number
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._exitcalls 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 ~~user~~developer 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.