1212use Flowpack \JobQueue \Common \Job \JobManager ;
1313use Neos \Flow \Cli \CommandController ;
1414use Neos \Flow \Log \ThrowableStorageInterface ;
15+ use Netlogix \JobQueue \Polling \PollScheduler ;
1516use Netlogix \JobQueue \Pool \Pool ;
1617use Netlogix \JobQueue \Scheduled \Domain \Model \ScheduledJob ;
1718use Netlogix \JobQueue \Scheduled \Domain \SchedulingCoordinator ;
@@ -90,14 +91,21 @@ public function pollForIncomingJobsCommand(
9091 outputResults: $ outputResults ,
9192 preforkSize: $ preforkSize
9293 )
93- ->runLoop (function (Pool $ pool ) use ($ groupName , $ parallel , $ stopPollingAfter , $ pollingIntervalInSeconds , &$ queueDueJobs ): void {
94- // Check for new jobs in the database and schedule as much as the pool has capacity for
95- $ queueDueJobs = $ pool ->eventLoop ->addPeriodicTimer (
96- interval: $ pollingIntervalInSeconds ,
97- callback: function () use ($ pool , $ groupName , $ parallel ) {
98- $ this ->queueDueJobs (pool: $ pool , groupName: $ groupName , parallel: $ parallel );
99- }
94+ ->runLoop (function (Pool $ pool ) use ($ groupName , $ parallel , $ stopPollingAfter , $ pollingIntervalInSeconds ): void {
95+ $ scheduler = null ;
96+
97+ // Check for new jobs in the database and schedule as much as the pool has capacity for.
98+ // Capacity check and slot occupation happen synchronously inside queueDueJobs, so the
99+ // periodic poll and the immediate poll on job completion can never exceed $parallel.
100+ $ scheduler = PollScheduler::create (
101+ loop: $ pool ->eventLoop ,
102+ tryToPickUpWork: function () use ($ pool , $ groupName , $ parallel , &$ scheduler ): void {
103+ $ this ->queueDueJobs (pool: $ pool , groupName: $ groupName , parallel: $ parallel , pollScheduler: $ scheduler );
104+ },
105+ hasCapacity: fn () => count ($ pool ) < $ parallel ,
106+ interval: $ pollingIntervalInSeconds
100107 );
108+ $ scheduler ->start ();
101109
102110 // Keep the database connection alive
103111 $ ping = $ pool ->eventLoop ->addPeriodicTimer (
@@ -111,8 +119,8 @@ public function pollForIncomingJobsCommand(
111119 if ($ stopPollingAfter ) {
112120 $ pool ->eventLoop ->addTimer (
113121 interval: $ stopPollingAfter ,
114- callback: function () use ($ pool , $ queueDueJobs , $ ping ) {
115- $ pool -> eventLoop -> cancelTimer ( $ queueDueJobs );
122+ callback: function () use ($ pool , $ scheduler , $ ping ) {
123+ $ scheduler -> stop ( );
116124 $ checkForPoolToClear = $ pool ->eventLoop ->addPeriodicTimer (
117125 interval: 1 ,
118126 callback: function () use ($ pool , $ ping , &$ checkForPoolToClear ) {
@@ -132,9 +140,10 @@ public function pollForIncomingJobsCommand(
132140 * @param Pool $pool A pool of subprocesses waiting for new jobs to execute
133141 * @param string $groupName Handle only jobs of this group
134142 * @param int $parallel Number of jobs to handle in parallel
143+ * @param ?PollScheduler $pollScheduler Re-poll immediately once a slot frees up
135144 * @return int Number of handled jobs
136145 */
137- protected function queueDueJobs (Pool $ pool , string $ groupName , int $ parallel ): int
146+ protected function queueDueJobs (Pool $ pool , string $ groupName , int $ parallel, ? PollScheduler $ pollScheduler = null ): int
138147 {
139148 $ numberOfHandledJobs = 0 ;
140149 $ retry = new SchedulingCoordinator ($ this ->scheduler );
@@ -159,12 +168,14 @@ protected function queueDueJobs(Pool $pool, string $groupName, int $parallel): i
159168 }
160169 );
161170
162- $ process ->on (Pool::EVENT_EXIT , function () use ($ pool , $ retry , $ ping , &$ numberOfHandledJobs ) {
171+ $ process ->on (Pool::EVENT_EXIT , function () use ($ pool , $ retry , $ ping , $ pollScheduler , &$ numberOfHandledJobs ) {
163172 $ pool ->eventLoop ->cancelTimer ($ ping );
164173 $ numberOfHandledJobs --;
165- if ($ numberOfHandledJobs === 0 ) {
166- ($ numberOfHandledJobs === 0 ) && $ retry ->scheduleAll ();
167- }
174+ if ($ numberOfHandledJobs === 0 ) {
175+ $ retry ->scheduleAll ();
176+ }
177+ // A slot just freed up - pick up the next due job without waiting for the next periodic tick.
178+ $ pollScheduler ?->requestImmediatePoll();
168179 });
169180 $ process ->on (Pool::EVENT_SUCCESS , fn () => $ this ->scheduler ->release ($ next ));
170181 $ process ->on (Pool::EVENT_ERROR , fn () => $ retry ->markJobForRescheduling ($ next ));
0 commit comments