v13.39.0 の公式ソースを確認し、タスクスケジューリングの基本操作をパッケージ開発に適用します。プロバイダーの読み込みはパッケージの自動検出、設定の公開はパッケージ設定のマージとキャッシュを参照してください。
Scheduleの解決後に登録する
通常のLaravelアプリケーションでは、FoundationServiceProvider が Schedule::class をsingletonとして登録します。その生成処理はConsole Kernelの resolveConsoleSchedule() を呼び、アプリケーションのタイムゾーンとスケジューラ用キャッシュストアを適用します。
パッケージはこの共有Scheduleを拡張し、自分用の new Schedule() を作って終わりにしないようにします。次のプロバイダーは、Acme\Courier\ がComposerのPSR-4で src/ に対応し、パッケージに config/courier.php がある構成です。
callAfterResolving() は、未解決なら今後の解決を待ち、すでに解決済みなら取得してその場でもコールバックを呼びます。これにより、別のプロバイダーが先にScheduleを使った場合も登録できます。詳細はサービス解決後フックを参照してください。
runningInConsole() は特定のArtisanコマンドの判定ではありません。この例ではHTTP経由でScheduleを使う用途を対象外にします。また、フックを登録するプロバイダー自体は遅延プロバイダーにしません。必要な時点までに boot() が動かなければ、フックも登録されないためです。
イベントの登録とタスクの実行は別です。このプロバイダーを読み込んでも、その場でHeartbeatを実行するわけではありません。登録だけではcronや
schedule:work も起動しません。利用者が明示的に有効化する
パッケージ側のconfig/courier.php は、初期状態を無効にします。
schedule.enabled を true に変更します。この例では値を登録時に読み取ります。タイムゾーンはUTCを初期値とし、別のタイムゾーンを選ぶ場合は夏時間による実行の重複・欠落も考慮します。
設定は浅くマージされるため、登録器でも各キーの既定値を指定します。部分的な上書きで schedule 配列のほかのキーがなくなっても、基本設定で登録できます。
同じScheduleへの二重登録を防ぐ
次のsrc/CourierSchedule.php は、ログ1件を記録する最小のHeartbeatを登録します。WeakMap により、この登録器が同じScheduleオブジェクトへ追加するのは1回だけにします。
Schedule::call()・exec()・job() はイベントを配列へ追加します。同じ名前のイベントを見つけて置換したり、重複排除したりするAPIではありません。
イベント名はロックの互換性にも関わる
name() は description() の別名ですが、ロック名への影響はイベントの種類で異なります。以下は既定のmutex名です。
CallbackEventでは、
withoutOverlapping() や onOneServer() より先に空でない名前を付けます。名前が未設定なら、これらのメソッドは例外を投げます。
job() は通常、ジョブクラス名などを初期名にします。ただし、同じジョブを異なる引数・対象で登録する場合は、対象ごとの名前を明示します。引数だけを変えても、初期名が自動的に別名になるわけではありません。
SyncCatalogJob はパッケージが提供するジョブクラスの例です。ジョブの実装方法はキューを参照してください。
名前やコマンド、cron式を変更すると、種類によってロックの識別子も変わります。旧版と新版のプロセスが混在するデプロイでは、同じ処理でも別のロックを取得する可能性があります。単なる表示文言の変更と同じ扱いにせず、タスク名を安定した公開仕様として管理します。
onOneServerとwithoutOverlappingを分けて考える
onOneServer()は、その実行時刻に実行を担当するサーバーを選びます。確認したCacheSchedulingMutexは、イベントのmutex名に時分を加え、1時間の有効期限で保持します。タスク終了に合わせて解放する重複防止ロックとは別です。withoutOverlapping()は、前の実行が続いている間の重複を防ぎます。指定する期限の単位は分で、既定は1,440分です。短すぎる期限では、処理が終わる前にロックが失効します。job()がキューへ投入する場合、スケジュールイベントの完了はジョブの処理完了ではありません。ワーカーでの重複制御は、ジョブ側のユニークジョブやWithoutOverlappingミドルウェアなども別に設計します。
onOneServer() を有効にする利用者には、公式ドキュメントが指定する database・memcached・dynamodb・redis の共有キャッシュ構成を必要条件として案内します。ロックは業務処理のexactly-onceを保証するものではなく、再実行されても壊れない処理も必要です。
重い依存関係は実行時に解決する
上のHeartbeatのLoggerInterface は、イベント実行時に CallbackEvent がコンテナの call() を使って解決します。フックの登録時にログを出したり、クライアントを生成したりしない構成です。
コンストラクターに外部接続や業務処理を置かない設計は、Artisanコマンドの依存解決でも重要です。
また、
call() と job() はCallbackEventなので、runInBackground() は使えません。時間のかかる処理はキューに委譲するか、必要に応じて command() のバックグラウンド実行を選びます。
アプリケーション全体の設定を奪わない
Schedule::useCache() はScheduleが持つmutexオブジェクトのストアを変更します。パッケージのイベントだけを対象にする設定ではなく、同じmutexを共有するイベントにも影響します。パッケージの boot() で無条件に呼ばず、利用アプリケーションが選ぶ設定として扱います。
同様に、Schedule::alwaysOnOneServer()・スケジューラの一時停止・メンテナンスモード中の実行方針を、パッケージが勝手に変更しないようにします。
設定変更後はScheduleを新しく作る
例のenabled・cron式・タイムゾーンは登録時の設定です。生成済みScheduleに対して config() の値を変えるだけでは、イベントが追加・削除されたり、設定が読み直されたりしません。WeakMapの判定も再設定を行うためのものではありません。
設定キャッシュを利用するデプロイではキャッシュを再構築し、新しい設定を読み込むプロセスで schedule:list を確認します。長時間動くスケジューラや、1分未満のタスクを実行中の schedule:run についても、旧コード・旧設定が残らない運用を確認します。サブ分タスクのデプロイ時の中断はタスクスケジューリングを参照してください。
登録を実行から分けてテストする
次は、パッケージのComposerオートロードとPHPUnitを使うtests/CourierScheduleTest.php の例です。mutexをテストダブルにし、外部キャッシュや実際のcronを使わずに登録結果を確認します。
登録テストを通すために
schedule:run で利用アプリケーションの全タスクを実行しないようにします。本体のテストと、制御した環境でのスケジューラ統合テストを分けることで、無関係な定期処理の副作用を避けられます。
関連ページ
タスクスケジューリング
実行頻度、cronの導入、キューへの委譲、デプロイ時の運用を確認します。
パッケージのサービス解決後フック
未解決・解決済みサービスへの適用と、再登録時の注意点を確認します。
参照した一次情報
- Laravel公式ドキュメント: タスクスケジューリング
- Laravel公式ドキュメント: パッケージ開発
- FoundationServiceProvider: Scheduleのsingleton登録
- Console Kernel: Scheduleの生成と既定の設定
- ServiceProvider: callAfterResolving()
- Schedule: イベントの追加、ジョブの遅延生成、mutexの共有
- Event: コマンドイベントのmutex名
- CallbackEvent: 名前の必須条件と実行時の依存解決
- CacheEventMutex: 重複防止ロックと期限の変換
- CacheSchedulingMutex: 実行時刻ごとのロック
- 公式テスト: イベントの登録とタイムゾーン
- 公式テスト: ジョブクラス名の遅延生成