概要
LaravelのジョブでSFTPを行う際にConnection closed prematurely
エラーが起きました。
この記事では
- 原因
- 解決策
を解説します。
前提
- SFTP通信には
league/flysystem-sftp: ^1.1
を使用 - ジョブの処理には
php artisan queue:work
を使用
原因
シンプルに前のコネクションが残っておりタイムアウトしたからです😭
なぜ前のコネクションが残っていた?
queue:work
でジョブの処理を行う際、Laravelはワーカープロセスを常駐させ処理毎にアプリの更新を行いません。
つまり、シングルトンインスタンスに設定されたプロパティがジョブ毎にリセットされません。
(通常のリクエスト=>アプリ生成=>処理実行=>アプリ停止の流れだとリセットされます)
league/flysystem-sftp
ではプロパティにコネクションが設定されていれば、そのまま使用します。そのため、以下の流れでエラーが発生してしまいます。
- プロセス立ち上げ後の初回のジョブ実行で
connection
プロパティ設定 - 2回目以降は、
connection
プロパティが存在するのでそのまま利用 -
connection
プロパティがタイムアウトしているためエラー発生
↓コネクションの有無をチェックしているコード
public function getConnection()
{
$tries = 0;
while ( ! $this->isConnected() && $tries < 3) {
$tries++;
$this->disconnect();
$this->connect();
}
return $this->connection;
}
解決策
2パターンを紹介します。
queue:listenを利用する。
queue:listen
はqueue:work
と異なり、ジョブ毎にアプリを生成します。
そのため、インスタンスの内容もリセットされコネクションも残りません。
しかし、都度アプリを生成するので相応に負荷が上昇します。
手動で再接続する
Adapterの接続/切断メソッドはpublicなので手動で実行できます。
putの前に再接続を仕込んでおくことで、コネクションをリセットできます。
処理後にdisconnect()を仕込んでおくだけでも動作可能だと思います。
/** @var Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk($diskName);
$disk->getDriver()->getAdapter()->disconnect();
$disk->getDriver()->getAdapter()->connect();
まとめ
queue:work
が常駐プロセスなので、一部情報が残ってしまうことに気づかず起きたエラーです。
SFTP接続に限らず、要らぬリスクを埋め込んでしまう危険性があるので今後注意したいと思います。