1414namespace Graze \ParallelProcess ;
1515
1616use Graze \DataStructure \Collection \Collection ;
17- use Graze \ParallelProcess \Exceptions \AlreadyRunningException ;
17+ use Graze \ParallelProcess \Exceptions \NotRunningException ;
1818use InvalidArgumentException ;
1919use Symfony \Component \Process \Process ;
2020
2121class Pool extends Collection implements RunInterface
2222{
2323 const CHECK_INTERVAL = 0.1 ;
24+ const NO_MAX = -1 ;
2425
2526 /** @var RunInterface[] */
2627 protected $ items = [];
2728 /** @var RunInterface[] */
2829 protected $ running = [];
30+ /** @var RunInterface[] */
31+ protected $ waiting = [];
2932 /** @var callable|null */
3033 protected $ onSuccess ;
3134 /** @var callable|null */
3235 protected $ onFailure ;
3336 /** @var callable|null */
3437 protected $ onProgress ;
38+ /** @var int */
39+ private $ maxSimultaneous = -1 ;
3540
3641 /**
3742 * Pool constructor.
@@ -42,18 +47,21 @@ class Pool extends Collection implements RunInterface
4247 * @param callable|null $onSuccess function (Process $process, float $duration, string $last) : void
4348 * @param callable|null $onFailure function (Process $process, float $duration, string $last) : void
4449 * @param callable|null $onProgress function (Process $process, float $duration, string $last) : void
50+ * @param int $maxSimultaneous
4551 */
4652 public function __construct (
4753 array $ items = [],
4854 callable $ onSuccess = null ,
4955 callable $ onFailure = null ,
50- callable $ onProgress = null
56+ callable $ onProgress = null ,
57+ $ maxSimultaneous = self ::NO_MAX
5158 ) {
5259 parent ::__construct ($ items );
5360
5461 $ this ->onSuccess = $ onSuccess ;
5562 $ this ->onFailure = $ onFailure ;
5663 $ this ->onProgress = $ onProgress ;
64+ $ this ->maxSimultaneous = $ maxSimultaneous ;
5765 }
5866
5967 /**
@@ -106,15 +114,14 @@ public function add($item)
106114 throw new InvalidArgumentException ("add: Can only add `RunInterface` to this collection " );
107115 }
108116
109- $ itemRunning = $ item ->isRunning ();
110- if ((count ($ this ->running ) > 0 ) && !$ itemRunning ) {
111- throw new AlreadyRunningException ("add: unable to add an item when the pool is currently running " );
117+ if (!$ this ->isRunning () && $ item ->isRunning ()) {
118+ throw new NotRunningException ("add: unable to add a running item when the pool has not started " );
112119 }
113120
114121 parent ::add ($ item );
115122
116- if ($ itemRunning ) {
117- $ this ->running [] = $ item ;
123+ if ($ this -> isRunning () ) {
124+ $ this ->startRun ( $ item) ;
118125 }
119126
120127 return $ this ;
@@ -145,14 +152,27 @@ protected function addProcess(Process $process)
145152 public function start ()
146153 {
147154 foreach ($ this ->items as $ run ) {
148- $ run -> start ( );
155+ $ this -> startRun ( $ run );
149156 }
150157
151- $ this ->running = $ this ->items ;
152-
153158 return $ this ;
154159 }
155160
161+ /**
162+ * Start a run (or queue it if we are running the maximum number of processes already)
163+ *
164+ * @param RunInterface $run
165+ */
166+ private function startRun (RunInterface $ run )
167+ {
168+ if ($ this ->maxSimultaneous === static ::NO_MAX || count ($ this ->running ) < $ this ->maxSimultaneous ) {
169+ $ run ->start ();
170+ $ this ->running [] = $ run ;
171+ } else {
172+ $ this ->waiting [] = $ run ;
173+ }
174+ }
175+
156176 /**
157177 * Blocking call to run processes;
158178 *
@@ -163,14 +183,31 @@ public function start()
163183 public function run ($ checkInterval = self ::CHECK_INTERVAL )
164184 {
165185 $ this ->start ();
186+ $ interval = $ checkInterval * 1000000 ;
166187
167- while ($ this ->isRunning ()) {
168- usleep ($ checkInterval * 1000000 );
188+ while ($ this ->poll ()) {
189+ usleep ($ interval );
169190 }
170191
171192 return $ this ->isSuccessful ();
172193 }
173194
195+ /**
196+ * Check when a run has finished, if there are processes waiting, start them
197+ */
198+ private function checkFinished ()
199+ {
200+ if ($ this ->maxSimultaneous !== static ::NO_MAX
201+ && count ($ this ->waiting ) > 0
202+ && count ($ this ->running ) < $ this ->maxSimultaneous ) {
203+ for ($ i = count ($ this ->running ); $ i < $ this ->maxSimultaneous && count ($ this ->waiting ) > 0 ; $ i ++) {
204+ $ run = array_shift ($ this ->waiting );
205+ $ run ->start ();
206+ $ this ->running [] = $ run ;
207+ }
208+ }
209+ }
210+
174211 /**
175212 * Determine if any item has run
176213 *
@@ -191,13 +228,23 @@ public function hasStarted()
191228 *
192229 * @return bool
193230 */
194- public function isRunning ()
231+ public function poll ()
195232 {
196233 /** @var Run[] $running */
197234 $ this ->running = array_filter ($ this ->running , function (RunInterface $ run ) {
198- return $ run ->isRunning ();
235+ return $ run ->poll ();
199236 });
200237
238+ $ this ->checkFinished ();
239+
240+ return $ this ->isRunning ();
241+ }
242+
243+ /**
244+ * @return bool
245+ */
246+ public function isRunning ()
247+ {
201248 return count ($ this ->running ) > 0 ;
202249 }
203250
@@ -220,4 +267,43 @@ public function isSuccessful()
220267
221268 return true ;
222269 }
270+
271+ /**
272+ * Get a list of all the currently running runs
273+ *
274+ * @return RunInterface[]
275+ */
276+ public function getRunning ()
277+ {
278+ return $ this ->running ;
279+ }
280+
281+ /**
282+ * Get a list of all the current waiting runs
283+ *
284+ * @return RunInterface[]
285+ */
286+ public function getWaiting ()
287+ {
288+ return $ this ->waiting ;
289+ }
290+
291+ /**
292+ * @return int
293+ */
294+ public function getMaxSimultaneous ()
295+ {
296+ return $ this ->maxSimultaneous ;
297+ }
298+
299+ /**
300+ * @param int $maxSimultaneous
301+ *
302+ * @return $this
303+ */
304+ public function setMaxSimultaneous ($ maxSimultaneous )
305+ {
306+ $ this ->maxSimultaneous = $ maxSimultaneous ;
307+ return $ this ;
308+ }
223309}
0 commit comments