-
-
Notifications
You must be signed in to change notification settings - Fork 2k
ref(cleanup): Schedule sentry cleanup with python instead of cron #4544
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| """Run a command every day at midnight, container local time. | ||
|
|
||
| Usage: python3 /cron/run_daily.py <command> [args...] | ||
|
|
||
| A run still going at the next midnight is stopped before the next one starts, | ||
| so a stuck run can't block the schedule and runs never overlap. | ||
| """ | ||
|
|
||
| import datetime | ||
| import os | ||
| import signal | ||
| import subprocess | ||
| import sys | ||
| import time | ||
|
|
||
| STOP_GRACE_SECONDS = 60 | ||
|
|
||
| current: subprocess.Popen[bytes] | None = None | ||
|
|
||
|
|
||
| def next_midnight(now: datetime.datetime) -> datetime.datetime: | ||
| tomorrow = now + datetime.timedelta(days=1) | ||
| return tomorrow.replace(hour=0, minute=0, second=0, microsecond=0) | ||
|
|
||
|
|
||
| def seconds_until(when: datetime.datetime) -> float: | ||
| return (when - datetime.datetime.now()).total_seconds() | ||
|
|
||
|
|
||
| def stop(proc: subprocess.Popen[bytes]) -> None: | ||
| # The run is its own process group, so this also reaches its workers. | ||
| os.killpg(proc.pid, signal.SIGTERM) | ||
| try: | ||
| proc.wait(timeout=STOP_GRACE_SECONDS) | ||
| except subprocess.TimeoutExpired: | ||
| os.killpg(proc.pid, signal.SIGKILL) | ||
| proc.wait() | ||
|
|
||
|
|
||
| def on_sigterm(signum: int, frame: object) -> None: | ||
| if current is not None and current.poll() is None: | ||
| stop(current) | ||
| sys.exit(0) | ||
|
|
||
|
|
||
| def main(command: list[str]) -> None: | ||
| global current | ||
| signal.signal(signal.SIGTERM, on_sigterm) | ||
| run_at = next_midnight(datetime.datetime.now()) | ||
| while True: | ||
| print(f"Next run of `{' '.join(command)}` at {run_at:%Y-%m-%d %H:%M}", flush=True) | ||
| # Check the wall clock every minute, like cron, so clock changes and | ||
| # host suspends don't delay the run. | ||
| while (remaining := seconds_until(run_at)) > 0: | ||
| time.sleep(min(remaining, 60)) | ||
|
|
||
| deadline = next_midnight(run_at) | ||
| current = subprocess.Popen(command, start_new_session=True) | ||
| while current.poll() is None and (remaining := seconds_until(deadline)) > 0: | ||
| time.sleep(min(remaining, 60)) | ||
| if current.poll() is None: | ||
| print(f"`{' '.join(command)}` still running at the next scheduled run, stopping it", flush=True) | ||
| stop(current) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Past deadlines kill freshly started runsLow Severity After a long suspend or clock jump past the computed Additional Locations (1)Reviewed by Cursor Bugbot for commit 3e8ee18. Configure here. |
||
| if current.returncode != 0: | ||
| print(f"`{' '.join(command)}` exited with {current.returncode}", flush=True) | ||
| run_at = deadline | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| if len(sys.argv) < 2: | ||
| print("usage: run_daily.py <command> [args...]", file=sys.stderr) | ||
| sys.exit(2) | ||
| main(sys.argv[1:]) | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: An unhandled
ProcessLookupErrorcan crash the script due to a race condition where a subprocess exits beforeos.killpg()is called.Severity: MEDIUM
Suggested Fix
Wrap the
os.killpg()calls in atry...except ProcessLookupErrorblock to gracefully handle cases where the process has already exited, preventing the script from crashing.Prompt for AI Agent
Also affects:
cron/run_daily.py:36~36Did we get this right? 👍 / 👎 to inform future reviews.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oioki mind addressing this one first?