Repository: celery/celery

Issue:
Question: any chance to have a on_started handler in the Task class?
It would be very handy to have an on_started call back in the Task class. In addition to after_return, on_failure, on_success and on_retry.

Thoughts?
Question: any chance to have a on_started handler in the Task class?
It would be very handy to have an on_started call back in the Task class. In addition to after_return, on_failure, on_success and on_retry.

Thoughts?


Discussion:
@rhymes could you describe a use case?
Celery provides a [task_track_started](http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-task_track_started) configuration option, this allows us to open up flower and see which of the many long running tasks have been actually "STARTED" instead of received or pending.

We would like to show in the frontend that a task has been actually picked up and started in the queue but there's no callback for that.

As the documentation says:

> Having a ‘started’ state can be useful for when there are long running tasks and there’s a need to report what task is currently running.

But how can we report, or send notifications or tell the users in the frontend which of the many batch calculations in the queue is currently running, if there's no callback?

Hope this makes sense.
I don't know why `on_started` handler doesn't exist while others does.
Below structure can be workaround.
```
class BaseTask(Task):
    def __call__(self, *args, **kwargs):
        self.before_run()
        return super(BaseTask, self).__call__(*args, **kwargs)
    def before_run(self):
        #do stuff
        pass

@app.task(base=BaseTask)
def task1():
    pass

```
I too have a use case for `on_started` if possible im looking for `on_progress` (custom state) also.
The workaround specified by @aydin will so but the task state will not update right?
@pavankumarkatakam no, you need to update manually the state. See here an example http://docs.celeryproject.org/en/latest/userguide/tasks.html#custom-states
To other people who find this issue & code suggestion from @aydin, so that you don't spend much time debugging.

As of recent-ish updates to Celery docs following #5652, it is recommended to call `self.run` instead of `super.__call__` when overriding the `__call__` method. We have hit a similar scenario during testing when using `__call__` as described in #5643 where tasks are not enqueued for retries.
> To other people who find this issue & code suggestion from @aydin, so that you don't spend much time debugging.
> 
> As of recent-ish updates to Celery docs following #5652, it is recommended to call `self.run` instead of `super.__call__` when overriding the `__call__` method. We have hit a similar scenario during testing when using `__call__` as described in #5643 where tasks are not enqueued for retries.

so should we close this issue?
I think we can add this to the documentation or add the hook if everyone wants it.
@rhymes could you describe a use case?
Celery provides a [task_track_started](http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-task_track_started) configuration option, this allows us to open up flower and see which of the many long running tasks have been actually "STARTED" instead of received or pending.

We would like to show in the frontend that a task has been actually picked up and started in the queue but there's no callback for that.

As the documentation says:

> Having a ‘started’ state can be useful for when there are long running tasks and there’s a need to report what task is currently running.

But how can we report, or send notifications or tell the users in the frontend which of the many batch calculations in the queue is currently running, if there's no callback?

Hope this makes sense.
I don't know why `on_started` handler doesn't exist while others does.
Below structure can be workaround.
```
class BaseTask(Task):
    def __call__(self, *args, **kwargs):
        self.before_run()
        return super(BaseTask, self).__call__(*args, **kwargs)
    def before_run(self):
        #do stuff
        pass

@app.task(base=BaseTask)
def task1():
    pass

```
I too have a use case for `on_started` if possible im looking for `on_progress` (custom state) also.
The workaround specified by @aydin will so but the task state will not update right?
@pavankumarkatakam no, you need to update manually the state. See here an example http://docs.celeryproject.org/en/latest/userguide/tasks.html#custom-states
To other people who find this issue & code suggestion from @aydin, so that you don't spend much time debugging.

As of recent-ish updates to Celery docs following #5652, it is recommended to call `self.run` instead of `super.__call__` when overriding the `__call__` method. We have hit a similar scenario during testing when using `__call__` as described in #5643 where tasks are not enqueued for retries.
> To other people who find this issue & code suggestion from @aydin, so that you don't spend much time debugging.
> 
> As of recent-ish updates to Celery docs following #5652, it is recommended to call `self.run` instead of `super.__call__` when overriding the `__call__` method. We have hit a similar scenario during testing when using `__call__` as described in #5643 where tasks are not enqueued for retries.

so should we close this issue?
I think we can add this to the documentation or add the hook if everyone wants it.

Resolving patch:
diff --git a/celery/app/task.py b/celery/app/task.py
--- a/celery/app/task.py
+++ b/celery/app/task.py
@@ -972,6 +972,20 @@ def update_state(self, task_id=None, state=None, meta=None, **kwargs):
         self.backend.store_result(
             task_id, meta, state, request=self.request, **kwargs)
 
+    def before_start(self, task_id, args, kwargs):
+        """Handler called before the task starts.
+
+        .. versionadded:: 5.2
+
+        Arguments:
+            task_id (str): Unique id of the task to execute.
+            args (Tuple): Original arguments for the task to execute.
+            kwargs (Dict): Original keyword arguments for the task to execute.
+
+        Returns:
+            None: The return value of this handler is ignored.
+        """
+
     def on_success(self, retval, task_id, args, kwargs):
         """Success handler.
 
diff --git a/celery/app/trace.py b/celery/app/trace.py
--- a/celery/app/trace.py
+++ b/celery/app/trace.py
@@ -345,8 +345,11 @@ def build_tracer(name, task, loader=None, hostname=None, store_errors=True,
     loader_task_init = loader.on_task_init
     loader_cleanup = loader.on_process_cleanup
 
+    task_before_start = None
     task_on_success = None
     task_after_return = None
+    if task_has_custom(task, 'before_start'):
+        task_before_start = task.before_start
     if task_has_custom(task, 'on_success'):
         task_on_success = task.on_success
     if task_has_custom(task, 'after_return'):
@@ -442,6 +445,9 @@ def trace_task(uuid, args, kwargs, request=None):
 
                 # -*- TRACE -*-
                 try:
+                    if task_before_start:
+                        task_before_start(uuid, args, kwargs)
+
                     R = retval = fun(*args, **kwargs)
                     state = SUCCESS
                 except Reject as exc:


Test patch:
diff --git a/t/unit/tasks/test_trace.py b/t/unit/tasks/test_trace.py
--- a/t/unit/tasks/test_trace.py
+++ b/t/unit/tasks/test_trace.py
@@ -61,6 +61,14 @@ def test_trace_successful(self):
         assert info is None
         assert retval == 4
 
+    def test_trace_before_start(self):
+        @self.app.task(shared=False, before_start=Mock())
+        def add_with_before_start(x, y):
+            return x + y
+
+        self.trace(add_with_before_start, (2, 2), {})
+        add_with_before_start.before_start.assert_called()
+
     def test_trace_on_success(self):
         @self.app.task(shared=False, on_success=Mock())
         def add_with_success(x, y):