浏览代码

Try to fix crontab infinite loop (invalid date)

When never occurrence can never be reached (example, April, 31th), trying
to reach the next occurrence would trigger an infinite loop.

Try fixing that by raising a RuntimeError after 2,000 iterations

(Also added a test for crontab leap years in the process)
Romuald Brunet 9 年之前
父节点
当前提交
779924faf6
共有 2 个文件被更改,包括 21 次插入1 次删除
  1. 5 1
      celery/schedules.py
  2. 16 0
      celery/tests/app/test_schedules.py

+ 5 - 1
celery/schedules.py

@@ -461,7 +461,7 @@ class crontab(schedule):
             return False
 
         def roll_over():
-            while 1:
+            for _ in range(2000):
                 flag = (datedata.dom == len(days_of_month) or
                         day_out_of_range(datedata.year,
                                          months_of_year[datedata.moy],
@@ -478,6 +478,10 @@ class crontab(schedule):
                         datedata.year += 1
                 else:
                     break
+            else:
+                # Tried 2000 times, we're most likely in an infinite loop
+                raise RuntimeError('unable to rollover, '
+                                   'time specification is probably invalid')
 
         if last_run_at.month in self.month_of_year:
             datedata.dom = bisect(days_of_month, last_run_at.day)

+ 16 - 0
celery/tests/app/test_schedules.py

@@ -434,6 +434,22 @@ class test_crontab_remaining_estimate(AppCase):
         )
         self.assertEqual(next, datetime(2010, 5, 29, 0, 5))
 
+    def test_invalid_specification(self):
+        # *** WARNING ***
+        # This test triggers an infinite loop in case of a regression
+        with self.assertRaises(RuntimeError):
+            next = self.next_ocurrance(
+                self.crontab(day_of_month=31, month_of_year=4),
+                datetime(2010, 1, 28, 14, 30, 15),
+            )
+
+    def test_leapyear(self):
+        next = self.next_ocurrance(
+            self.crontab(minute=30, hour=14, day_of_month=29, month_of_year=2),
+            datetime(2012, 2, 29, 14, 30),
+        )
+        self.assertEqual(next, datetime(2016, 2, 29, 14, 30))
+
 
 class test_crontab_is_due(AppCase):