collective

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

diff --git a/crates/api_clients/fal_client/src/polling/mod.rs b/crates/api_clients/fal_client/src/polling/mod.rs--- a/crates/api_clients/fal_client/src/polling/mod.rs+++ b/crates/api_clients/fal_client/src/polling/mod.rs@@ -1,2 +1,3 @@ pub mod poll_job_status; pub mod poll_job_response;+mod polling_http_client;diff --git a/crates/api_clients/fal_client/src/polling/poll_job_response/poll_job_response.rs b/crates/api_clients/fal_client/src/polling/poll_job_response/poll_job_response.rs--- a/crates/api_clients/fal_client/src/polling/poll_job_response/poll_job_response.rs+++ b/crates/api_clients/fal_client/src/polling/poll_job_response/poll_job_response.rs@@ -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()diff --git a/crates/api_clients/fal_client/src/polling/poll_job_status/poll_job_status.rs b/crates/api_clients/fal_client/src/polling/poll_job_status/poll_job_status.rs--- a/crates/api_clients/fal_client/src/polling/poll_job_status/poll_job_status.rs+++ b/crates/api_clients/fal_client/src/polling/poll_job_status/poll_job_status.rs@@ -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()diff --git a/crates/api_clients/fal_client/src/polling/polling_http_client.rs b/crates/api_clients/fal_client/src/polling/polling_http_client.rsnew file mode 100644--- /dev/null+++ b/crates/api_clients/fal_client/src/polling/polling_http_client.rs@@ -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);diff --git a/crates/desktop/artcraft/src/core/threads/third_party_task_polling_thread/third_party_task_polling_thread.rs b/crates/desktop/artcraft/src/core/threads/third_party_task_polling_thread/third_party_task_polling_thread.rs--- a/crates/desktop/artcraft/src/core/threads/third_party_task_polling_thread/third_party_task_polling_thread.rs+++ b/crates/desktop/artcraft/src/core/threads/third_party_task_polling_thread/third_party_task_polling_thread.rs@@ -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,diff --git a/frontend/apps/artcraft/app/src/components/signaled/TopBar/TaskQueue.tsx b/frontend/apps/artcraft/app/src/components/signaled/TopBar/TaskQueue.tsx--- a/frontend/apps/artcraft/app/src/components/signaled/TopBar/TaskQueue.tsx+++ b/frontend/apps/artcraft/app/src/components/signaled/TopBar/TaskQueue.tsx@@ -680,7 +680,6 @@ export const TaskQueue = () => {         const result = await GetTaskQueue();          if (cancelled) return;-        console.log("TaskQueue:GetTaskQueue result", result);          const { tasks } = result; diff --git a/frontend/apps/artcraft/app/src/signals/activeJobs.ts b/frontend/apps/artcraft/app/src/signals/activeJobs.ts--- a/frontend/apps/artcraft/app/src/signals/activeJobs.ts+++ b/frontend/apps/artcraft/app/src/signals/activeJobs.ts@@ -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.