sessions->getOrFail($sessionId); $lockSeconds = (int) config('uploads.chunk.lock_seconds', 10); $lockWait = (int) config('uploads.chunk.lock_wait_seconds', 5); $lock = Cache::lock('uploads:cancel:' . $sessionId, $lockSeconds); try { $lock->block($lockWait); } catch (\Throwable $e) { $this->audit->log($userId, 'upload_cancel_locked', $ip, [ 'session_id' => $sessionId, ]); throw new RuntimeException('Upload is busy. Please retry.'); } try { if (in_array($session->status, [UploadSessionStatus::CANCELLED, UploadSessionStatus::PROCESSED, UploadSessionStatus::QUARANTINED], true)) { $this->audit->log($userId, 'upload_cancel_noop', $ip, [ 'session_id' => $sessionId, 'status' => $session->status, ]); return [ 'session_id' => $sessionId, 'status' => $session->status, ]; } $this->safeDeleteTmp($session->tempPath); $this->sessions->updateStatus($sessionId, UploadSessionStatus::CANCELLED); $this->sessions->updateProgress($sessionId, 0); $this->sessions->updateFailureReason($sessionId, 'cancelled'); $this->audit->log($userId, 'upload_cancelled', $ip, [ 'session_id' => $sessionId, ]); return [ 'session_id' => $sessionId, 'status' => UploadSessionStatus::CANCELLED, ]; } finally { optional($lock)->release(); } } private function safeDeleteTmp(string $path): void { $tmpRoot = $this->storage->sectionPath('tmp'); $realRoot = realpath($tmpRoot); $realPath = realpath($path); if (! $realRoot || ! $realPath || strpos($realPath, $realRoot) !== 0) { return; } if (File::exists($realPath)) { File::delete($realPath); } } }