mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 17:28:24 -04:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61f0b453dd | ||
|
|
48f26bf621 | ||
|
|
d2fb989e78 |
+54
@@ -1,12 +1,22 @@
|
||||
use log::warn;
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use artcraft_router::api::router_provider::RouterProvider;
|
||||
use enums::by_table::debug_logs::debug_log_level::DebugLogLevel;
|
||||
use enums::by_table::debug_logs::debug_log_type::DebugLogType;
|
||||
use mysql_queries::queries::debug_logs::insert_debug_log::{insert_debug_log, InsertDebugLogArgs};
|
||||
use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
/// Request-scoped identity for debug log inserts, so deep pipeline code can
|
||||
/// log without threading four separate arguments.
|
||||
pub struct GenerationDebugLogContext<'a> {
|
||||
pub event_token: &'a DebugLogEventToken,
|
||||
pub user_token: &'a UserToken,
|
||||
pub ip_address: &'a str,
|
||||
pub request_url: &'a str,
|
||||
}
|
||||
|
||||
/// Best-effort info-level debug log of an inbound generation HTTP request.
|
||||
/// Never fails the request — insert errors are logged and swallowed.
|
||||
pub async fn insert_generation_request_debug_log<'e, 'c: 'e, E>(
|
||||
@@ -60,3 +70,47 @@ pub async fn insert_generation_failure_debug_log<'e, 'c: 'e, E>(
|
||||
warn!("Failed to insert generation failure debug log: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
/// The debug log type for an outbound request to the given provider, or
|
||||
/// `None` for providers we don't capture.
|
||||
pub fn provider_request_debug_log_type(provider: RouterProvider) -> Option<DebugLogType> {
|
||||
match provider {
|
||||
RouterProvider::Fal => Some(DebugLogType::FalRequest),
|
||||
RouterProvider::GrokApi => Some(DebugLogType::GrokApiRequest),
|
||||
RouterProvider::Seedance2Pro => Some(DebugLogType::KinoviRequest),
|
||||
// First-party: the HttpRequest log already captures the user request.
|
||||
RouterProvider::Artcraft => None,
|
||||
// No DebugLogType variant yet; add one if GmiCloud routing is revived.
|
||||
RouterProvider::GmiCloud => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Best-effort info-level debug log of an outbound provider generation
|
||||
/// request. MUST be inserted during the pre-request DB phase — on the same
|
||||
/// open connection as the session/billing/HTTP-request writes, BEFORE that
|
||||
/// connection is released and the external provider call is made — so the
|
||||
/// outbound payload is captured even when the upload/enqueue fails, without
|
||||
/// ever touching the pool.
|
||||
/// Never fails the request — insert errors are logged and swallowed.
|
||||
pub async fn insert_provider_request_debug_log<'e, 'c: 'e, E>(
|
||||
context: &'e GenerationDebugLogContext<'e>,
|
||||
debug_log_type: DebugLogType,
|
||||
outbound_request_debug: &'e str,
|
||||
mysql_executor: E,
|
||||
) where
|
||||
E: 'c + Executor<'c, Database = MySql>,
|
||||
{
|
||||
if let Err(err) = insert_debug_log(InsertDebugLogArgs {
|
||||
apriori_debug_log_event_token: Some(context.event_token),
|
||||
maybe_creator_user_token: Some(context.user_token),
|
||||
debug_log_type,
|
||||
maybe_log_level: Some(DebugLogLevel::Info),
|
||||
maybe_ip_address: Some(context.ip_address),
|
||||
maybe_url: Some(context.request_url),
|
||||
message: outbound_request_debug,
|
||||
mysql_executor,
|
||||
phantom: Default::default(),
|
||||
}).await {
|
||||
warn!("Failed to insert {} debug log: {:?}", debug_log_type, err);
|
||||
}
|
||||
}
|
||||
|
||||
+18
-25
@@ -8,6 +8,7 @@ use sqlx::Acquire;
|
||||
use artcraft_api_defs::omni_api::generate_requests::omni_api_image_generate_request::OmniApiImageGenerateRequest;
|
||||
use artcraft_api_defs::omni_gen::generate_response::omni_gen_image_generate_response::OmniGenImageGenerateResponse;
|
||||
use artcraft_router::generate::generate_image::generate_image_response::GenerateImageResponse;
|
||||
use enums::by_table::debug_logs::debug_log_level::DebugLogLevel;
|
||||
use enums::by_table::debug_logs::debug_log_type::DebugLogType;
|
||||
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
|
||||
use enums::by_table::prompts::prompt_type::PromptType;
|
||||
@@ -16,7 +17,6 @@ use enums::common::generation::common_model_type::CommonModelType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::platform_type::PlatformType;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use enums::by_table::debug_logs::debug_log_level::DebugLogLevel;
|
||||
use mysql_queries::queries::debug_logs::insert_debug_log::{insert_debug_log, InsertDebugLogArgs};
|
||||
use mysql_queries::queries::generic_inference::api_providers::seedance2pro::insert_generic_inference_job_for_seedance2pro_queue_with_apriori_job_token::KinoviVersion;
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
@@ -28,6 +28,7 @@ use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoints::generate::common::generation_debug_logs::GenerationDebugLogContext;
|
||||
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
|
||||
use crate::http_server::endpoints::omni_api::generate::image::check_request::check_request;
|
||||
use crate::http_server::endpoints::omni_api::generate::image::hydrate_to_router_request::hydrate_to_router_request;
|
||||
@@ -36,8 +37,8 @@ use crate::http_server::endpoints::omni_api::generate::image::insert_db_job::ins
|
||||
use crate::http_server::endpoints::omni_api::generate::image::insert_db_job::insert_seedance2pro_jobs::{insert_seedance2pro_jobs, InsertSeedance2proJobsArgs};
|
||||
use crate::http_server::endpoints::omni_api::generate::image::insert_db_job::shared_job_args::SharedJobArgs;
|
||||
use crate::http_server::endpoints::omni_api::generate::image::pipeline_v2::run_pipeline_v2::{run_pipeline_v2, RunPipelineV2Args};
|
||||
use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags;
|
||||
use crate::http_server::user_lookup::api_keys::require_api_key_user::require_api_key_user;
|
||||
use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags;
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::util::lookup::lookup_media_files_as_cdn_url_list_and_map::lookup_media_files_as_cdn_url_list_and_map;
|
||||
@@ -152,16 +153,25 @@ pub async fn omni_api_image_generate_handler(
|
||||
|
||||
// ==================== PIPELINE ==================== //
|
||||
|
||||
// NB: Release the pooled DB connection before the (slow, external) generation call so we don't
|
||||
// hold a pool slot idle while waiting on the provider — that's what starves the pool and causes
|
||||
// PoolTimedOut on unrelated endpoints. We re-acquire below to write the result.
|
||||
drop(mysql_connection);
|
||||
// NB: The pipeline takes over the connection for its remaining pre-request DB writes (billing,
|
||||
// outbound provider request debug log) and releases it before the (slow, external) generation
|
||||
// call — holding a pool slot across that call is what starves the pool and causes PoolTimedOut
|
||||
// on unrelated endpoints. We re-acquire below to write the result.
|
||||
|
||||
let debug_log_context = GenerationDebugLogContext {
|
||||
event_token: &debug_log_event_token,
|
||||
user_token,
|
||||
ip_address: &ip_address,
|
||||
request_url: &request_url,
|
||||
};
|
||||
|
||||
let pipeline_result = run_pipeline_v2(RunPipelineV2Args {
|
||||
router_builder: &router_builder,
|
||||
server_state: &server_state,
|
||||
user_token,
|
||||
resolved_media: &resolved_media,
|
||||
debug_log_context: &debug_log_context,
|
||||
mysql_connection,
|
||||
}).await;
|
||||
|
||||
// ==================== DEBUG LOG: PIPELINE ERROR ==================== //
|
||||
@@ -191,25 +201,8 @@ pub async fn omni_api_image_generate_handler(
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
|
||||
|
||||
// ==================== DEBUG LOG: FAL REQUEST ==================== //
|
||||
|
||||
if let GenerateImageResponse::Fal(ref fal_payload) = pipeline_result.response {
|
||||
if let Some(ref outbound_request) = fal_payload.maybe_outbound_request {
|
||||
if let Err(err) = insert_debug_log(InsertDebugLogArgs {
|
||||
apriori_debug_log_event_token: Some(&debug_log_event_token),
|
||||
maybe_creator_user_token: Some(user_token),
|
||||
debug_log_type: DebugLogType::FalRequest,
|
||||
maybe_log_level: Some(DebugLogLevel::Info),
|
||||
maybe_ip_address: Some(&ip_address),
|
||||
maybe_url: Some(&request_url),
|
||||
message: &format!("{:#?}", outbound_request),
|
||||
mysql_executor: &mut *mysql_connection,
|
||||
phantom: Default::default(),
|
||||
}).await {
|
||||
warn!("Failed to insert Fal request debug log: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
// NB: Outbound provider requests (Fal/Grok/Kinovi) are debug-logged inside
|
||||
// the pipeline BEFORE the send, so the payload is captured even on failure.
|
||||
|
||||
// ==================== WRITE RESULT ==================== //
|
||||
|
||||
|
||||
+30
-8
@@ -17,6 +17,9 @@ use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoints::generate::common::generation_debug_logs::{
|
||||
insert_provider_request_debug_log, provider_request_debug_log_type, GenerationDebugLogContext,
|
||||
};
|
||||
use crate::http_server::endpoints::omni_api::generate::image::pipeline_result::ImagePipelineResult;
|
||||
use crate::http_server::endpoints::omni_api::shared_utils::map_seedance2pro_router_error::map_router_error_to_web_error;
|
||||
use crate::state::server_state::ServerState;
|
||||
@@ -27,6 +30,11 @@ pub struct RunPipelineV2Args<'a> {
|
||||
pub server_state: &'a ServerState,
|
||||
pub user_token: &'a UserToken,
|
||||
pub resolved_media: &'a MediaFilesAsCdnUrlListAndMap,
|
||||
pub debug_log_context: &'a GenerationDebugLogContext<'a>,
|
||||
/// The handler's open connection. The pipeline uses it for its remaining
|
||||
/// pre-request DB writes (billing, outbound-request debug log) and releases
|
||||
/// it BEFORE the external provider call.
|
||||
pub mysql_connection: sqlx::pool::PoolConnection<sqlx::MySql>,
|
||||
}
|
||||
|
||||
// NB: This pipeline does an external generation call (`finalize_and_generate`) that can take many
|
||||
@@ -41,6 +49,8 @@ pub async fn run_pipeline_v2(
|
||||
server_state,
|
||||
user_token,
|
||||
resolved_media,
|
||||
debug_log_context,
|
||||
mut mysql_connection,
|
||||
} = args;
|
||||
|
||||
let hydrated_builder = apply_hydrated_media_inputs(
|
||||
@@ -57,24 +67,36 @@ pub async fn run_pipeline_v2(
|
||||
let apriori_job_token = InferenceJobToken::generate();
|
||||
|
||||
let maybe_wallet_ledger_entry_token = if cost > 0 {
|
||||
// NB: Short-lived connection — released before the external call below.
|
||||
let mut billing_connection = server_state.mysql_pool.acquire().await
|
||||
.map_err(|err| {
|
||||
warn!("Failed to acquire MySQL connection for image billing: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
// Billed on the handler's connection (same pre-request DB phase).
|
||||
let deduction = attempt_wallet_deduction_else_common_web_error(
|
||||
user_token,
|
||||
Some(apriori_job_token.as_str()),
|
||||
cost,
|
||||
&mut billing_connection,
|
||||
&mut mysql_connection,
|
||||
).await?;
|
||||
Some(deduction.ledger_entry_token)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// NB: No pooled DB connection is held across this external generation call.
|
||||
// Debug-log the outbound provider request BEFORE the send — still on the
|
||||
// handler's connection — so the payload is captured even when the
|
||||
// upload/enqueue fails.
|
||||
if let Some(debug_log_type) = provider_request_debug_log_type(draft_or_request.get_provider()) {
|
||||
insert_provider_request_debug_log(
|
||||
debug_log_context,
|
||||
debug_log_type,
|
||||
&format!("{:#?}", draft_or_request),
|
||||
&mut *mysql_connection,
|
||||
).await;
|
||||
}
|
||||
|
||||
// NB: Done with pre-request DB writes. Release the pooled connection before
|
||||
// the (slow, external) provider call — holding it across that call is what
|
||||
// starves the pool and causes PoolTimedOut. No pooled DB connection is held
|
||||
// across the external generation call below.
|
||||
drop(mysql_connection);
|
||||
|
||||
let response = finalize_and_generate(draft_or_request, server_state, resolved_media).await?;
|
||||
|
||||
Ok(ImagePipelineResult {
|
||||
|
||||
+16
-43
@@ -32,6 +32,7 @@ use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoints::generate::common::generation_debug_logs::GenerationDebugLogContext;
|
||||
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
|
||||
use crate::http_server::endpoints::omni_api::generate::video::check_request::check_request;
|
||||
use crate::http_server::endpoints::omni_api::generate::video::ingest_url_inputs::ingest_url_inputs;
|
||||
@@ -217,10 +218,17 @@ pub async fn omni_api_video_generate_handler(
|
||||
warn!("Failed to insert HTTP request debug log: {:?}", err);
|
||||
}
|
||||
|
||||
// NB: Release the pooled DB connection before the (slow, external) generation call so we don't
|
||||
// hold a pool slot idle while waiting on the provider — that's what starves the pool and causes
|
||||
// PoolTimedOut on unrelated endpoints. We re-acquire below to write the result.
|
||||
drop(mysql_connection);
|
||||
// NB: The pipeline takes over the connection for its remaining pre-request DB writes (billing,
|
||||
// outbound provider request debug log) and releases it before the (slow, external) generation
|
||||
// call — holding a pool slot across that call is what starves the pool and causes PoolTimedOut
|
||||
// on unrelated endpoints. We re-acquire below to write the result.
|
||||
|
||||
let debug_log_context = GenerationDebugLogContext {
|
||||
event_token: &debug_log_event_token,
|
||||
user_token,
|
||||
ip_address: &ip_address,
|
||||
request_url: &request_url,
|
||||
};
|
||||
|
||||
let pipeline_result = run_pipeline_v2(RunPipelineV2Args {
|
||||
router_builder: &router_builder,
|
||||
@@ -229,6 +237,8 @@ pub async fn omni_api_video_generate_handler(
|
||||
media_file_to_url_map: &media_file_to_url_map,
|
||||
kinovi_character_id_map: &kinovi_character_id_map,
|
||||
kinovi_account,
|
||||
debug_log_context: &debug_log_context,
|
||||
mysql_connection,
|
||||
}).await;
|
||||
|
||||
// ==================== DEBUG LOG: PIPELINE ERROR ==================== //
|
||||
@@ -258,45 +268,8 @@ pub async fn omni_api_video_generate_handler(
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
|
||||
|
||||
// ==================== DEBUG LOG: FAL REQUEST ==================== //
|
||||
|
||||
if let GenerateVideoResponse::Fal(ref fal_payload) = pipeline_result.response {
|
||||
if let Some(ref outbound_request) = fal_payload.maybe_outbound_request {
|
||||
if let Err(err) = insert_debug_log(InsertDebugLogArgs {
|
||||
apriori_debug_log_event_token: Some(&debug_log_event_token),
|
||||
maybe_creator_user_token: Some(user_token),
|
||||
debug_log_type: DebugLogType::FalRequest,
|
||||
maybe_log_level: Some(DebugLogLevel::Info),
|
||||
maybe_ip_address: Some(&ip_address),
|
||||
maybe_url: Some(&request_url),
|
||||
message: &format!("{:#?}", outbound_request),
|
||||
mysql_executor: &mut *mysql_connection,
|
||||
phantom: Default::default(),
|
||||
}).await {
|
||||
warn!("Failed to insert Fal request debug log: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== DEBUG LOG: GROK API REQUEST ==================== //
|
||||
|
||||
if let GenerateVideoResponse::Grok(ref grok_payload) = pipeline_result.response {
|
||||
if let Some(ref outbound_request) = grok_payload.maybe_outbound_request {
|
||||
if let Err(err) = insert_debug_log(InsertDebugLogArgs {
|
||||
apriori_debug_log_event_token: Some(&debug_log_event_token),
|
||||
maybe_creator_user_token: Some(user_token),
|
||||
debug_log_type: DebugLogType::GrokApiRequest,
|
||||
maybe_log_level: Some(DebugLogLevel::Info),
|
||||
maybe_ip_address: Some(&ip_address),
|
||||
maybe_url: Some(&request_url),
|
||||
message: &format!("{:#?}", outbound_request),
|
||||
mysql_executor: &mut *mysql_connection,
|
||||
phantom: Default::default(),
|
||||
}).await {
|
||||
warn!("Failed to insert Grok API request debug log: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
// NB: Outbound provider requests (Fal/Grok/Kinovi) are debug-logged inside
|
||||
// the pipeline BEFORE the send, so the payload is captured even on failure.
|
||||
|
||||
// ==================== WRITE RESULT ==================== //
|
||||
|
||||
|
||||
+29
-9
@@ -15,6 +15,9 @@ use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoint_helpers::refund_wallet_after_api_failure::refund_wallet_after_api_failure;
|
||||
use crate::http_server::endpoints::generate::common::generation_debug_logs::{
|
||||
insert_provider_request_debug_log, provider_request_debug_log_type, GenerationDebugLogContext,
|
||||
};
|
||||
use crate::http_server::endpoints::omni_api::generate::video::helpers::bill_wallet::bill_wallet;
|
||||
use crate::http_server::endpoints::omni_api::generate::video::helpers::build_router_client::build_router_client;
|
||||
use crate::http_server::endpoints::omni_api::generate::video::helpers::pipeline_result::PipelineResult;
|
||||
@@ -31,6 +34,11 @@ pub struct RunPipelineV2Args<'a> {
|
||||
pub media_file_to_url_map: &'a Option<HashMap<MediaFileToken, String>>,
|
||||
pub kinovi_character_id_map: &'a Option<HashMap<CharacterToken, String>>,
|
||||
pub kinovi_account: KinoviAccount,
|
||||
pub debug_log_context: &'a GenerationDebugLogContext<'a>,
|
||||
/// The handler's open connection. The pipeline uses it for its remaining
|
||||
/// pre-request DB writes (billing, outbound-request debug log) and releases
|
||||
/// it BEFORE the external provider call.
|
||||
pub mysql_connection: sqlx::pool::PoolConnection<sqlx::MySql>,
|
||||
}
|
||||
|
||||
// NB: This pipeline does an external generation call (`upload_and_generate`) that can take many
|
||||
@@ -45,6 +53,8 @@ pub async fn run_pipeline_v2(args: RunPipelineV2Args<'_>) -> Result<PipelineResu
|
||||
media_file_to_url_map,
|
||||
kinovi_character_id_map,
|
||||
kinovi_account,
|
||||
debug_log_context,
|
||||
mut mysql_connection,
|
||||
} = args;
|
||||
|
||||
let mut router_builder = router_builder.clone();
|
||||
@@ -138,15 +148,25 @@ pub async fn run_pipeline_v2(args: RunPipelineV2Args<'_>) -> Result<PipelineResu
|
||||
|
||||
info!("v2 estimated cost: {} credits (estimates: {:?})", cost, cost_estimates);
|
||||
|
||||
// 3. Bill wallet (short-lived connection — released before the external call below).
|
||||
let billing = {
|
||||
let mut billing_connection = server_state.mysql_pool.acquire().await
|
||||
.map_err(|err| {
|
||||
error!("Failed to acquire MySQL connection for billing: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
bill_wallet(user_token, cost, &mut billing_connection).await?
|
||||
};
|
||||
// 3. Bill wallet on the handler's connection (same pre-request DB phase).
|
||||
let billing = bill_wallet(user_token, cost, &mut mysql_connection).await?;
|
||||
|
||||
// Debug-log the outbound provider request BEFORE the send — still on the
|
||||
// handler's connection — so the payload is captured even when the
|
||||
// upload/enqueue fails.
|
||||
if let Some(debug_log_type) = provider_request_debug_log_type(provider) {
|
||||
insert_provider_request_debug_log(
|
||||
debug_log_context,
|
||||
debug_log_type,
|
||||
&format!("{:#?}", draft_or_request),
|
||||
&mut *mysql_connection,
|
||||
).await;
|
||||
}
|
||||
|
||||
// NB: Done with pre-request DB writes. Release the pooled connection before
|
||||
// the (slow, external) provider call — holding it across that call is what
|
||||
// starves the pool and causes PoolTimedOut. Post-send writes re-acquire.
|
||||
drop(mysql_connection);
|
||||
|
||||
// 4. Upload media (if draft) and generate video.
|
||||
// The entire block is wrapped so Kinovi failures trigger a refund.
|
||||
|
||||
+16
-23
@@ -27,6 +27,7 @@ use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoints::generate::common::generation_debug_logs::GenerationDebugLogContext;
|
||||
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
|
||||
use crate::http_server::endpoints::omni_gen::generate::image::hydrate_to_router_request::hydrate_to_router_request;
|
||||
use crate::http_server::endpoints::omni_gen::generate::image::insert_db_job::insert_fal_job::{insert_fal_job, InsertFalJobArgs};
|
||||
@@ -150,16 +151,25 @@ pub async fn omni_gen_image_generate_handler(
|
||||
|
||||
// ==================== PIPELINE ==================== //
|
||||
|
||||
// NB: Release the pooled DB connection before the (slow, external) generation call so we don't
|
||||
// hold a pool slot idle while waiting on the provider — that's what starves the pool and causes
|
||||
// PoolTimedOut on unrelated endpoints. We re-acquire below to write the result.
|
||||
drop(mysql_connection);
|
||||
// NB: The pipeline takes over the connection for its remaining pre-request DB writes (billing,
|
||||
// outbound provider request debug log) and releases it before the (slow, external) generation
|
||||
// call — holding a pool slot across that call is what starves the pool and causes PoolTimedOut
|
||||
// on unrelated endpoints. We re-acquire below to write the result.
|
||||
|
||||
let debug_log_context = GenerationDebugLogContext {
|
||||
event_token: &debug_log_event_token,
|
||||
user_token,
|
||||
ip_address: &ip_address,
|
||||
request_url: &request_url,
|
||||
};
|
||||
|
||||
let pipeline_result = run_pipeline_v2(RunPipelineV2Args {
|
||||
router_builder: &router_builder,
|
||||
server_state: &server_state,
|
||||
user_token,
|
||||
resolved_media: &resolved_media,
|
||||
debug_log_context: &debug_log_context,
|
||||
mysql_connection,
|
||||
}).await;
|
||||
|
||||
// ==================== DEBUG LOG: PIPELINE ERROR ==================== //
|
||||
@@ -189,25 +199,8 @@ pub async fn omni_gen_image_generate_handler(
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
|
||||
|
||||
// ==================== DEBUG LOG: FAL REQUEST ==================== //
|
||||
|
||||
if let GenerateImageResponse::Fal(ref fal_payload) = pipeline_result.response {
|
||||
if let Some(ref outbound_request) = fal_payload.maybe_outbound_request {
|
||||
if let Err(err) = insert_debug_log(InsertDebugLogArgs {
|
||||
apriori_debug_log_event_token: Some(&debug_log_event_token),
|
||||
maybe_creator_user_token: Some(user_token),
|
||||
debug_log_type: DebugLogType::FalRequest,
|
||||
maybe_log_level: Some(DebugLogLevel::Info),
|
||||
maybe_ip_address: Some(&ip_address),
|
||||
maybe_url: Some(&request_url),
|
||||
message: &format!("{:#?}", outbound_request),
|
||||
mysql_executor: &mut *mysql_connection,
|
||||
phantom: Default::default(),
|
||||
}).await {
|
||||
warn!("Failed to insert Fal request debug log: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
// NB: Outbound provider requests (Fal/Grok/Kinovi) are debug-logged inside
|
||||
// the pipeline BEFORE the send, so the payload is captured even on failure.
|
||||
|
||||
// ==================== WRITE RESULT ==================== //
|
||||
|
||||
|
||||
+30
-8
@@ -17,6 +17,9 @@ use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoints::generate::common::generation_debug_logs::{
|
||||
insert_provider_request_debug_log, provider_request_debug_log_type, GenerationDebugLogContext,
|
||||
};
|
||||
use crate::http_server::endpoints::omni_gen::generate::image::pipeline_result::ImagePipelineResult;
|
||||
use crate::http_server::endpoints::omni_gen::shared_utils::map_seedance2pro_router_error::map_router_error_to_web_error;
|
||||
use crate::state::server_state::ServerState;
|
||||
@@ -27,6 +30,11 @@ pub struct RunPipelineV2Args<'a> {
|
||||
pub server_state: &'a ServerState,
|
||||
pub user_token: &'a UserToken,
|
||||
pub resolved_media: &'a MediaFilesAsCdnUrlListAndMap,
|
||||
pub debug_log_context: &'a GenerationDebugLogContext<'a>,
|
||||
/// The handler's open connection. The pipeline uses it for its remaining
|
||||
/// pre-request DB writes (billing, outbound-request debug log) and releases
|
||||
/// it BEFORE the external provider call.
|
||||
pub mysql_connection: sqlx::pool::PoolConnection<sqlx::MySql>,
|
||||
}
|
||||
|
||||
// NB: This pipeline does an external generation call (`finalize_and_generate`) that can take many
|
||||
@@ -41,6 +49,8 @@ pub async fn run_pipeline_v2(
|
||||
server_state,
|
||||
user_token,
|
||||
resolved_media,
|
||||
debug_log_context,
|
||||
mut mysql_connection,
|
||||
} = args;
|
||||
|
||||
let hydrated_builder = apply_hydrated_media_inputs(
|
||||
@@ -57,24 +67,36 @@ pub async fn run_pipeline_v2(
|
||||
let apriori_job_token = InferenceJobToken::generate();
|
||||
|
||||
let maybe_wallet_ledger_entry_token = if cost > 0 {
|
||||
// NB: Short-lived connection — released before the external call below.
|
||||
let mut billing_connection = server_state.mysql_pool.acquire().await
|
||||
.map_err(|err| {
|
||||
warn!("Failed to acquire MySQL connection for image billing: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
// Billed on the handler's connection (same pre-request DB phase).
|
||||
let deduction = attempt_wallet_deduction_else_common_web_error(
|
||||
user_token,
|
||||
Some(apriori_job_token.as_str()),
|
||||
cost,
|
||||
&mut billing_connection,
|
||||
&mut mysql_connection,
|
||||
).await?;
|
||||
Some(deduction.ledger_entry_token)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// NB: No pooled DB connection is held across this external generation call.
|
||||
// Debug-log the outbound provider request BEFORE the send — still on the
|
||||
// handler's connection — so the payload is captured even when the
|
||||
// upload/enqueue fails.
|
||||
if let Some(debug_log_type) = provider_request_debug_log_type(draft_or_request.get_provider()) {
|
||||
insert_provider_request_debug_log(
|
||||
debug_log_context,
|
||||
debug_log_type,
|
||||
&format!("{:#?}", draft_or_request),
|
||||
&mut *mysql_connection,
|
||||
).await;
|
||||
}
|
||||
|
||||
// NB: Done with pre-request DB writes. Release the pooled connection before
|
||||
// the (slow, external) provider call — holding it across that call is what
|
||||
// starves the pool and causes PoolTimedOut. No pooled DB connection is held
|
||||
// across the external generation call below.
|
||||
drop(mysql_connection);
|
||||
|
||||
let response = finalize_and_generate(draft_or_request, server_state, resolved_media).await?;
|
||||
|
||||
Ok(ImagePipelineResult {
|
||||
|
||||
+16
-43
@@ -31,6 +31,7 @@ use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoints::generate::common::generation_debug_logs::GenerationDebugLogContext;
|
||||
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::helpers::hydrate_router_request::hydrate_to_router_request;
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::helpers::resolve_kinovi_character_ids::resolve_kinovi_character_ids;
|
||||
@@ -209,10 +210,17 @@ pub async fn omni_gen_video_generate_handler(
|
||||
warn!("Failed to insert HTTP request debug log: {:?}", err);
|
||||
}
|
||||
|
||||
// NB: Release the pooled DB connection before the (slow, external) generation call so we don't
|
||||
// hold a pool slot idle while waiting on the provider — that's what starves the pool and causes
|
||||
// PoolTimedOut on unrelated endpoints. We re-acquire below to write the result.
|
||||
drop(mysql_connection);
|
||||
// NB: The pipeline takes over the connection for its remaining pre-request DB writes (billing,
|
||||
// outbound provider request debug log) and releases it before the (slow, external) generation
|
||||
// call — holding a pool slot across that call is what starves the pool and causes PoolTimedOut
|
||||
// on unrelated endpoints. We re-acquire below to write the result.
|
||||
|
||||
let debug_log_context = GenerationDebugLogContext {
|
||||
event_token: &debug_log_event_token,
|
||||
user_token,
|
||||
ip_address: &ip_address,
|
||||
request_url: &request_url,
|
||||
};
|
||||
|
||||
let pipeline_result = run_pipeline_v2(RunPipelineV2Args {
|
||||
router_builder: &router_builder,
|
||||
@@ -221,6 +229,8 @@ pub async fn omni_gen_video_generate_handler(
|
||||
media_file_to_url_map: &media_file_to_url_map,
|
||||
kinovi_character_id_map: &kinovi_character_id_map,
|
||||
kinovi_account,
|
||||
debug_log_context: &debug_log_context,
|
||||
mysql_connection,
|
||||
}).await;
|
||||
|
||||
// ==================== DEBUG LOG: PIPELINE ERROR ==================== //
|
||||
@@ -250,45 +260,8 @@ pub async fn omni_gen_video_generate_handler(
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
|
||||
|
||||
// ==================== DEBUG LOG: FAL REQUEST ==================== //
|
||||
|
||||
if let GenerateVideoResponse::Fal(ref fal_payload) = pipeline_result.response {
|
||||
if let Some(ref outbound_request) = fal_payload.maybe_outbound_request {
|
||||
if let Err(err) = insert_debug_log(InsertDebugLogArgs {
|
||||
apriori_debug_log_event_token: Some(&debug_log_event_token),
|
||||
maybe_creator_user_token: Some(user_token),
|
||||
debug_log_type: DebugLogType::FalRequest,
|
||||
maybe_log_level: Some(DebugLogLevel::Info),
|
||||
maybe_ip_address: Some(&ip_address),
|
||||
maybe_url: Some(&request_url),
|
||||
message: &format!("{:#?}", outbound_request),
|
||||
mysql_executor: &mut *mysql_connection,
|
||||
phantom: Default::default(),
|
||||
}).await {
|
||||
warn!("Failed to insert Fal request debug log: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== DEBUG LOG: GROK API REQUEST ==================== //
|
||||
|
||||
if let GenerateVideoResponse::Grok(ref grok_payload) = pipeline_result.response {
|
||||
if let Some(ref outbound_request) = grok_payload.maybe_outbound_request {
|
||||
if let Err(err) = insert_debug_log(InsertDebugLogArgs {
|
||||
apriori_debug_log_event_token: Some(&debug_log_event_token),
|
||||
maybe_creator_user_token: Some(user_token),
|
||||
debug_log_type: DebugLogType::GrokApiRequest,
|
||||
maybe_log_level: Some(DebugLogLevel::Info),
|
||||
maybe_ip_address: Some(&ip_address),
|
||||
maybe_url: Some(&request_url),
|
||||
message: &format!("{:#?}", outbound_request),
|
||||
mysql_executor: &mut *mysql_connection,
|
||||
phantom: Default::default(),
|
||||
}).await {
|
||||
warn!("Failed to insert Grok API request debug log: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
// NB: Outbound provider requests (Fal/Grok/Kinovi) are debug-logged inside
|
||||
// the pipeline BEFORE the send, so the payload is captured even on failure.
|
||||
|
||||
// ==================== WRITE RESULT ==================== //
|
||||
|
||||
|
||||
+29
-9
@@ -15,6 +15,9 @@ use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoint_helpers::refund_wallet_after_api_failure::refund_wallet_after_api_failure;
|
||||
use crate::http_server::endpoints::generate::common::generation_debug_logs::{
|
||||
insert_provider_request_debug_log, provider_request_debug_log_type, GenerationDebugLogContext,
|
||||
};
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::helpers::bill_wallet::bill_wallet;
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::helpers::build_router_client::build_router_client;
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::helpers::pipeline_result::PipelineResult;
|
||||
@@ -31,6 +34,11 @@ pub struct RunPipelineV2Args<'a> {
|
||||
pub media_file_to_url_map: &'a Option<HashMap<MediaFileToken, String>>,
|
||||
pub kinovi_character_id_map: &'a Option<HashMap<CharacterToken, String>>,
|
||||
pub kinovi_account: KinoviAccount,
|
||||
pub debug_log_context: &'a GenerationDebugLogContext<'a>,
|
||||
/// The handler's open connection. The pipeline uses it for its remaining
|
||||
/// pre-request DB writes (billing, outbound-request debug log) and releases
|
||||
/// it BEFORE the external provider call.
|
||||
pub mysql_connection: sqlx::pool::PoolConnection<sqlx::MySql>,
|
||||
}
|
||||
|
||||
// NB: This pipeline does an external generation call (`upload_and_generate`) that can take many
|
||||
@@ -45,6 +53,8 @@ pub async fn run_pipeline_v2(args: RunPipelineV2Args<'_>) -> Result<PipelineResu
|
||||
media_file_to_url_map,
|
||||
kinovi_character_id_map,
|
||||
kinovi_account,
|
||||
debug_log_context,
|
||||
mut mysql_connection,
|
||||
} = args;
|
||||
|
||||
let mut router_builder = router_builder.clone();
|
||||
@@ -141,15 +151,25 @@ pub async fn run_pipeline_v2(args: RunPipelineV2Args<'_>) -> Result<PipelineResu
|
||||
|
||||
info!("v2 estimated cost: {} credits (estimates: {:?})", cost, cost_estimates);
|
||||
|
||||
// 3. Bill wallet (short-lived connection — released before the external call below).
|
||||
let billing = {
|
||||
let mut billing_connection = server_state.mysql_pool.acquire().await
|
||||
.map_err(|err| {
|
||||
error!("Failed to acquire MySQL connection for billing: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
bill_wallet(user_token, cost, &mut billing_connection).await?
|
||||
};
|
||||
// 3. Bill wallet on the handler's connection (same pre-request DB phase).
|
||||
let billing = bill_wallet(user_token, cost, &mut mysql_connection).await?;
|
||||
|
||||
// Debug-log the outbound provider request BEFORE the send — still on the
|
||||
// handler's connection — so the payload is captured even when the
|
||||
// upload/enqueue fails.
|
||||
if let Some(debug_log_type) = provider_request_debug_log_type(provider) {
|
||||
insert_provider_request_debug_log(
|
||||
debug_log_context,
|
||||
debug_log_type,
|
||||
&format!("{:#?}", draft_or_request),
|
||||
&mut *mysql_connection,
|
||||
).await;
|
||||
}
|
||||
|
||||
// NB: Done with pre-request DB writes. Release the pooled connection before
|
||||
// the (slow, external) provider call — holding it across that call is what
|
||||
// starves the pool and causes PoolTimedOut. Post-send writes re-acquire.
|
||||
drop(mysql_connection);
|
||||
|
||||
// 4. Upload media (if draft) and generate video.
|
||||
// The entire block is wrapped so Kinovi failures trigger a refund.
|
||||
|
||||
Reference in New Issue
Block a user