4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravelのジョブ内で通信を行う場合の注意点

Posted at

概要

LaravelのジョブでSFTPを行う際にConnection closed prematurelyエラーが起きました。
この記事では

  • 原因
  • 解決策

を解説します。

前提

  • SFTP通信にはleague/flysystem-sftp: ^1.1を使用
  • ジョブの処理にはphp artisan queue:workを使用

原因

シンプルに前のコネクションが残っておりタイムアウトしたからです😭

なぜ前のコネクションが残っていた?

queue:workでジョブの処理を行う際、Laravelはワーカープロセスを常駐させ処理毎にアプリの更新を行いません。
つまり、シングルトンインスタンスに設定されたプロパティがジョブ毎にリセットされません。
(通常のリクエスト=>アプリ生成=>処理実行=>アプリ停止の流れだとリセットされます)

league/flysystem-sftpではプロパティにコネクションが設定されていれば、そのまま使用します。そのため、以下の流れでエラーが発生してしまいます。

  1. プロセス立ち上げ後の初回のジョブ実行でconnectionプロパティ設定
  2. 2回目以降は、connectionプロパティが存在するのでそのまま利用
  3. connectionプロパティがタイムアウトしているためエラー発生

↓コネクションの有無をチェックしているコード

Adapter/AbstractFtpAdapter.php
    public function getConnection()
    {
        $tries = 0;

        while ( ! $this->isConnected() && $tries < 3) {
            $tries++;
            $this->disconnect();
            $this->connect();
        }

        return $this->connection;
    }

解決策

2パターンを紹介します。

queue:listenを利用する。

queue:listenqueue:workと異なり、ジョブ毎にアプリを生成します。
そのため、インスタンスの内容もリセットされコネクションも残りません。
しかし、都度アプリを生成するので相応に負荷が上昇します。

手動で再接続する

Adapterの接続/切断メソッドはpublicなので手動で実行できます。
putの前に再接続を仕込んでおくことで、コネクションをリセットできます。
処理後にdisconnect()を仕込んでおくだけでも動作可能だと思います。

    /** @var Illuminate\Filesystem\FilesystemAdapter */
    $disk = Storage::disk($diskName);

    $disk->getDriver()->getAdapter()->disconnect();
    $disk->getDriver()->getAdapter()->connect();

まとめ

queue:workが常駐プロセスなので、一部情報が残ってしまうことに気づかず起きたエラーです。
SFTP接続に限らず、要らぬリスクを埋め込んでしまう危険性があるので今後注意したいと思います。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?