feat: workerize external generation
This commit is contained in:
@@ -21,6 +21,11 @@ pub struct AppConfig {
|
||||
pub bind_port: u16,
|
||||
pub listen_backlog: i32,
|
||||
pub worker_threads: Option<usize>,
|
||||
pub process_role: ProcessRole,
|
||||
pub external_generation_worker_id: String,
|
||||
pub external_generation_worker_concurrency: usize,
|
||||
pub external_generation_worker_poll_interval: Duration,
|
||||
pub external_generation_worker_lease: Duration,
|
||||
pub max_concurrent_requests: Option<usize>,
|
||||
pub gallery_max_concurrent_requests: Option<usize>,
|
||||
pub detail_max_concurrent_requests: Option<usize>,
|
||||
@@ -159,6 +164,31 @@ pub struct AppConfig {
|
||||
pub slow_request_threshold_ms: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ProcessRole {
|
||||
Api,
|
||||
ExternalGenerationWorker,
|
||||
All,
|
||||
}
|
||||
|
||||
impl ProcessRole {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Api => "api",
|
||||
Self::ExternalGenerationWorker => "external-generation-worker",
|
||||
Self::All => "all",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn runs_http(self) -> bool {
|
||||
matches!(self, Self::Api | Self::All)
|
||||
}
|
||||
|
||||
pub fn runs_external_generation_worker(self) -> bool {
|
||||
matches!(self, Self::ExternalGenerationWorker | Self::All)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AppConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@@ -166,6 +196,11 @@ impl Default for AppConfig {
|
||||
bind_port: 3000,
|
||||
listen_backlog: 1024,
|
||||
worker_threads: None,
|
||||
process_role: ProcessRole::Api,
|
||||
external_generation_worker_id: default_external_generation_worker_id(),
|
||||
external_generation_worker_concurrency: 2,
|
||||
external_generation_worker_poll_interval: Duration::from_millis(2_000),
|
||||
external_generation_worker_lease: Duration::from_secs(3_600),
|
||||
max_concurrent_requests: None,
|
||||
gallery_max_concurrent_requests: None,
|
||||
detail_max_concurrent_requests: None,
|
||||
@@ -347,6 +382,30 @@ impl AppConfig {
|
||||
if let Some(worker_threads) = read_first_usize_env(&["GENARRATIVE_API_WORKER_THREADS"]) {
|
||||
config.worker_threads = Some(worker_threads);
|
||||
}
|
||||
if let Some(process_role) = read_first_process_role_env(&["GENARRATIVE_PROCESS_ROLE"]) {
|
||||
config.process_role = process_role;
|
||||
}
|
||||
if let Some(worker_id) =
|
||||
read_first_non_empty_env(&["GENARRATIVE_EXTERNAL_GENERATION_WORKER_ID"])
|
||||
{
|
||||
config.external_generation_worker_id = worker_id;
|
||||
}
|
||||
if let Some(concurrency) =
|
||||
read_first_usize_env(&["GENARRATIVE_EXTERNAL_GENERATION_WORKER_CONCURRENCY"])
|
||||
{
|
||||
config.external_generation_worker_concurrency = concurrency.max(1);
|
||||
}
|
||||
if let Some(poll_interval_ms) = read_first_positive_u64_env(&[
|
||||
"GENARRATIVE_EXTERNAL_GENERATION_WORKER_POLL_INTERVAL_MS",
|
||||
]) {
|
||||
config.external_generation_worker_poll_interval =
|
||||
Duration::from_millis(poll_interval_ms);
|
||||
}
|
||||
if let Some(lease_seconds) = read_first_duration_seconds_env(&[
|
||||
"GENARRATIVE_EXTERNAL_GENERATION_WORKER_LEASE_SECONDS",
|
||||
]) {
|
||||
config.external_generation_worker_lease = Duration::from_secs(lease_seconds.max(1));
|
||||
}
|
||||
if let Some(max_concurrent_requests) =
|
||||
read_first_usize_env(&["GENARRATIVE_API_MAX_CONCURRENT_REQUESTS"])
|
||||
{
|
||||
@@ -979,6 +1038,14 @@ fn read_first_llm_provider_env(keys: &[&str]) -> Option<LlmProvider> {
|
||||
})
|
||||
}
|
||||
|
||||
fn read_first_process_role_env(keys: &[&str]) -> Option<ProcessRole> {
|
||||
keys.iter().find_map(|key| {
|
||||
env::var(key)
|
||||
.ok()
|
||||
.and_then(|value| parse_process_role(&value))
|
||||
})
|
||||
}
|
||||
|
||||
fn read_first_positive_u32_env(keys: &[&str]) -> Option<u32> {
|
||||
keys.iter().find_map(|key| {
|
||||
env::var(key)
|
||||
@@ -1026,6 +1093,36 @@ fn read_first_u8_env(keys: &[&str]) -> Option<u8> {
|
||||
.find_map(|key| env::var(key).ok().and_then(|value| parse_u8(&value)))
|
||||
}
|
||||
|
||||
fn default_external_generation_worker_id() -> String {
|
||||
let host = env::var("HOSTNAME")
|
||||
.or_else(|_| env::var("COMPUTERNAME"))
|
||||
.unwrap_or_else(|_| "local".to_string());
|
||||
format!("{}-{}", host.trim(), std::process::id())
|
||||
}
|
||||
|
||||
fn parse_process_role(value: &str) -> Option<ProcessRole> {
|
||||
match trim_quoted_env_value(value).to_ascii_lowercase().as_str() {
|
||||
"api" => Some(ProcessRole::Api),
|
||||
"external-generation-worker" | "external_generation_worker" | "worker" => {
|
||||
Some(ProcessRole::ExternalGenerationWorker)
|
||||
}
|
||||
"all" => Some(ProcessRole::All),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn trim_quoted_env_value(raw: &str) -> &str {
|
||||
let raw = raw.trim();
|
||||
raw.strip_prefix('"')
|
||||
.and_then(|value| value.strip_suffix('"'))
|
||||
.or_else(|| {
|
||||
raw.strip_prefix('\'')
|
||||
.and_then(|value| value.strip_suffix('\''))
|
||||
})
|
||||
.unwrap_or(raw)
|
||||
.trim()
|
||||
}
|
||||
|
||||
fn read_first_positive_u16_env(keys: &[&str]) -> Option<u16> {
|
||||
keys.iter().find_map(|key| {
|
||||
env::var(key)
|
||||
@@ -1146,7 +1243,8 @@ fn parse_positive_u16(raw: &str) -> Option<u16> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
AppConfig, DEFAULT_VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS, LlmProvider, parse_bool,
|
||||
AppConfig, DEFAULT_VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS, LlmProvider, ProcessRole,
|
||||
parse_bool, parse_process_role,
|
||||
};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
@@ -1188,6 +1286,32 @@ mod tests {
|
||||
assert_eq!(parse_bool("'off'"), Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_role_controls_http_and_external_generation_worker_roles() {
|
||||
assert_eq!(parse_process_role("api"), Some(ProcessRole::Api));
|
||||
assert_eq!(
|
||||
parse_process_role("\"external-generation-worker\""),
|
||||
Some(ProcessRole::ExternalGenerationWorker)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_process_role("'external_generation_worker'"),
|
||||
Some(ProcessRole::ExternalGenerationWorker)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_process_role("worker"),
|
||||
Some(ProcessRole::ExternalGenerationWorker)
|
||||
);
|
||||
assert_eq!(parse_process_role("all"), Some(ProcessRole::All));
|
||||
assert_eq!(parse_process_role("unknown"), None);
|
||||
|
||||
assert!(ProcessRole::Api.runs_http());
|
||||
assert!(!ProcessRole::Api.runs_external_generation_worker());
|
||||
assert!(!ProcessRole::ExternalGenerationWorker.runs_http());
|
||||
assert!(ProcessRole::ExternalGenerationWorker.runs_external_generation_worker());
|
||||
assert!(ProcessRole::All.runs_http());
|
||||
assert!(ProcessRole::All.runs_external_generation_worker());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_env_reads_sms_enabled_when_shell_value_keeps_quotes() {
|
||||
let _guard = ENV_LOCK
|
||||
|
||||
Reference in New Issue
Block a user