diff --git a/docs/howto/basics.md b/docs/howto/basics.md index 4fd2fe154..dbe638497 100644 --- a/docs/howto/basics.md +++ b/docs/howto/basics.md @@ -12,4 +12,5 @@ basics/tasks basics/defer basics/worker basics/command_line +basics/windows ::: diff --git a/docs/howto/basics/windows.md b/docs/howto/basics/windows.md new file mode 100644 index 000000000..32155e651 --- /dev/null +++ b/docs/howto/basics/windows.md @@ -0,0 +1,20 @@ +# Running on Windows + +Procrastinate has been manually tested and is known to work on Windows. However, +there are a few things to keep in mind: + +- Due to the way signals are implemented on Windows (in short, they are not fully supported), + the worker will not be able to gracefully stop when receiving a `SIGINT` or `SIGTERM` signal + as described in the {doc}`../production/retry_stalled_jobs` guide. This means that any running + jobs will be halted abruptly. + +- We do not use windows for running Procrastinate in production, so we may not + be aware of all the issues that may arise. + +- There are no automated tests in place for windows, so it's possible you encounter + bugs. If you know what needs to be fixed, we accept + [pull requests](https://github.com/procrastinate-org/procrastinate/pulls). + Thank you! + +Given these limitations, we strongly advise using a Unix-like system for running Procrastinate +in production. Windows should primarily be used for development purposes only. diff --git a/procrastinate/cli.py b/procrastinate/cli.py index 169a29d0d..0910352f3 100644 --- a/procrastinate/cli.py +++ b/procrastinate/cli.py @@ -659,4 +659,6 @@ async def shell_(app: procrastinate.App, shell_command: list[str]): def main(): + if os.name == "nt": + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) asyncio.run(cli(sys.argv[1:])) diff --git a/procrastinate/contrib/django/management/commands/procrastinate.py b/procrastinate/contrib/django/management/commands/procrastinate.py index 22bf59f17..1b73eb185 100644 --- a/procrastinate/contrib/django/management/commands/procrastinate.py +++ b/procrastinate/contrib/django/management/commands/procrastinate.py @@ -2,6 +2,7 @@ import asyncio import contextlib +import os from django.core.management.base import BaseCommand @@ -24,6 +25,9 @@ def add_arguments(self, parser): suppressed_base_arguments = {"-v", "--version"} def handle(self, *args, **kwargs): + if os.name == "nt": + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + kwargs = {k: v for k, v in kwargs.items() if k not in self._django_options} context = contextlib.nullcontext() diff --git a/procrastinate/signals.py b/procrastinate/signals.py index a88bb5bb2..e13446e37 100644 --- a/procrastinate/signals.py +++ b/procrastinate/signals.py @@ -3,6 +3,7 @@ import asyncio import contextlib import logging +import os import signal import threading from typing import Any, Callable @@ -25,6 +26,13 @@ @contextlib.contextmanager def on_stop(callback: Callable[[], None]): + if os.name == "nt": + logger.warning( + "Skipping signal handling, does not work on Windows", + extra={"action": "skip_signal_handlers"}, + ) + yield + return if threading.current_thread() is not threading.main_thread(): logger.warning( "Skipping signal handling, because this is not the main thread",