pod_config_reload
Hot-reload capability for pod datasource configuration.
Provides config-file polling, hub polling, and atomic datasource swapping via a
composed DatasourceManager. Instantiated by Pod and used as a composed object
(Pod._config_reloader).
Classes
ConfigReloadMtimeMonitor
class ConfigReloadMtimeMonitor():Thread-safe high-water mark for the last-processed config mtime.
Shared between file polling, hub polling, and on-demand reload paths so all have a consistent view of "what has already been processed".
Each reload path advances the value after successfully processing (or deliberately skipping) a change, preventing other paths from redundantly reprocessing it.
Methods
advance
def advance(self, value: float) ‑> None:Advance the high-water mark to value if it exceeds the current value.
get
def get(self) ‑> float:Return the last-processed mtime (thread-safe).
ConfigReloadSettings
class ConfigReloadSettings( config_path: Path | None = None, file_poll_interval_seconds: float | None = None, hub_poll_interval_seconds: float | None = None, initial_pod_config: PodConfig | None = None,):Configuration for the hot-reload subsystem, passed as a single object to Pod.
Variables
- static
config_path : Path | None
- static
file_poll_interval_seconds : float | None
- static
hub_poll_interval_seconds : float | None
- static
initial_pod_config : PodConfig | None
DatasourceConfigReloader
class DatasourceConfigReloader( *, datasource_manager: DatasourceManager, hub: BitfountHub, pod_name: str, config_path: Path | None = None, file_poll_interval_seconds: float | None = None, hub_poll_interval_seconds: float | None = None, initial_pod_config: PodConfig | None = None, on_datasources_changed: Callable[[set[str], set[str], list[DatasourceConfig]], Awaitable[None]] | None = None, link_new_datasets_to_cluster: Callable[[list[str]], None] | None = None,):Composed object providing hot-reload for pod datasource configuration.
Four reload paths are supported, all converging on reload_datasources:
- File polling: call
start_polling— a background daemon thread watches the config file's mtime and reloads automatically. - On task receipt: call
check_and_reload_if_changed— checks both the config file mtime and the hub for changes before processing a task, reloading if either source has changed. - Orchestrator push: call
reload_from_pod_config(orreload_from_datasource_configs) — applies config pushed via HTTP. - Hub polling: call
start_polling— a background thread polls the hub for config changes using server-sidelastUpdatedtimestamps.
Mtime interplay:
Paths 1 & 2 compare the file's mtime against _last_mtime. When
the orchestrator pushes a config (path 3) or the hub poller applies a
change (path 4), _last_mtime is set to time.time() so that
file-based polling won't revert the remote update with stale file
contents.
Thread safety:
The atomic swap of base_datasources is safe under CPython's GIL.
Workers that have already obtained a datasource reference will continue
using the old config until their task completes.
Variables
config_path : pathlib.Path | None- The config file path being watched.
event_loop : asyncio.events.AbstractEventLoop | None- The pod's event loop, set when polling starts.
file_poll_interval_seconds : float | None- The file polling interval in seconds.
file_polling_supported : bool- Whether file polling can be started (config_path and interval set).
hub_poll_interval_seconds : float | None- The hub polling interval in seconds.
hub_polling_supported : bool- Whether hub polling can be started (interval set).
Methods
check_and_reload_if_changed
async def check_and_reload_if_changed(self) ‑> bool:Check all configured sources for config changes and reload if needed.
Intended for on-demand use from an async context (e.g. upon task receipt). Runs hub-based then file-based checks, swallowing errors from each independently so that a failure in one does not prevent the other from executing. Hub runs first to establish the timestamp baseline; the file check then runs and wins if the file is genuinely more recent than the hub's last change.
Returns True if any reload was performed, False otherwise.
check_file_and_reload_if_changed
async def check_file_and_reload_if_changed(self) ‑> bool:Check the config file for changes and reload if needed.
check_hub_and_reload_if_changed
async def check_hub_and_reload_if_changed(self) ‑> bool:Check the hub for config changes and reload if needed.
reload_datasources
async def reload_datasources( self, new_datasource_configs: list[DatasourceConfig], *, origin: "Literal['file', 'hub']" = 'hub',) ‑> None:Atomically reload datasources from new configuration.
Handles three cases:
- Removed datasets are dropped from local datasources.
- New datasets are registered on the hub.
- Existing datasets are updated (upsert via register_pod).
This method is safe to call from any thread. In-flight workers will continue using their existing datasource references until completion.
Arguments
new_datasource_configs: The new datasource configurations to apply.origin: Source of the reload request. File reloads may link new datasets to the pod compute cluster; hub reloads do not.
reload_from_datasource_configs
async def reload_from_datasource_configs( self, datasource_configs: list[DatasourceConfig],) ‑> None:Reload datasources directly from a list of datasource configs.
Skips pod-level validation — the caller takes responsibility for ensuring pod-level settings are compatible. Intended for use cases where only datasource configs are available (no full PodConfig).
Arguments
datasource_configs: The new datasource configurations to apply.
reload_from_pod_config
async def reload_from_pod_config(self, pod_config: PodConfig) ‑> None:Validate pod-level fields and reload datasources from a full PodConfig.
Intended for use when the orchestrator pushes a new config (not
file-backed). Validates that pod-level fields are unchanged, then
applies datasource changes via reload_datasources.
Arguments
pod_config: The new full pod configuration.
Raises
PodConfigReloadError: If pod-level configuration has changed (caller should trigger a pod restart).ValueError: If the config contains no datasources.
Note
Changes to config_reload_file_poll_interval_seconds are silently
ignored (it is excluded from pod-level comparison). The polling
interval cannot be changed at runtime without a pod restart.
start_polling
def start_polling(self, mailbox: _PodMailbox | None = None) ‑> None:Start background polling for config changes (paths 1 & 4).
Starts file polling if file_polling_supported, hub polling if
hub_polling_supported. Raises if neither is available.
Arguments
mailbox: The pod's mailbox for re-registering dataset names after reload.
stop_polling
def stop_polling(self) ‑> None:Stop all polling (file and hub).
update_pod_name
def update_pod_name(self, pod_name: str) ‑> None:Update the cached pod name used when reconnecting to the message service.
The reloader caches the pod name at construction; identifier-length
enforcement can shorten it afterwards. A reload reconnect calls
connect_pod directly (bypassing the mailbox boundary guard), so the
cached name must be kept in sync with the enforced one or the reconnect
is rejected as over-length.
PodConfigReloadError
class PodConfigReloadError(*args, **kwargs):Raised when a config reload cannot proceed or is rejected.