Local runtime and process ownership¶
lcl-fastapi serve -o config service.lclcfg owns one foreground service. The
configuration file is trusted application configuration. Keep its state directory
private to the service account and dedicate it to this service. The framework
coordinates one service on one machine; it does not coordinate separate services
or machines.
On Windows, Uvicorn owns worker creation and recovery. On
Linux, Gunicorn's native ASGI worker and arbiter own them. A worker
loads the current configuration and creates its own Frame, logger, Snowflake
generator, and application lifespan. The framework does not add a supervisor.
Code-registered background threads are managed inside that API lifespan, without
another API process supervisor; see background workers.
The configuration file's directory is available on each worker's Python import
path, so an adjacent app.py can be the target app:service even when the
installed console command is invoked from a different working directory.
The master reads listener and worker-management settings once. Replacement workers read current source files with the launch-time CLI overrides reapplied. Do not edit configuration while a service is running unless you understand that existing and replacement workers can differ. Apply coordinated changes by stopping the entire service and starting it again.
server.root_path is optional complete external-origin metadata, such as
https://api.example.com:8443. It is never an ASGI path prefix. Business paths
come from Router registration; the ASGI root path remains empty.
The controller owns a separate upstream logger for service and worker lifecycle observations. Its writer, executor, and event loop are drained and closed around Gunicorn's native worker-fork boundary, then reopened only in the parent. Windows uses spawned workers with independent log scopes. Shutdown flushes the controller scope after the native manager has joined its workers. See logging for filename patterns and the observation consistency contract.
Development hot reload¶
serve -o config service.lclcfg -o hot_reload watches the explicit
server.reload_dirs roots with watchfiles on both platforms.
One master-owned iterator groups editor changes and is advanced from the native
process manager loop. Native automatic reload watchers remain disabled, so imported
modules outside the selected roots cannot trigger a reload.
The controller records hot-reload watcher ready after the first native watch
poll initializes filesystem monitoring. API startup can finish before this poll;
automation that immediately edits source should wait for that controller event.
Each change batch gracefully retires the current worker. Uvicorn or Gunicorn owns
its replacement; the master PID, listener, service-start identity, and control token
remain unchanged. Business teardown, logger flushing, observation removal, and lease
release run before replacement. Requests may be unavailable briefly during reload.
Changes during replacement are coalesced into a pending reload, and whole-service
shutdown takes priority. On Windows an identity-specific reload.json marker drives
the existing worker exit bridge without console signals. Linux signals the verified
worker with SIGTERM. Both platforms record an accepted whole-service shutdown before
signaling, preventing a pending reload from retiring another worker. Master cleanup
removes the markers and closes the watcher.
Import or lifespan startup failures stop the complete service with the original diagnostic and a nonzero exit. Watcher failures also stop the service; they are not silently ignored. Fix the problem and start the service again. Configuration edits do not trigger reload, but replacement workers still read current configuration. Use a full restart for configuration changes. Browser refresh is not provided.
Local observations¶
runtime.state_dir/runtime.json records the actual master PID, its operating
system creation time, a fresh complete-start identifier, launch-time listener,
worker count, observation interval, and graceful deadline. runtime.pid_file
contains that master's PID. Windows virtual-environment launchers can introduce
an additional parent process; their PID is not the Uvicorn master PID.
Each worker atomically replaces runtime.worker_state_dir/<pid>.json. Its
observation includes PID, process creation time, complete-start identifier,
Snowflake worker number, actual lclang log paths, and observation time. The
local commands verify both PID and creation time and require the current start's
identifier. A reused PID or a previous start's state cannot become a live worker.
Runtime JSON, PID files, control tokens, and worker-ID leases coordinate the current process lifecycle; they are not persistent business data. Leases assign distinct IDs in the configured range to live concurrent workers and reclaim an exited worker's ID only after validating its process identity. This coordination does not extend upstream Snowflake guarantees across crashes or rapid ID reuse.
lcl-fastapi status -o config service.lclcfg
lcl-fastapi logs -o config service.lclcfg
Status reports RUNNING, STOPPED, or STALE, along with the verified service
and worker observations. Missing or malformed JSON is treated as absent; an
existing process identity that no longer matches is stale. Access failures are
reported as errors rather than disguised as a stopped service.
Logs JSON contains paths, observed_at, and stale. Paths are deduplicated
absolute names reported by live workers, never guessed from file modification
times. observed_at is the earliest participating observation in Unix seconds,
or null when none exists. stale identifies live-worker observations older
than the configured sampling interval. This age flag does not mean that the
worker has stopped. A healthy worker can temporarily report stale: true
during sampling, state publication, or CLI identity checks. Dead workers never
reenter the result because their observation is stale.
Log paths are eventually consistent. Under normal operation a rotation appears
within health.sample_interval_seconds plus the time needed to collect metrics
and write one state observation. The refresh loop sleeps for the interval before collecting and
publishing its next observation; freshness is not guaranteed for every CLI
invocation. Upstream metrics can report the current or last path; this is not proof that a file is
still writable after a sink failure. Disabling the health HTTP endpoint does
not disable observation refresh.
Shutdown and cleanup¶
lcl-fastapi stop -o config service.lclcfg
The master creates a random control.token for every complete start. On Linux
the token file uses mode 0600; on Windows its inherited ACL is removed and the
current service-account SID receives access using the native whoami and
icacls tools. The secret is written after permissions are restricted. Do not
publish this file or its containing directory.
The CLI reads current state, verifies the master identity, and submits a local
POST /_lcl/shutdown with X-LCL-Control-Token. Missing or incorrect tokens
receive HTTP 403. HTTP 202 accepts shutdown; the response is sent before the
native runtime is asked to stop. The CLI then waits for the verified master to
exit and reports failure when its configured graceful deadline expires. It does
not send emergency signals to an unknown PID.
Status, logs, and stop locate state through the supplied configuration and use launch-time listener information from that state. A later port edit does not redirect stop to another service. If the state directory changes, use a configuration that still locates the original state to manage the old service.
Business lifespan exits before framework logging is flushed and closed. Background retirement finishes before that business teardown. A noncooperative background thread causes controller-logged termination of the entire API process after the graceful deadline; forced termination cannot guarantee cleanup. Retirement deadlines are armed by the controller before native stop/reload and do not rely on the API journal remaining writable. Actual submitted API Tasks, including asynchronous cancellation cleanup, settle before background attempts finish and before business resources close. Normal worker cleanup removes its observation and lease. Kernel file locks release after a process crash; the next allocation can reclaim a lease only after its old process identity is no longer live. Master exit removes abandoned worker state and leases, its PID/state files, and its control token. Lock files remain as stable synchronization locations.
Only the process that acquired a resource releases its ownership. On Linux, a gracefully exiting forked worker preserves the master's state, PID file, control token, and service lock. It closes inherited lock descriptors without unlocking the master's shared file description. Its own worker observation and lease are still cleaned before Gunicorn creates a replacement.
Users may override predefined routes with the same HTTP method and exact path.
This is not recommended unless you understand the consequences. In particular,
overriding POST /_lcl/shutdown can prevent the stop command from working.
Worker number scope¶
The lease pool uses snowflake.worker_id_base and snowflake.worker_id_count.
Concurrent live leases in this local service receive different numbers. This
does not enlarge lclang's historical ID guarantees across rapid reuse, crashes,
or clock rollback. The framework delegates Snowflake generation to
lclang>=1.1.0 and does not change its algorithm.
For multiple machines, assign nonoverlapping ranges through LCL's environment
configuration facilities. The runtime does not read environment variables as an
alternative source of public service settings. Its LCL_FASTAPI_CONFIG and
LCL_FASTAPI_STATE variables are internal parent-to-worker launch coordinates.
Concurrent state reads on Windows¶
Workers publish observations by atomic replacement. State-file replacement retries Windows PermissionError up to three attempts, with 10 ms between attempts, when a concurrent reader temporarily prevents replacing the destination. Exhausted retries propagate the error, preserve the previous state, and remove the unused temporary file; other platforms fail immediately. Windows can briefly deny a read while a replaced file is pending deletion. The shared state reader retries PermissionError up to three attempts, with 10 ms between attempts, on Windows. Persistent denial is still raised; Linux permission failures are raised immediately. This bounded retry preserves complete JSON observations without treating a transient sharing conflict as a controller failure or weakening directory access.