@@ -171,6 +171,53 @@ function ms_run_touch(string $id, int $ttl = 300): void {
171171 }
172172}
173173
174+ /**
175+ * Atomically claim run start for a short period to prevent duplicate dispatch
176+ * from concurrent manager requests.
177+ *
178+ * @param string $id Run identifier
179+ * @param int $ttl Lock lifetime in seconds
180+ * @return bool true if claim acquired, false if already claimed recently
181+ */
182+ if (!function_exists ('ms_run_claim_start ' )) {
183+ function ms_run_claim_start (string $ id , int $ ttl = 30 ): bool {
184+ $ safe = preg_replace ('~[^A-Za-z0-9_\-]~ ' , '' , $ id );
185+ if ($ safe === '' ) {
186+ return false ;
187+ }
188+
189+ $ path = ms_run_store_dir () . '/ ' . $ safe . '.start ' ;
190+ $ now = time ();
191+
192+ $ fp = @fopen ($ path , 'c+ ' );
193+ if (!is_resource ($ fp )) {
194+ return false ;
195+ }
196+
197+ if (!@flock ($ fp , LOCK_EX )) {
198+ @fclose ($ fp );
199+ return false ;
200+ }
201+
202+ $ raw = stream_get_contents ($ fp );
203+ $ claimedAt = (int )trim ((string )$ raw );
204+ if ($ claimedAt > 0 && ($ claimedAt + $ ttl ) > $ now ) {
205+ @flock ($ fp , LOCK_UN );
206+ @fclose ($ fp );
207+ return false ;
208+ }
209+
210+ ftruncate ($ fp , 0 );
211+ rewind ($ fp );
212+ fwrite ($ fp , (string )$ now );
213+ fflush ($ fp );
214+ @flock ($ fp , LOCK_UN );
215+ @fclose ($ fp );
216+
217+ return true ;
218+ }
219+ }
220+
174221/**
175222 * Delete run plan after completion.
176223 *
@@ -179,8 +226,13 @@ function ms_run_touch(string $id, int $ttl = 300): void {
179226 */
180227if (!function_exists ('ms_run_del ' )) {
181228 function ms_run_del (string $ id ): void {
182- $ path = ms_run_store_dir () . '/ ' . preg_replace ('~[^A-Za-z0-9_\-]~ ' , '' , $ id ) . '.json ' ;
183- @unlink ($ path );
229+ $ safe = preg_replace ('~[^A-Za-z0-9_\-]~ ' , '' , $ id );
230+ if ($ safe === '' ) {
231+ return ;
232+ }
233+
234+ @unlink (ms_run_store_dir () . '/ ' . $ safe . '.json ' );
235+ @unlink (ms_run_store_dir () . '/ ' . $ safe . '.start ' );
184236 }
185237}
186238
0 commit comments