feat: add poll_now_notify to poll_loop and on_work_available callback#1893
feat: add poll_now_notify to poll_loop and on_work_available callback#1893Jeadie wants to merge 4 commits into
poll_now_notify to poll_loop and on_work_available callback#1893Conversation
Adds an optional poll_now_notify: Option<Arc<Notify>> parameter to the executor poll_loop so the scheduler can wake an idle executor immediately instead of waiting for the next poll interval. The idle wait becomes a tokio::select! over the poll interval and the notify. Adds an OnWorkAvailableFn callback to SchedulerConfig, invoked from the query stage scheduler when new work becomes available (after JobSubmitted and when new stages become runnable). Ported from #12 for upstreaming.
poll_now_notify to poll_loop and on_work_available callback
| /// - Tasks complete and new stages become runnable | ||
| /// | ||
| /// This allows external systems to notify executors to poll immediately | ||
| /// rather than waiting for their next poll interval. |
There was a problem hiding this comment.
| /// rather than waiting for their next poll interval. | |
| /// rather than waiting for their next poll interval. | |
| /// | |
| /// # Warning | |
| /// | |
| /// This callback is executed synchronously within the scheduler's main event loop. | |
| /// Implementations **must be non-blocking** and should offload any blocking or | |
| /// long-running operations (such as network I/O) to a separate task or thread. |
The callback is synchronous and a blocking implementation may affect badly the Scheduler;w work.
| match &poll_now_notify { | ||
| Some(notify) => { | ||
| tokio::select! { | ||
| () = tokio::time::sleep(Duration::from_millis(50)) => {} |
There was a problem hiding this comment.
The duration here could be longer.
The idea is that notify will notify us as soon as there is some work, right ?
So, there is no need to poll so frequently if an external mechanism will notify us when to do it.
There was a problem hiding this comment.
Agreed, implemented to use a 1s fallback if a poll_now_notify is provided, otherwise unchanged at 50ms to preserve existing behavior.
| // Notify external systems when new stages become runnable | ||
| if !stage_events.is_empty() | ||
| && let Some(ref callback) = self.config.on_work_available | ||
| { | ||
| callback("tasks_completed:new_stages_runnable"); | ||
| } | ||
|
|
There was a problem hiding this comment.
Shouldn't this be done after line 312 ? I.e. after posting the events.
Currently this will tell executors to poll but it seems to early.
There was a problem hiding this comment.
The stage_events are not necessarily "new stages are runnable". They might be JobRunningFailed or JobFinished, neither of which represents new runnable work.
There was a problem hiding this comment.
This is correct, changed to remove the callback from TaskUpdating and moved into the JobUpdated arm, assuming update_job returns new_tasks > 0. So this only notifies when stages genuinely had new scheduleable tasks, and only after they are updated.
| cors_allowed_origins: opt.cors_allowed_origins, | ||
| #[cfg(feature = "rest-api")] | ||
| cors_allowed_methods: opt.cors_allowed_methods, | ||
| on_work_available: None, |
There was a problem hiding this comment.
How is this new setting supposed to be set ?
I'd expect at least a new with_on_work_available setter to be added if there is no new command line option.
There was a problem hiding this comment.
Added SchedulerConfig::with_on_work_available
|
|
||
| // Notify external systems that new work is available | ||
| if let Some(ref callback) = self.config.on_work_available { | ||
| callback(&format!("job_submitted:{job_id}")); |
There was a problem hiding this comment.
The event_sender.post_event(QueryStageSchedulerEvent::ReviveOffers) is non-blocking and the events might be still in the channel when this callback is executed. I.e. the first poll may see no events.
There was a problem hiding this comment.
Fixed. The callback now fires only after graph.revive() has run (in update_job, and submit_plan revives before caching), which is the exact point tasks become visible to poll_work. Now a woken executor's first poll always finds the work.
| /// | ||
| /// This allows external systems to notify executors to poll immediately | ||
| /// rather than waiting for their next poll interval. | ||
| pub type OnWorkAvailableFn = Arc<dyn Fn(&str) + Send + Sync>; |
There was a problem hiding this comment.
The function receives a plain &str as a command/message. IMO it would be better to use an Enum is all possible variants of commands.
There was a problem hiding this comment.
Is there a need to use an Arc ?
It could be a Box.
There was a problem hiding this comment.
Replaced the &str with a WorkAvailableReason enum (JobSubmitted { job_id } / NewStagesRunnable { job_id }), so consumers get structured data instead of parsing strings.
It also needs to stay Arc because SchedulerConfig derives Clone and Box<dyn Fn> isn't cloneable. We could drop the Clone requirement if we want to keep it as Box.
- Replace the callback's `&str` argument with a `WorkAvailableReason` enum - Fire the stage-resolution callback from the JobUpdated arm after `update_job` has revived the graph, so it only fires for genuinely new runnable work and never before the work is pollable - Add `SchedulerConfig::with_on_work_available` - Document that the callback runs synchronously in the scheduler event loop and must not block - Idle-poll less aggressively (1s fallback) when a poll_now notifier is wired
|
@martin-g Thanks for the thorough review, this should be ready now for another look. |
Which issue does this PR close?
None
Closes #.Suggested by @milenkovicm that we should upstream our changes (see comment).Rationale for this change
Useful in spiceai/datafusion-ballista for production features.
What changes are included in this PR?
Adds two related hooks for push-style executor scheduling:
poll_now_notify: Option<Arc<Notify>>parameter on the executorpoll_loop. When provided, the idle wait becomes atokio::select!over the poll interval and the notify, so the scheduler can wake an idle executor immediately instead of waiting for the next poll tick.Nonepreserves the existing timer-only behavior.OnWorkAvailableFncallback onSchedulerConfig(on_work_available), invoked by the query stage scheduler when new work becomes available (afterJobSubmittedand when new stages become runnable).Ported from spiceai#12