# How the Music Assistant Task Manager Handles Background Tasks

> Learn how the Music Assistant task manager handles background tasks. Discover its robust features for scheduling, queuing, and monitoring async operations.

- Repository: [Music Assistant/server](https://github.com/music-assistant/server)
- Tags: internals
- Published: 2026-06-21

---

**The Music Assistant server uses a dedicated `TasksController` in [`music_assistant/controllers/tasks/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/tasks/controller.py) to schedule, queue, and monitor asynchronous background tasks with built-in concurrency limits, persistence, and progress tracking.**

The **Music Assistant task manager background tasks** system provides the infrastructure for long-running operations like library scans and metadata refreshes without blocking the main event loop. Located in the `music_assistant.controllers.tasks` package within the [music-assistant/server](https://github.com/music-assistant/server) repository, this subsystem manages everything from ad-hoc job queuing to recurring scheduled maintenance.

## Core Architecture – The TasksController

The `TasksController` class in [`music_assistant/controllers/tasks/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/tasks/controller.py) serves as the central nervous system for all background operations. It inherits from `CoreController`, giving it access to the main `MusicAssistant` instance and standard lifecycle hooks.

### Task Registration and Storage

The controller distinguishes between two primary task types:

- **Ad-hoc tasks** – Created via `run_background_task()` for one-time operations like user-initiated playlist refreshes
- **Recurring tasks** – Registered via `register_scheduled_task()` for periodic maintenance like daily library cleanup

Both methods create a `BackgroundTask` model (from `music_assistant_models.background_task`) and wrap it in a `ManagedTask` container defined in [`models.py`](https://github.com/music-assistant/server/blob/main/models.py). The controller stores these in `self._tasks`, a dictionary mapping task IDs to `ManagedTask` instances.

### Queueing and Execution Logic

Tasks enter a `deque` called `self._pending_task_ids` where priority tasks (flagged with `priority=True`) are inserted at the front. The controller respects `self._max_concurrent_tasks`, defaulting to **2** concurrent executions as defined in [`constants.py`](https://github.com/music-assistant/server/blob/main/constants.py).

When the worker loop picks a task ID, it creates an `asyncio.Task` to run the provider-supplied coroutine. During execution, the `ACTIVE_TASK_ID` context variable (defined in [`constants.py`](https://github.com/music-assistant/server/blob/main/constants.py)) is set so that nested calls can locate the current task via `TaskExecutionContext` from [`context.py`](https://github.com/music-assistant/server/blob/main/context.py).

### Scheduling and Persistence

Recurring tasks integrate with the server's timer subsystem using `self.mass.schedule_timer` and `self.mass.cancel_timer`. The next-run timestamp is stored in `BackgroundTask.schedule.next_run` and persisted under the config key `TASK_STATE_CONFIG_KEY` (`"scheduled_task_states"`).

On shutdown, the controller unregisters all tasks, removes the `TaskLogHandler`, and clears persisted schedule state to ensure clean restarts.

## Supporting Infrastructure

### Constants and Configuration

The [`constants.py`](https://github.com/music-assistant/server/blob/main/constants.py) file defines critical operational limits:

- `DEFAULT_TASK_LOG_LINES` (250) – Maximum log entries retained per task
- `MAX_FINISHED_TASK_HISTORY` (100) – Cap on completed non-recurring tasks
- `TASK_LIFECYCLE_UPDATE_DEBOUNCE` (0.25 seconds) – Debounce period for UI updates

### Execution Context and Logging

The [`helpers.py`](https://github.com/music-assistant/server/blob/main/helpers.py) module provides `TaskLogHandler`, a custom logging handler that intercepts output from any Python `logging` call made within a task. Log lines are trimmed to the 250-line limit and stored in the task's `logs` list. The `update_task_progress` method allows providers to report completion percentages and status messages.

### State Serialization

Helper functions `serialize_task_state` and `restore_task_state` in [`helpers.py`](https://github.com/music-assistant/server/blob/main/helpers.py) handle JSON encoding of schedule states, enabling task persistence across server restarts.

## Implementing Background Tasks

Providers and controllers interact with the task manager through straightforward async APIs.

### Registering a Recurring Task

```python

# From a provider or core controller

tasks_controller.register_scheduled_task(
    task_id="library_cleanup",
    name="Library cleanup",
    handler=cleanup_library,                     # async callable

    schedule=TaskSchedule(interval=86_400),      # every 24 hours

    translation_key="background_task.library_cleanup",
)

```

### Running an Ad-Hoc Task

```python

# Queue immediate execution with priority

await tasks_controller.run_background_task(
    name="Refresh playlist",
    handler=refresh_playlist,                    # async callable

    translation_key="background_task.refresh_playlist",
    user_id=current_user.user_id,
    priority=True,                               # insert at front of queue

)

```

### Querying via WebSocket API

Clients can retrieve visible tasks through the WebSocket API:

```python
await websocket.send_json({"command": "tasks/list"})
response = await websocket.receive_json()
for task in response["result"]:
    print(f"{task['name']} – {task['status']} (progress {task.get('progress', 0)}%)")

```

## API Integration and Security

The `TasksController` exposes several `@api_command` decorated methods including `tasks/list`, `tasks/get`, `tasks/run`, and `tasks/cancel`. The permission model uses `UserRole` enforcement—only administrators can execute, retry, or cancel arbitrary tasks.

Non-admin users are restricted to viewing tasks they started via `list_tasks_for_user` and `_get_visible_managed_task`, which validate `task_info.user_id` against the requesting user.

## Summary

- **The `TasksController`** in [`music_assistant/controllers/tasks/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/tasks/controller.py) manages the full lifecycle of background tasks with a default concurrency limit of 2.
- **Task persistence** utilizes the config key `"scheduled_task_states"` to survive server restarts.
- **Thread safety** is enforced by checking `get_ident()` against `self.mass.loop_thread_id` and rescheduling via `call_soon_threadsafe`.
- **Progress tracking** combines `update_task_progress` calls with automatic log capture via `TaskLogHandler`.
- **API security** restricts sensitive operations to admin users while allowing non-admins to view their own tasks.

## Frequently Asked Questions

### How does Music Assistant limit concurrent background tasks?

The `TasksController` enforces a concurrency cap of **2** by default through `self._max_concurrent_tasks` defined in [`constants.py`](https://github.com/music-assistant/server/blob/main/constants.py). The worker loop only creates new `asyncio.Task` instances when the number of active tasks falls below this limit, queueing additional task IDs in `self._pending_task_ids` until capacity becomes available.

### Where are scheduled task states stored in Music Assistant?

Scheduled task states are serialized using `serialize_task_schedule_state` from [`helpers.py`](https://github.com/music-assistant/server/blob/main/helpers.py) and stored under the configuration key `"scheduled_task_states"` (`TASK_STATE_CONFIG_KEY`). This JSON-encoded state persists the `next_run` timestamps and schedule parameters, allowing recurring tasks to resume their cadence after server restarts.

### How can providers update task progress?

Providers call `self.tasks.update_task_progress(progress, text)` where `progress` is an integer percentage and `text` is a descriptive message. These updates are automatically debounced by 0.25 seconds (`TASK_LIFECYCLE_UPDATE_DEBOUNCE`) to prevent UI flooding. Additionally, any standard Python logging output from the task is captured by `TaskLogHandler` and stored in the task's `logs` list (capped at 250 lines).

### What happens to finished tasks in the Music Assistant task manager?

Completed non-recurring tasks are retained in a history buffer capped at **100** entries (`MAX_FINISHED_TASK_HISTORY`). Administrators can manually clear this history via the `tasks/clear_finished` API command. On controller shutdown, all tasks are unregistered and log handlers removed to ensure a clean state for the next server launch.