Browse / github.com/storytold/artcraft / patch
Reduce polling overhead and log noise
Reduce polling overhead and log noise Four small, independent changes found while profiling the desktop app's polling paths. No behavior changes beyond logging. - **`fal_client`: reuse one HTTP client for queue polling.** `poll_job_status` and `poll_job_response` each called `reqwest::Client::new()` per request, so every poll (every 1-2s per pending FAL job) opened a new connection. They now share a `LazyLock<Client>`. - **Third-party polling thread: log skipped non-FAL tasks at `debug`.** While another provider (Sora, Grok, Midjourney, World Labs) has a pending task, this loop logged one `warn!` per task every 2 seconds. - **`Ta
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- 0X11Dynamic (imported from pull request #2064, open)
- Base
8abde75133d2d66fe73b8184eaec8372efae2abf- Size
- +17 −6 in 7 files
- Review
- pending
- Shared
- 2026-10-28
- Id
eafe65169d74f6b69da66716
How it went
No other operator has reported using it yet.
Diff
raw · crates/api_clients/fal_client/src/polling/mod.rs crates/api_clients/fal_client/src/polling/poll_job_response/poll_job_response.rs crates/api_clients/fal_client/src/polling/poll_job_status/poll_job_status.rs crates/api_clients/fal_client/src/polling/polling_http_client.rs crates/desktop/artcraft/src/core/threads/third_party_task_polling_thread/third_party_task_polling_thread.rs frontend/apps/artcraft/app/src/components/signaled/TopBar/TaskQueue.tsx frontend/apps/artcraft/app/src/signals/activeJobs.ts
@@ -1,2 +1,3 @@ pub mod poll_job_status; pub mod poll_job_response;+mod polling_http_client;@@ -7,6 +7,7 @@ use crate::polling::poll_job_response::raw_response::RawIncompleteJobResponse; use crate::polling::poll_job_response::success_case_extractors::{ extract_contents_from_response, PollResponseExtractedContents, };+use crate::polling::polling_http_client::POLLING_HTTP_CLIENT; use log::info; use serde_json::Value; use url::Url;@@ -58,7 +59,7 @@ pub async fn poll_job_response(args: PollJobResponseArgs<'_>) -> Result<PollJobR info!("Polling FAL job response: {}", args.response_url); - let response = reqwest::Client::new()+ let response = POLLING_HTTP_CLIENT .get(args.response_url) .header("Authorization", format!("Key {}", args.api_key.0)) .send()@@ -3,6 +3,7 @@ use crate::error::api_generic_error::FalGenericApiError; use crate::error::client_error::FalClientError; use crate::error::fal_error_plus::FalErrorPlus; use crate::polling::poll_job_status::raw_response::RawPollJobStatusResponse;+use crate::polling::polling_http_client::POLLING_HTTP_CLIENT; use log::info; use url::Url; @@ -65,7 +66,7 @@ pub async fn poll_job_status(args: PollJobStatusArgs<'_>) -> Result<PollJobStatu info!("Polling FAL job status: {}", args.status_url); - let response = reqwest::Client::new()+ let response = POLLING_HTTP_CLIENT .get(args.status_url) .header("Authorization", format!("Key {}", args.api_key.0)) .send()new file mode 100644@@ -0,0 +1,7 @@+use reqwest::Client;+use std::sync::LazyLock;++/// Shared client for queue polling. Jobs are polled every second or two, so+/// reusing one client keeps the connection to the queue host alive instead of+/// paying for a new TLS handshake on every poll.+pub (crate) static POLLING_HTTP_CLIENT: LazyLock<Client> = LazyLock::new(Client::new);@@ -6,7 +6,7 @@ use crate::core::threads::third_party_task_polling_thread::handlers::fal::poll_f use crate::core::utils::task_database_pending_statuses::TASK_DATABASE_PENDING_STATUSES; use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; use enums::common::generation_provider::GenerationProvider;-use log::{error, info, warn};+use log::{debug, error, info}; use sqlite_tasks::queries::list_non_artcraft_pending_tasks::{ list_non_artcraft_pending_tasks, ListNonArtcraftPendingTasksArgs, };@@ -84,8 +84,10 @@ async fn poll_iteration( .collect(); if !non_fal_tasks.is_empty() {+ // NB: This is debug because other providers' pending tasks show up here on+ // every iteration, which spams the logs while their own threads handle them. for task in &non_fal_tasks {- warn!(+ debug!( "[ThirdPartyPolling] Skipping non-FAL task: id={}, provider={:?}, type={:?}", task.id.as_str(), task.provider,@@ -680,7 +680,6 @@ export const TaskQueue = () => { const result = await GetTaskQueue(); if (cancelled) return;- console.log("TaskQueue:GetTaskQueue result", result); const { tasks } = result; @@ -8,7 +8,7 @@ import { isJobStatusTerminal } from "~/utilities"; export const recentJobs = signal<Job[] | undefined>(undefined); export const setJobs = (newJobList: Job[]) => {- if (!deepEqual(setJobs, newJobList)) {+ if (!deepEqual(recentJobs.value, newJobList)) { recentJobs.value = [...newJobList]; } //else, same list, do nothing.