8

How can I stop a python process in such a way that any active context managers will gracefully call their __exit__ function before closing?

I use context managers (__enter__() and __exit__()) to reliably and safely close connections to optical hardware. This has been working great, although we are now starting to execute routines that run for hours. Often we will realize shortly after starting one that we have a bug, and would rather to stop the process short.

I have been running code from PyCharm, which has allows you to "stop" a running process. This seems to instantly kill the process, whether I'm in debug or run. The __exit__ functions don't seem to get called.

Also, the computer that controls the hardware runs windows, if that somehow comes into play. ***

***Indeed in comes into play. Macosx seems to call the exit function while windows does not.

I decided to write a basic test:

from abc import *
import time

class Test(object):
    __metaclass__ = ABCMeta

    def __init__(self, *args, **kwargs):
        print("Init called.")

    def __enter__(self, *args, **kwargs):
        print("enter called")

    def __exit__(self, type, value, traceback):
        print("Exit called")

with Test() as t:
    time.sleep(100)
print("Should never get here.")

I run this code from PyCharm, and while it is in the sleep statement I press the stop button in pycharm. Here is the output from both:

Macosx:

Init called.
enter called
Exit called
Traceback (most recent call last):
  File "/Applications/PyCharm CE.app/Contents/helpers/pydev/pydevd.py", line 1591, in <module>
    globals = debugger.run(setup['file'], None, None, is_module)
  File "/Applications/PyCharm CE.app/Contents/helpers/pydev/pydevd.py", line 1018, in run
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Users/.../Library/Preferences/PyCharmCE2017.1/scratches/scratch_25.py", line 22, in <module>
    time.sleep(100)
KeyboardInterrupt

Windows

Init called.
enter called

Process finished with exit code 1
Batman0730
  • 487
  • 2
  • 5
  • 21
  • Control-C would work, if it was running from a command prompt. – jasonharper Jul 19 '17 at 19:55
  • We currently run from PyCharm, although I think it lets you bring up a terminal of some kind. – Batman0730 Jul 19 '17 at 19:57
  • What operating system and Python version? – phd Jul 19 '17 at 19:59
  • Wrap the `__enter__` method definition around a `try-except` and call `self.__exit__` inside the `except` clause? I am not what PyCharm uses, but handling `KeyboardInterrupt` sounds like a decent course of action. – Abdou Jul 19 '17 at 20:03
  • I may be all wrong about this. I just made a basic context manager with a time.sleep(100) in it. When I hit the stop button in PyCharm it calls the exit function. – Batman0730 Jul 19 '17 at 20:09
  • I have run the same simple test on Macosx and Windows (both using python27). Macosx calls the exit print statment, Windows does not. I am going to update the description. – Batman0730 Jul 19 '17 at 20:16
  • This is a duplicate of [that](https://stackoverflow.com/questions/31296375/how-to-gracefully-terminate-python-process-on-windows). – user2722968 Jul 19 '17 at 20:17
  • The whole point of `SIGKILL` (or the Windows `WM_QUIT` equivalent) is to forcibly stop a process from executing - under the assumption that the process has gone wild and won't respond to graceful calls for exit - and as such they cannot (or rather, shouldn't be able to) be captured within the process. This also means that your Python interpreter doesn't get the chance to react and exit cleanly - it's forcibly killed. PyCharm is sending a `SIGKILL` signal when you press the stop button hence your problem. Your best bet is to do a clean up on the next run. – zwer Jul 19 '17 at 20:33
  • @zwer, Unix `SIGKILL` is closest to Windows `TerminateProcess`, i.e. the kernel immediately terminates the process. `WM_QUIT` is voluntary like Unix `SIGTERM`. It can be sent via `PostThreadMessage` to a thread of a process in the current session that's at or below the sender's integrity level. The thread must have a message loop; it's not applicable to background threads. It's more typical to send `WM_CLOSE` to the windows of a process. Sending `WM_CLOSE` also works for a console process if it owns the console. However, closing the console also kills all other processes attached to it. – Eryk Sun Jul 19 '17 at 23:40

1 Answers1

6

I found a workaround on the PyCharm bug tracker:

https://youtrack.jetbrains.com/issue/PY-17252

  • In PyCharm go to help->Edit Custom Properties
  • Agree to create the idea.properties file (if it asks)
  • Add the following line: kill.windows.processes.softly=true
  • If you use Numpy or Scipy, you will also need to add the following environment variable:

    os.environ['FOR_DISABLE_CONSOLE_CTRL_HANDLER'] = "1"

  • Restart Pycharm

Now when I run my test with this applied (on Windows!) I get the following output:

Init called.
enter called
Exit called
Traceback (most recent call last):
  File "C:/Users/.../.PyCharmCE2017.1/config/scratches/scratch_3.py", line 20, in <module>
    time.sleep(100)
KeyboardInterrupt

Process finished with exit code 1
Batman0730
  • 487
  • 2
  • 5
  • 21