Threading

Configuration

Property

Default

Description

pin_apps

True

Whether to pin apps to threads

total_threads

None

The total number of threads to create, defaults to the number of active apps.

pin_threads

None

The number (out of the total) of threads to reserve for pinning; only used by select_q when pin_apps=False. Defaults to all threads

Thread Creation

  • Threads are created during startup in create_initial_threads().

  • User-configured values are resolved into actual numbers using resolve_thread_counts().

  • Threads all have an ID number (0 - <appdaemon.total_threads>)

  • Some of them are reserved for pinned callbacks (0 - <appdaemon.pin_threads>)

  • The remaining are free for unpinned callbacks

  • AppDaemon will assign a pin_thread to an app when it’s created, if pin_app=True and pin_thread=None.

    • Must be configured in file or have global setting to pin_app=False to not get an assigned thread.

  • Pin behavior and thread assignments are stored in ManagedObject attributes

Thread Pinning

These settings get passed around through the AppDaemon internals and ultimately determine which thread handles a callback.

Setting

Description

pin_app

Whether the app should be pinned to a single thread.

pin_thread

The thread the app is pinned to. This determines which thread the callback gets run in

Level

Description

Global

The global settings are set in the appdaemon.yaml file, and each app defaults to the global setting when it’s created.

Per App

The pinning behavior for apps can be changed at runtime. The current settings are stored in the corresponding ManagedObject

Per Callback

The pinning behavior for a specific callback can be overridden at registration.

  • App gets created by the app manager

    • App object and settings are stored in the ManagedObject

    • pin_app is set from the config file, defaulting to global setting

    • pin_thread could be set from the config file, but could be assigned by AD at that point

  • App runs and maybe the pin_app or pin_thread gets changed at some point.

    • Any changes affect the settings in ManagedObject

  • Callback could also override either pin_app or pin_thread

    • pin_app should be calculated at callback registration

      • If the callback specifies a pin_thread that implies pin_app=True, otherwise the pin_thread will get ignored in select_q

      • If pin_app ends up being True, but there’s no pin_thread assigned, one needs to be assigned at call time - will be 0.

      • If pin_app is False, pin_thread will get ignored at call time

    • Callback lives in internal dicts

      • pin_app is never None

      • pin_thread could be None - load distribution should be calculated at call time.

  • pin_app and pin_thread make their way through the internals, eventually to Threading.select_q

    • select_q hinges on pin_app being True/False.

    • pin_app is never None

    • If pin_app is not True, pin_thread is ignored, and the thread is automatically chosen according to the load distribution setting

Config Precedence

  1. Callback-level pin: Top-level override

  2. App-level pin: Acts as the default for each callback as it’s created.

  3. Config-level pin (default): Acts as the default when the app manager creates a new ManagedObject

Default Case

By default, a new thread will be created for each active app, and each app will get pinned to its own thread.

  • Default (initial case)

    • Global: pin_apps=True

    • App: pin_app=None

  • Each app has a ManagedObject instance that holds the pin_app and pin_thread settings for that app.

  • When an app is created pin_app=True gets set (from the global default) and pin_thread gets assigned.

  • The pin_app and pin_thread settings for an app can be altered in the ManagedObject during runtime.

  • The pin_app and pin_thread settings can be provided during callback registration to override the settings for that callback only.

Callback Registration

The determine_thread() method is used during callback registration to resolve pin_app for that callback.

Callback Type

Registration

Processing

Schedule

insert_schedule()

exec_schedule()

Event

add_event_callback()

process_event_callbacks()

State

add_state_callback()

process_state_callbacks()

Log

add_log_callback()

process_log_callbacks()

Reference

class appdaemon.threads.Threading(ad: AppDaemon)

Subsystem container for managing Thread objects

async add_thread(silent: bool = False, id: int | None = None) None

Create a new worker thread.

This is where the Thread object is actually created and started. The thread will begin running the worker method as its target, and a new entity will be added to the thread domain in the admin namespace to track it.

determine_thread(name: str, cb_pin: bool | None, cb_pin_thread: int | None) tuple[bool, int | None]

Determine pin settings for a callback using inputs from the callback registration with settings from the app management as defaults.

If the callback thread is not specified, then which thread it gets called in should be calculated at call time to get good results from the different load distribution strategies. The length of the various thread queues can be wildly different at call time from when the callback was first registered.

Dev Note:

This method is a good place to handle things related to thread/pinning at callback registration.

Returns:

Whether to pin the callback and if so, what thread it should be pinned to.

Return type:

tuple[bool, int | None]

async dispatch_worker(name: str, args: dict[str, Any])

Apply any constraints and if they pass, dispatch the callback to the worker thread by using the select_q method.

select_q(args: dict[str, Any])

Selects the queue for the thread and calls put_nowait on it to dispatch the callback to the worker thread.