Skip to content

Commit 2d808c6

Browse files
mehdigmirasylvinus
authored andcommitted
Max time (#152)
* add max_time to worker * add max_time parameter to config * script limit on max_time * add tests for max_time * add config doc for max_time * review * refactor max_time tests
1 parent 55667ea commit 2d808c6

4 files changed

Lines changed: 41 additions & 5 deletions

File tree

docs/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ USE_LARGE_JOB_IDS = False #Do not use compacted job IDs in Redis. For compatibil
5656
"""
5757
QUEUES = ("default",) # The queues to listen on.Defaults to default , which will listen on all queues.
5858
MAX_JOBS = 0 #Gevent:max number of jobs to do before quitting. Workaround for memory leaks in your tasks. Defaults to 0
59+
MAX_TIME = 0 # max number of seconds a worker runs before quitting
5960
MAX_MEMORY = 1 #Max memory (in Mb) after which the process will be shut down. Use with PROCESS = [1-N] to have supervisord automatically respawn the worker when this happens.Defaults to 1
6061
GRENLETS = 1 #Max number of greenlets to use.Defaults to 1.
6162
PROCESSES = 0 #Number of processes to launch with supervisord.Defaults to 0.

mrq/config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,13 @@ def add_parser_args(parser, config_type):
267267
help='Gevent: max number of jobs to do before quitting.' +
268268
' Temp workaround for memory leaks')
269269

270+
parser.add_argument(
271+
'--max_time',
272+
default=0.0,
273+
type=float,
274+
action='store',
275+
help='Max time a worker should run before quitting.')
276+
270277
parser.add_argument(
271278
'--max_memory',
272279
default=0,

mrq/worker.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import sys
1616
import json as json_stdlib
1717
import ujson as json
18-
import http.server
1918
from bson import ObjectId
2019
from collections import defaultdict
2120

@@ -53,6 +52,7 @@ def __init__(self):
5352

5453
self.done_jobs = 0
5554
self.max_jobs = self.config["max_jobs"]
55+
self.max_time = datetime.timedelta(seconds=self.config["max_time"]) or None
5656

5757
self.connected = False # MongoDB + Redis
5858

@@ -419,7 +419,7 @@ def work(self):
419419
"""
420420
self.work_init()
421421

422-
self.work_loop(max_jobs=self.max_jobs)
422+
self.work_loop(max_jobs=self.max_jobs, max_time=self.max_time)
423423

424424
return self.work_stop()
425425

@@ -449,7 +449,7 @@ def work_init(self):
449449

450450
self.install_signal_handlers()
451451

452-
def work_loop(self, max_jobs=None):
452+
def work_loop(self, max_jobs=None, max_time=None):
453453

454454
self.done_jobs = 0
455455
self.idle_wait_count = 0
@@ -459,6 +459,7 @@ def work_loop(self, max_jobs=None):
459459
try:
460460

461461
queue_offset = 0
462+
max_time_reached = False
462463

463464
while True:
464465

@@ -472,6 +473,12 @@ def work_loop(self, max_jobs=None):
472473

473474
while True:
474475

476+
# we put this here to make sure we have a strict limit on max_time
477+
if max_time and datetime.datetime.utcnow() - self.datestarted >= max_time:
478+
self.log.info("Reached max_time=%s" % max_time.seconds)
479+
max_time_reached = True
480+
break
481+
475482
free_pool_slots = self.gevent_pool.free_count()
476483

477484
if max_jobs:
@@ -487,6 +494,9 @@ def work_loop(self, max_jobs=None):
487494
self.status = "full"
488495
gevent.sleep(0.01)
489496

497+
if max_time_reached:
498+
break
499+
490500
jobs = []
491501

492502
available_queues = [
@@ -561,7 +571,6 @@ def work_loop(self, max_jobs=None):
561571

562572
self.log.debug("Joining the greenlet pool...")
563573
self.status = "join"
564-
565574
self.gevent_pool.join(timeout=None, raise_error=False)
566575
self.log.debug("Joined.")
567576

tests/test_interrupts.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import time
22
import datetime
33
from builtins import str
4-
from mrq.job import Job
4+
from mrq.job import Job, get_job_result
55
from mrq.queue import Queue
66
from bson import ObjectId
77
import pytest
@@ -322,6 +322,25 @@ def test_interrupt_maxjobs(worker):
322322
assert Queue("default").size() == 7
323323

324324

325+
def test_worker_interrupt_after_max_time(worker):
326+
worker.start(flags="--greenlets=2 --max_time=2", queues="test1 default")
327+
328+
task_ids = worker.send_tasks("tests.tasks.general.Add", [{"a": i, "b": 1, "sleep": 3} for i in range(5)],
329+
block=False)
330+
331+
time.sleep(5)
332+
333+
results = [get_job_result(task_id) for task_id in task_ids]
334+
335+
queued_tasks = [result for result in results if result['status'] == "queued"]
336+
successful_tasks = [(i, result) for i, result in enumerate(results) if result['status'] == "success"]
337+
338+
assert len(queued_tasks) == 3
339+
assert len(successful_tasks) == 2
340+
for i, result in successful_tasks:
341+
assert result['result'] == i + 1
342+
343+
325344
def test_interrupt_maxconcurrency(worker):
326345

327346
# The worker will raise a maxconcurrency on the second job

0 commit comments

Comments
 (0)