What exit code does PyCharm show when the Timeout expires?
PyCharm itself does not inject a custom exit status when the Timeout field triggers. The process is killed by the operating system, so the console displays whatever exit code the OS assigns to the signal that terminated the process. On Linux this is usually 137 (signal 9, SIGKILL), on macOS it can be 143 (signal 15, SIGTERM), and on Windows the IDE reports 1 for an abnormal termination. The exact value depends on the PyCharm version and the underlying OS, and there is no IDE setting that changes it.
Can the Cancel button’s signal be forced to pre‑empt blocking I/O?
PyCharm sends SIGINT (or SIGTERM on Windows) to the process group when you click the Stop/Cancel button. If the Python interpreter is in the middle of a blocking system call—such as read(), socket.recv(), or a third‑party library that performs a long I/O operation—the signal will be delivered only after the call returns. There is no IDE‑side configuration that changes this OS‑level behaviour. To get a more immediate response you must either:
- Make the code cooperative (e.g., use non‑blocking sockets or time‑out‑aware I/O), or
- Wrap the entry point in a small supervisor that installs a signal handler and explicitly stops the event loop.
How to make asyncio tasks honour PyCharm’s cancellation without touching the app code?
Because the OS signal is not automatically translated into an asyncio.CancelledError, the event loop continues to run until the current awaitable finishes. A practical workaround is to launch the program through a wrapper script that does the following:
- Spawns the real entry point as a child process.
- Installs a signal handler that calls
loop.stop() or task.cancel() on the main loop.
- Waits for the child to exit and forwards its status.
This approach does not require any changes to the original application source code. Below is a minimal example for a Unix‑like system:
#!/usr/bin/env python3
import asyncio
import signal
import subprocess
import sys
# Path to the real script
REAL_SCRIPT = "./my_app.py"
async def main():
# Start the real script as a subprocess
proc = await asyncio.create_subprocess_exec(
sys.executable, REAL_SCRIPT,
stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr,
)
# Forward SIGINT/SIGTERM to the child
def _sig_handler(signum, frame):
print(f"Received signal {signum}, terminating child…")
proc.terminate()
signal.signal(signal.SIGINT, _sig_handler)
signal.signal(signal.SIGTERM, _sig_handler)
# Wait for the child to finish
await proc.wait()
sys.exit(proc.returncode)
if __name__ == "__main__":
asyncio.run(main())
Run this wrapper instead of the original script in PyCharm’s Script path field. When you click Stop, the wrapper forwards the signal to the child, causing it to exit promptly. The wrapper then exits with the same code, so the IDE displays the usual exit status.
What to check next?
If you observe a different exit code than the typical values mentioned above, it may be due to the specific PyCharm build or the operating system’s signal handling. Knowing the exact OS and PyCharm version would help confirm the mapping.