From 7c570438f5a34009a1ce15acb1abdace7e1604df Mon Sep 17 00:00:00 2001 From: Piyush Bafna <130243298+piyushdev04@users.noreply.github.com> Date: Mon, 26 Jan 2026 23:26:31 +0530 Subject: [PATCH 1/7] [connection] Ignore current task when checking active tasks #1204 The _is_update_in_progress function was incorrectly detecting the current Celery task as another running task, causing update_config to exit early. This fix excludes the current task by comparing task IDs, ensuring only other instances for the same device are considered. Fixes #1204 --- openwisp_controller/connection/tasks.py | 10 +++- .../connection/tests/test_tasks.py | 58 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/openwisp_controller/connection/tasks.py b/openwisp_controller/connection/tasks.py index d75bbde20..00da80a7b 100644 --- a/openwisp_controller/connection/tasks.py +++ b/openwisp_controller/connection/tasks.py @@ -2,7 +2,7 @@ import time import swapper -from celery import current_app, shared_task +from celery import current_app, current_task, shared_task from celery.exceptions import SoftTimeLimitExceeded from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import gettext_lazy as _ @@ -20,11 +20,17 @@ def _is_update_in_progress(device_id): active = current_app.control.inspect().active() if not active: return False + current_task_id = getattr(current_task, 'request', None) + if current_task_id: + current_task_id = current_task_id.id + else: + current_task_id = None # check if there's any other running task before adding it for task_list in active.values(): for task in task_list: if task["name"] == _TASK_NAME and str(device_id) in task["args"]: - return True + if task.get("id") != current_task_id: + return True return False diff --git a/openwisp_controller/connection/tests/test_tasks.py b/openwisp_controller/connection/tests/test_tasks.py index 700dbbf32..9186c772e 100644 --- a/openwisp_controller/connection/tests/test_tasks.py +++ b/openwisp_controller/connection/tests/test_tasks.py @@ -89,6 +89,64 @@ def test_launch_command_exception(self, *args): self.assertEqual(command.output, "Internal system error: test error\n") +class TestIsUpdateInProgress(CreateConnectionsMixin, TestCase): + @mock.patch('openwisp_controller.connection.tasks.current_task') + @mock.patch('openwisp_controller.connection.tasks.current_app') + def test_is_update_in_progress_same_worker(self, mocked_current_app, mocked_current_task): + device_id = 1 + mocked_current_task.request.id = 'task123' + mocked_inspect = mock.Mock() + mocked_current_app.control.inspect.return_value = mocked_inspect + mocked_inspect.active.return_value = { + 'worker1': [ + {'name': 'openwisp_controller.connection.tasks.update_config', 'args': ['1'], 'id': 'task123'} + ] + } + result = tasks._is_update_in_progress(device_id) + self.assertFalse(result) + + @mock.patch('openwisp_controller.connection.tasks.current_task') + @mock.patch('openwisp_controller.connection.tasks.current_app') + def test_is_update_in_progress_different_worker(self, mocked_current_app, mocked_current_task): + device_id = 1 + mocked_current_task.request.id = 'task123' + mocked_inspect = mock.Mock() + mocked_current_app.control.inspect.return_value = mocked_inspect + mocked_inspect.active.return_value = { + 'worker2': [ + {'name': 'openwisp_controller.connection.tasks.update_config', 'args': ['1'], 'id': 'task456'} + ] + } + result = tasks._is_update_in_progress(device_id) + self.assertTrue(result) + + @mock.patch('openwisp_controller.connection.tasks.current_task') + @mock.patch('openwisp_controller.connection.tasks.current_app') + def test_is_update_in_progress_no_active_tasks(self, mocked_current_app, mocked_current_task): + device_id = 1 + mocked_current_task.request.id = 'task123' + mocked_inspect = mock.Mock() + mocked_current_app.control.inspect.return_value = mocked_inspect + mocked_inspect.active.return_value = {} + result = tasks._is_update_in_progress(device_id) + self.assertFalse(result) + + @mock.patch('openwisp_controller.connection.tasks.current_task') + @mock.patch('openwisp_controller.connection.tasks.current_app') + def test_is_update_in_progress_different_device(self, mocked_current_app, mocked_current_task): + device_id = 1 + mocked_current_task.request.id = 'task123' + mocked_inspect = mock.Mock() + mocked_current_app.control.inspect.return_value = mocked_inspect + mocked_inspect.active.return_value = { + 'worker1': [ + {'name': 'openwisp_controller.connection.tasks.update_config', 'args': ['2'], 'id': 'task456'} + ] + } + result = tasks._is_update_in_progress(device_id) + self.assertFalse(result) + + class TestTransactionTasks( TestRegistrationMixin, CreateConnectionsMixin, TransactionTestCase ): From cf7966bafeb849b0f4787d01f0cb0e673854cee1 Mon Sep 17 00:00:00 2001 From: Piyush Bafna <130243298+piyushdev04@users.noreply.github.com> Date: Mon, 26 Jan 2026 23:56:09 +0530 Subject: [PATCH 2/7] fix: [connection/tasks] Ignore current task instance when checking active tasks - #1204 The _is_update_in_progress function was incorrectly detecting the current Celery task as another running task, causing update_config to exit early. This fix excludes the current task by comparing task IDs, ensuring only other instances for the same device are considered. Added tests to cover same worker (should not skip) and different worker (should skip) scenarios. Fixes #1204 --- .../connection/tests/test_tasks.py | 64 ++++++++++++------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/openwisp_controller/connection/tests/test_tasks.py b/openwisp_controller/connection/tests/test_tasks.py index 9186c772e..857beca8d 100644 --- a/openwisp_controller/connection/tests/test_tasks.py +++ b/openwisp_controller/connection/tests/test_tasks.py @@ -90,57 +90,77 @@ def test_launch_command_exception(self, *args): class TestIsUpdateInProgress(CreateConnectionsMixin, TestCase): - @mock.patch('openwisp_controller.connection.tasks.current_task') - @mock.patch('openwisp_controller.connection.tasks.current_app') - def test_is_update_in_progress_same_worker(self, mocked_current_app, mocked_current_task): + @mock.patch("openwisp_controller.connection.tasks.current_task") + @mock.patch("openwisp_controller.connection.tasks.current_app") + def test_is_update_in_progress_same_worker( + self, mocked_current_app, mocked_current_task + ): device_id = 1 - mocked_current_task.request.id = 'task123' + mocked_current_task.request.id = "task123" mocked_inspect = mock.Mock() mocked_current_app.control.inspect.return_value = mocked_inspect mocked_inspect.active.return_value = { - 'worker1': [ - {'name': 'openwisp_controller.connection.tasks.update_config', 'args': ['1'], 'id': 'task123'} + "worker1": [ + { + "name": "openwisp_controller.connection.tasks.update_config", + "args": ["1"], + "id": "task123", + } ] } result = tasks._is_update_in_progress(device_id) self.assertFalse(result) - @mock.patch('openwisp_controller.connection.tasks.current_task') - @mock.patch('openwisp_controller.connection.tasks.current_app') - def test_is_update_in_progress_different_worker(self, mocked_current_app, mocked_current_task): + @mock.patch("openwisp_controller.connection.tasks.current_task") + @mock.patch("openwisp_controller.connection.tasks.current_app") + def test_is_update_in_progress_different_worker( + self, mocked_current_app, mocked_current_task + ): device_id = 1 - mocked_current_task.request.id = 'task123' + mocked_current_task.request.id = "task123" mocked_inspect = mock.Mock() mocked_current_app.control.inspect.return_value = mocked_inspect mocked_inspect.active.return_value = { - 'worker2': [ - {'name': 'openwisp_controller.connection.tasks.update_config', 'args': ['1'], 'id': 'task456'} + "worker2": [ + { + "name": "openwisp_controller.connection.tasks.update_config", + "args": ["1"], + "id": "task456", + } ] } result = tasks._is_update_in_progress(device_id) self.assertTrue(result) - @mock.patch('openwisp_controller.connection.tasks.current_task') - @mock.patch('openwisp_controller.connection.tasks.current_app') - def test_is_update_in_progress_no_active_tasks(self, mocked_current_app, mocked_current_task): + @mock.patch("openwisp_controller.connection.tasks.current_task") + @mock.patch("openwisp_controller.connection.tasks.current_app") + def test_is_update_in_progress_no_active_tasks( + self, mocked_current_app, mocked_current_task + ): device_id = 1 - mocked_current_task.request.id = 'task123' + mocked_current_task.request.id = "task123" mocked_inspect = mock.Mock() mocked_current_app.control.inspect.return_value = mocked_inspect mocked_inspect.active.return_value = {} result = tasks._is_update_in_progress(device_id) self.assertFalse(result) - @mock.patch('openwisp_controller.connection.tasks.current_task') - @mock.patch('openwisp_controller.connection.tasks.current_app') - def test_is_update_in_progress_different_device(self, mocked_current_app, mocked_current_task): + @mock.patch("openwisp_controller.connection.tasks.current_task") + @mock.patch("openwisp_controller.connection.tasks.current_app") + def test_is_update_in_progress_different_device( + self, mocked_current_app, mocked_current_task + ): device_id = 1 - mocked_current_task.request.id = 'task123' + mocked_current_task.request.id = "task123" mocked_inspect = mock.Mock() mocked_current_app.control.inspect.return_value = mocked_inspect mocked_inspect.active.return_value = { - 'worker1': [ - {'name': 'openwisp_controller.connection.tasks.update_config', 'args': ['2'], 'id': 'task456'} + "worker1": [ + { + "name": "openwisp_controller.connection.tasks.update_config", + "args": ["2"], + "id": "task456", + } ] } result = tasks._is_update_in_progress(device_id) From 19110f1011b60faec1361fd79945f85805a46b54 Mon Sep 17 00:00:00 2001 From: Piyush Bafna <130243298+piyushdev04@users.noreply.github.com> Date: Tue, 27 Jan 2026 00:12:40 +0530 Subject: [PATCH 3/7] [fix/connection/tasks] Ignore current task instance when checking active tasks - #1204 The _is_update_in_progress function was incorrectly detecting the current Celery task as another running task, causing update_config to exit early. This fix excludes the current task by comparing task IDs, ensuring only other instances for the same device are considered. Added tests to cover same worker (should not skip) and different worker (should skip) scenarios. Fixes #1204 --- openwisp_controller/connection/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openwisp_controller/connection/tasks.py b/openwisp_controller/connection/tasks.py index 00da80a7b..cb1c0a57e 100644 --- a/openwisp_controller/connection/tasks.py +++ b/openwisp_controller/connection/tasks.py @@ -20,7 +20,7 @@ def _is_update_in_progress(device_id): active = current_app.control.inspect().active() if not active: return False - current_task_id = getattr(current_task, 'request', None) + current_task_id = getattr(current_task, "request", None) if current_task_id: current_task_id = current_task_id.id else: From 15804375944dc8ac18ea33be84705b06bde81a79 Mon Sep 17 00:00:00 2001 From: Piyush Bafna <130243298+piyushdev04@users.noreply.github.com> Date: Tue, 27 Jan 2026 00:38:19 +0530 Subject: [PATCH 4/7] [fix/connection/tasks] Ignore current task instance when checking active tasks - #1204 The _is_update_in_progress function was incorrectly detecting the current Celery task as another running task, causing update_config to exit early. This fix excludes the current task by comparing task IDs, ensuring only other instances for the same device are considered. Added tests to cover same worker (should not skip) and different worker (should skip) scenarios. Fixes #1204 --- openwisp_controller/connection/tasks.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/openwisp_controller/connection/tasks.py b/openwisp_controller/connection/tasks.py index cb1c0a57e..08eec5053 100644 --- a/openwisp_controller/connection/tasks.py +++ b/openwisp_controller/connection/tasks.py @@ -20,18 +20,12 @@ def _is_update_in_progress(device_id): active = current_app.control.inspect().active() if not active: return False - current_task_id = getattr(current_task, "request", None) - if current_task_id: - current_task_id = current_task_id.id - else: - current_task_id = None - # check if there's any other running task before adding it - for task_list in active.values(): - for task in task_list: - if task["name"] == _TASK_NAME and str(device_id) in task["args"]: - if task.get("id") != current_task_id: - return True - return False + current_task_id = current_task.request.id if current_task and current_task.request else None + return any( + task["name"] == _TASK_NAME and str(device_id) in task["args"] and task.get("id") != current_task_id + for task_list in active.values() + for task in task_list + ) @shared_task From 7394bd8c8f86ef86d75738fcd95acc18b7542410 Mon Sep 17 00:00:00 2001 From: Piyush Bafna <130243298+piyushdev04@users.noreply.github.com> Date: Tue, 27 Jan 2026 00:39:54 +0530 Subject: [PATCH 5/7] [fix/connection/tasks] Ignore current task instance when checking active tasks - #1204 The _is_update_in_progress function was incorrectly detecting the current Celery task as another running task, causing update_config to exit early. This fix excludes the current task by comparing task IDs, ensuring only other instances for the same device are considered. Added tests to cover same worker (should not skip) and different worker (should skip) scenarios. Fixes #1204 --- openwisp_controller/connection/tasks.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openwisp_controller/connection/tasks.py b/openwisp_controller/connection/tasks.py index 08eec5053..38a7ffaeb 100644 --- a/openwisp_controller/connection/tasks.py +++ b/openwisp_controller/connection/tasks.py @@ -20,9 +20,13 @@ def _is_update_in_progress(device_id): active = current_app.control.inspect().active() if not active: return False - current_task_id = current_task.request.id if current_task and current_task.request else None + current_task_id = ( + current_task.request.id if current_task and current_task.request else None + ) return any( - task["name"] == _TASK_NAME and str(device_id) in task["args"] and task.get("id") != current_task_id + task["name"] == _TASK_NAME + and str(device_id) in task["args"] + and task.get("id") != current_task_id for task_list in active.values() for task in task_list ) From 3c989ae2da4439cb504188e4bf073a88930143bd Mon Sep 17 00:00:00 2001 From: Piyush Bafna <130243298+piyushdev04@users.noreply.github.com> Date: Sun, 8 Feb 2026 11:26:15 +0530 Subject: [PATCH 6/7] [fix/connection/tasks] Ignore current task instance when checking active tasks #1204 The _is_update_in_progress function was incorrectly detecting the current Celery task as another running task, causing update_config to exit early. This fix excludes the current task by comparing task IDs, ensuring only other instances for the same device are considered. Added tests to cover same worker (should not skip) and different worker (should skip) scenarios. Additionally, added INFO-level logging to convey when a task is skipped because another task is in progress. Fixes #1204 --- openwisp_controller/connection/tasks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openwisp_controller/connection/tasks.py b/openwisp_controller/connection/tasks.py index 38a7ffaeb..58d38d053 100644 --- a/openwisp_controller/connection/tasks.py +++ b/openwisp_controller/connection/tasks.py @@ -53,6 +53,9 @@ def update_config(device_id): logger.warning(f'update_config("{device_id}") failed: {e}') return if _is_update_in_progress(device_id): + logger.info( + f"Skipping update_config for device {device_id} as another task is in progress." + ) return try: device_conn = DeviceConnection.get_working_connection(device) From 12c017851bcb43a6f7c199aa20320c905a508a9a Mon Sep 17 00:00:00 2001 From: Piyush Bafna <130243298+piyushdev04@users.noreply.github.com> Date: Sun, 8 Feb 2026 11:26:15 +0530 Subject: [PATCH 7/7] [fix/connection/tasks] Ignore current task instance when checking active tasks #1204 The _is_update_in_progress function was incorrectly detecting the current Celery task as another running task, causing update_config to exit early. This fix excludes the current task by comparing task IDs, ensuring only other instances for the same device are considered. Added tests to cover same worker (should not skip) and different worker (should skip) scenarios. Additionally, added INFO-level logging to convey when a task is skipped because another task is in progress. Fixes #1204 --- openwisp_controller/connection/tasks.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openwisp_controller/connection/tasks.py b/openwisp_controller/connection/tasks.py index 58d38d053..38a7ffaeb 100644 --- a/openwisp_controller/connection/tasks.py +++ b/openwisp_controller/connection/tasks.py @@ -53,9 +53,6 @@ def update_config(device_id): logger.warning(f'update_config("{device_id}") failed: {e}') return if _is_update_in_progress(device_id): - logger.info( - f"Skipping update_config for device {device_id} as another task is in progress." - ) return try: device_conn = DeviceConnection.get_working_connection(device)