Is it possible to prevent execution of further tasks in locust TaskSequence if some task has failed? - locust

For example i have the following class. How i can prevent execution of get_entity task if create_entity task was not executed?
class MyTaskSequence(TaskSequence):
#seq_task(1)
def create_entity(self):
self.round += 1
with self.client.post('/entities', json={}, catch_response=True) as resp:
if resp.status_code != HTTPStatus.CREATED:
resp.failure()
# how to stop other tasks for that run?
self.entity_id = resp.json()['data']['entity_id']
#seq_task(2)
def get_entity(self):
# It is being always executed,
# but it should not be run if create_entity task failed
resp = self.client.get(f'/entities/{self.entity_id}')
...
I found TaskSet.interrupt method in documentation, but does not allow to cancel root TaskSet. I tried to make parent TaskSet for my task sequence, so TaskSet.interrupt works.
class MyTaskSet(TaskSet):
tasks = {MyTaskSequence: 10}
But now i see that all results in ui are cleared after i call interrupt!
I just need to skip dependent tasks in this sequence. I need the results.

The easiest way to solve this is just to use a single #task with multiple requests inside it. Then, if a request fails just do a return after resp.failure()

Might self.interrupt() be what you are looking for?
See https://docs.locust.io/en/latest/writing-a-locustfile.html#interrupting-a-taskset for reference.

Why not using on_start(self): which runs once whenever a locust created, it can set a global which can be checked whether the locust executes the tasks
class MyTaskSequence(TaskSequence):
entity_created = false
def on_start(self):
self.round += 1
with self.client.post('/entities', json={}, catch_response=True) as resp:
if resp.status_code != HTTPStatus.CREATED:
self.entity_created = true
resp.failure()
self.entity_id = resp.json()['data']['entity_id']
#seq_task(2)
def get_entity(self):
if self.entity_created:
resp = self.client.get(f'/entities/{self.entity_id}')
...

Related

celery celery_session_worker pytest timeout

I'm trying to write integration test for the celery tasks and have a test class like below
#pytest.mark.usefixtures('celery_session_app')
#pytest.mark.usefixtures('celery_session_worker')
#pytest.mark.usefixtures('mongodb')
class TestIntegration:
def test_delete_collection_from_mongodb(self, x, y):
results = delete_collection_from_mongodb(x, y).delay()
assert results.get(timeout=20) == 20
And in my conftest I have the following fixtures
#pytest.fixture(scope='session')
def celery_config():
return {
'broker_url': RABBITMQ_BROKER_URL,
'shutdown_timeout': 30,
}
#pytest.fixture(scope='session')
def celery_worker_parameters():
return {
'queues': (....),
}
#pytest.fixture(scope='session')
def celery_enable_logging():
return True
However, when I run the test it times out. Stacktrace:
task_id = '6009db28-637b-4447-a2c5-c0bdb3c03981', timeout = 10.0, interval = 0.5
no_ack = True, on_interval = <promise#0x7fcfaac01d30>
def wait_for(self, task_id,
timeout=None, interval=0.5, no_ack=True, on_interval=None):
"""Wait for task and return its result.
If the task raises an exception, this exception
will be re-raised by :func:`wait_for`.
Raises:
celery.exceptions.TimeoutError:
If `timeout` is not :const:`None`, and the operation
takes longer than `timeout` seconds.
"""
self._ensure_not_eager()
time_elapsed = 0.0
while 1:
meta = self.get_task_meta(task_id)
if meta['status'] in states.READY_STATES:
return meta
if on_interval:
on_interval()
# avoid hammering the CPU checking status.
time.sleep(interval)
time_elapsed += interval
if timeout and time_elapsed >= timeout:
> raise TimeoutError('The operation timed out.')
E celery.exceptions.TimeoutError: The operation timed out.
/venv/lib/python3.9/site-packages/celery/backends/base.py:792: TimeoutError
I've also tried to set the result backend to RPC, Redis, or cache+memory, and it still times out. Any idea what I'm missing?
Depending on the celery version you are using it could be this issue which has been fixed since 5.0.4. It was root-caused to the cache backend, though I see you've tried other backends with the same result.
Is it possible your backend and/or broker arent up when you run the tests? I also saw this timeout error when testing in a docker setup with the broker, backend, and test code all running in separate containers. Blowing away all the containers & rebuilding fixed it for me, so it was probably some weird state.
The github issue offers this minimum fixture & test case that might be worth trying out to see if you can get that to work.

How to check status of a non-registered task?

My application A calls a celery task longtask in application B. However, longtask is registered in B but not in A, so A calls it by using send_task. I want a mechanism in A to check periodically if longtask is complete. How do I do it?
send_task returns an AsyncResult that contains the task id. You can use this id to periodically check on the result of longtask.
result = my_app.send_task('longtask', kwargs={})
task_id = result.id
# anywhere else in your code you can reuse the
# task_id to check the status
from celery.result import AsyncResult
import time
done = False
while not done:
result = AsyncResult(task_id)
current_status = result.status
if current_status == 'SUCCESS':
print('yay! we are done')
done = True
time.sleep(10)

#task_postrun.connect signal in Celery and executing another task results in some infite loop of executions

I need following workflow for my celery tasks.
when taskA finishes with success I want to execute taskB.
I know there is signal #task_success but this returns only task's result, and I need access to parameters of previous task's arguments. So I decided for code like these:
#app.task
def taskA(arg):
# not cool, but... https://github.com/celery/celery/issues/3797
from shopify.tasks import taskA
taskA(arg)
#task_postrun.connect
def fetch_taskA_success_handler(sender=None, **kwargs):
from gcp.tasks import taskB
if kwargs.get('state') == 'SUCCESS':
taskB.apply_async((kwargs.get('args')[0], ))
The problem is the taskB seems to be executed in some endless loop many, many times instead only once.
This way it works correctly:
#app.task
def taskA(arg):
# not cool, but... https://github.com/celery/celery/issues/3797
# otherwise it won't added in periodic tasks
from shopify.tasks import taskA
return taskA(arg)
#task_postrun.connect
def taskA_success_handler(sender=None, state=None, **kwargs):
resource_name = kwargs.get('kwargs', {}).get('resource_name')
if resource_name and state == 'SUCCESS':
if sender.name == 'shopify.tasks.taskA':
from gcp.tasks import taskB
taskB.apply_async(kwargs={
'resource_name': resource_name
})
just for reference:
celery==4.1.0
Django==2.0
django-celery-beat==1.1.0
django-celery-results==1.0.1
flower==0.9.2
amqp==2.2.2
Python 3.6

Celery send_task and retry on exception

I want to retry (official doc) a task when it raises an exception. Celery allows this by using the retry in form of self.retry(...)
Now, i can't figure out how to user self since i've a function without any class.
My code is this
.. imports ...
app = Celery('elasticcelery')
#app.task(name='rm_doc')
def rm_doc(schema_id, id):
es = Elasticsearch(es_ip)
try:
res = es.delete(schema_id, 'doc', id)
except NotFoundError as e:
<here goes the retry>
and it's called from another service in this way:
app_celery = Celery('celeryelastic')
app_celery.config_from_object('django.conf:settings')
app_celery.send_task('rm_doc', kwargs={"schema_id": schema_id, "id": document_id}, )
now, I should add the self.retry but there's no self in my method.
How should I proceed?
PS: I tried adding self as parmeter, but this fails since there's no mapping when the task is called the first time from the remote.
I forgot bind=True in the annotation of the method, now I can add self.

shutdown hook won't start upon ^C (scala)

i'm trying to get a clean and gracefull shutdown, and for some reason, it wont execute. iv'e tried:
sys addShutdownHook{
logger.warn("SHUTTING DOWN...")
// irrelevant logic here...
}
and also:
Runtime.getRuntime.addShutdownHook(ThreadOperations.delayOnThread{
logger.warn("SHUTTING DOWN...")
// irrelevant logic here...
}
)
where ThreadOperations.delayOnThread definition is:
object ThreadOperations {
def startOnThread(body: =>Unit) : Thread = {
onThread(true, body)
}
def delayOnThread(body: =>Unit) : Thread = {
onThread(false, body)
}
private def onThread(runNow : Boolean, body: =>Unit) : Thread = {
val t=new Thread {
override def run=body
}
if(runNow){t.start}
t
}
// more irrelevant operations...
}
but when i run my program (executable jar, double activation), the hook does not start. so what am i doing wrong? what is the right way to add a shutdown hook in scala? is it in any way related to the fact i'm using double activation?
double activation is done like that:
object Gate extends App {
val givenArgs = if(args.isEmpty){
Array("run")
}else{
args
}
val jar = Main.getClass.getProtectionDomain().getCodeSource().getLocation().getFile;
val dir = jar.dropRight(jar.split(System.getProperty("file.separator")).last.length + 1)
val arguments = Seq("java", "-cp", jar, "boot.Main") ++ givenArgs.toSeq
Process(arguments, new java.io.File(dir)).run();
}
(scala version: 2.9.2 )
thanks.
In your second attempt, your shutdown hook you seems to just create a thread and never start it (so it just gets garbage collected and does nothing). Did I miss something? (EDIT: yes I did, see comment. My bad).
In the first attempt, the problem might just be that the underlying log has some caching, and the application exits before the log is flushed.
Solved it.
For some reason, I thought that run as opposed to ! would detach the process. It actually hangs on because there are open streams left to the Process, which is returned from run (or maybe it just hangs for another reason, 'cause exec doesn't hang, but returns a Process with open streams to and from the child process, much like run). For this reason, the original process was still alive, and I accidentally sent the signals to it. Of course, it did not contain a handler, or a shutdown hook, so nothing happened.
The solution was to use Runtime.getRuntime.exec(arguments.toArray) instead of Process(arguments, new java.io.File(dir)).run();, close the streams in the Gate object, and send the ^C signal to the right process.