示例#1
0
    def fetch_job_one(self, queues: Optional[Iterable[str]]) -> Dict:
        # Creating a copy of the iterable so that we can modify it while we iterate

        for job in self.jobs.values():
            if (
                job["status"] == "todo"
                and (queues is None or job["queue_name"] in queues)
                and (not job["scheduled_at"] or job["scheduled_at"] <= utils.utcnow())
                and job["lock"] not in self.current_locks
            ):
                job["status"] = "doing"
                self.events[job["id"]].append({"type": "started", "at": utils.utcnow()})

                return job

        return {"id": None}
示例#2
0
    def defer_job_one(
        self, task_name, lock, queueing_lock, args, scheduled_at, queue
    ) -> JobRow:
        if any(
            job for job in self.jobs.values() if job["queueing_lock"] == queueing_lock
        ):
            raise exceptions.UniqueViolation(
                constraint_name=connector.QUEUEING_LOCK_CONSTRAINT
            )

        id = next(self.job_counter)

        self.jobs[id] = job_row = {
            "id": id,
            "queue_name": queue,
            "task_name": task_name,
            "lock": lock,
            "queueing_lock": queueing_lock,
            "args": args,
            "status": "todo",
            "scheduled_at": scheduled_at,
            "attempts": 0,
        }
        self.events[id] = []
        if scheduled_at:
            self.events[id].append({"type": "scheduled", "at": scheduled_at})
        self.events[id].append({"type": "deferred", "at": utils.utcnow()})
        if self.notify_event:
            if "procrastinate_any_queue" in self.notify_channels or (
                f"procrastinate_queue#{queue}" in self.notify_channels
            ):
                self.notify_event.set()
        return job_row
示例#3
0
def configure_task(
    *,
    name: str,
    job_store: store.JobStore,
    lock: Optional[str] = None,
    queueing_lock: Optional[str] = None,
    task_kwargs: Optional[types.JSONDict] = None,
    schedule_at: Optional[datetime.datetime] = None,
    schedule_in: Optional[Dict[str, int]] = None,
    queue: str = jobs.DEFAULT_QUEUE,
) -> jobs.JobDeferrer:
    if schedule_at and schedule_in is not None:
        raise ValueError("Cannot set both schedule_at and schedule_in")

    if schedule_in is not None:
        schedule_at = utils.utcnow() + datetime.timedelta(**schedule_in)

    task_kwargs = task_kwargs or {}
    return jobs.JobDeferrer(
        job=jobs.Job(
            id=None,
            lock=lock,
            queueing_lock=queueing_lock,
            task_name=name,
            queue=queue,
            task_kwargs=task_kwargs,
            scheduled_at=schedule_at,
        ),
        job_store=job_store,
    )
示例#4
0
    def delete_old_jobs_run(self, nb_hours, queue, statuses):
        for id, job in list(self.jobs.items()):

            if (job["status"] in statuses
                    and (max(e["at"] for e in self.events[id]) <
                         utils.utcnow() - datetime.timedelta(hours=nb_hours))
                    and queue in (job["queue_name"], None)):
                self.jobs.pop(id)
示例#5
0
def test_get_retry_exception_returns():
    strategy = retry_module.RetryStrategy(max_attempts=10, wait=5.0)

    now = utils.utcnow()
    expected = now + datetime.timedelta(seconds=5, microseconds=0)

    exc = strategy.get_retry_exception(exception=None, attempts=1)
    assert isinstance(exc, exceptions.JobRetry)
    assert exc.scheduled_at == expected.replace(microsecond=0)
示例#6
0
    def get_retry_exception(self, exception: Exception,
                            attempts: int) -> Optional[exceptions.JobRetry]:
        schedule_in = self.get_schedule_in(exception=exception,
                                           attempts=attempts)
        if schedule_in is None:
            return None

        schedule_at = utils.utcnow() + datetime.timedelta(seconds=schedule_in)
        return exceptions.JobRetry(schedule_at.replace(microsecond=0))
示例#7
0
 def retry_job_run(self, job_id: int, retry_at: datetime.datetime) -> None:
     job_row = self.jobs[job_id]
     job_row["status"] = "todo"
     job_row["attempts"] += 1
     job_row["scheduled_at"] = retry_at
     self.events[job_id].append({"type": "scheduled", "at": retry_at})
     self.events[job_id].append({
         "type": "deferred_for_retry",
         "at": utils.utcnow()
     })
示例#8
0
    def finish_job_run(self, job_id: int, status: str,
                       delete_job: bool) -> None:
        if delete_job:
            self.jobs.pop(job_id)
            return

        job_row = self.jobs[job_id]
        job_row["status"] = status
        job_row["attempts"] += 1
        self.events[job_id].append({"type": status, "at": utils.utcnow()})
示例#9
0
 def select_stalled_jobs_all(self, nb_seconds, queue, task_name):
     return (
         job
         for job in self.jobs.values()
         if job["status"] == "doing"
         and self.events[job["id"]][-1]["at"]
         < utils.utcnow() - datetime.timedelta(seconds=nb_seconds)
         and queue in (job["queue_name"], None)
         and task_name in (job["task_name"], None)
     )
示例#10
0
def test_delete_old_jobs_run(connector):
    connector.jobs = {
        # We're not deleting this job because it's "doing"
        1: {
            "id": 1,
            "status": "doing",
            "queue_name": "marsupilami"
        },
        # This one because it's the wrong queue
        2: {
            "id": 2,
            "status": "succeeded",
            "queue_name": "other_queue"
        },
        # This one is not old enough
        3: {
            "id": 3,
            "status": "succeeded",
            "queue_name": "marsupilami"
        },
        # This one we delete
        4: {
            "id": 4,
            "status": "succeeded",
            "queue_name": "marsupilami"
        },
    }
    connector.events = {
        1: [{
            "type": "succeeded",
            "at": conftest.aware_datetime(2000, 1, 1)
        }],
        2: [{
            "type": "succeeded",
            "at": conftest.aware_datetime(2000, 1, 1)
        }],
        3: [{
            "type": "succeeded",
            "at": utils.utcnow()
        }],
        4: [{
            "type": "succeeded",
            "at": conftest.aware_datetime(2000, 1, 1)
        }],
    }

    connector.delete_old_jobs_run(queue="marsupilami",
                                  statuses=("succeeded"),
                                  nb_hours=0)
    assert 4 not in connector.jobs
示例#11
0
    def do_retry(self, arg: str) -> None:
        """
        Retry a specific job (reset its status to todo).
        Usage: retry JOB_ID

        JOB_ID is the id (numeric) of the job.

        Example: retry 2
        """
        job_id = int(arg)
        self.job_manager.retry_job_by_id(  # type: ignore
            job_id=job_id,
            retry_at=utils.utcnow().replace(microsecond=0))
        (job, ) = self.job_manager.list_jobs(id=job_id)  # type: ignore
        print_job(job)
示例#12
0
    def finish_job_run(
        self, job_id: int, status: str, scheduled_at: Optional[datetime.datetime] = None
    ) -> None:
        job_row = self.jobs[job_id]
        job_row["status"] = status
        event_type = status

        if status == "todo":
            job_row["attempts"] += 1
            job_row["scheduled_at"] = scheduled_at
            if scheduled_at:
                self.events[job_id].append({"type": "scheduled", "at": scheduled_at})
            event_type = "deferred_for_retry"

        self.events[job_id].append({"type": event_type, "at": utils.utcnow()})
示例#13
0
    async def retry_job(
        self,
        job: "jobs.Job",
        retry_at: Optional[datetime.datetime] = None,
    ) -> None:
        """
        Indicates that a job should be retried later.

        Parameters
        ----------
        job : `jobs.Job`
        retry_at : ``Optional[datetime.datetime]``
            If set at present time or in the past, the job may be retried immediately.
            Otherwise, the job will be retried no sooner than this date & time.
            Should be timezone-aware (even if UTC). Defaults to present time.
        """
        assert job.id  # TODO remove this
        await self.retry_job_by_id_async(job_id=job.id,
                                         retry_at=retry_at or utils.utcnow())
示例#14
0
    def defer_job_one(
        self,
        task_name: str,
        lock: Optional[str],
        queueing_lock: Optional[str],
        args: types.JSONDict,
        scheduled_at: Optional[datetime.datetime],
        queue: str,
    ) -> JobRow:
        if queueing_lock is not None and any(
                job["queueing_lock"] == queueing_lock
                and job["status"] == "todo" for job in self.jobs.values()):
            from . import manager

            raise exceptions.UniqueViolation(
                constraint_name=manager.QUEUEING_LOCK_CONSTRAINT)

        id = next(self.job_counter)

        self.jobs[id] = job_row = {
            "id": id,
            "queue_name": queue,
            "task_name": task_name,
            "lock": lock,
            "queueing_lock": queueing_lock,
            "args": args,
            "status": "todo",
            "scheduled_at": scheduled_at,
            "attempts": 0,
        }
        self.events[id] = []
        if scheduled_at:
            self.events[id].append({"type": "scheduled", "at": scheduled_at})
        self.events[id].append({"type": "deferred", "at": utils.utcnow()})
        if self.notify_event:
            if "procrastinate_any_queue" in self.notify_channels or (
                    f"procrastinate_queue#{queue}" in self.notify_channels):
                self.notify_event.set()
        return job_row
示例#15
0
def test_utcnow(mocker):
    dt = mocker.patch("datetime.datetime")
    assert utils.utcnow() == dt.now.return_value
    dt.now.assert_called_once_with(tz=datetime.timezone.utc)