XcodeからiOS実機デバッグ時のエラー解消方法
概要
前提
エラー1(Could not locate device support files.)
- ビルド成功後、実機へ転送するタイミングで「Could not locate device support files.」というダイアログが表示
- 解決するには、以下から対象バージョン(今回は13.5)のzipファイルをダウンロードし、解凍したディレクトリを「/Applications/Xcode10.3.app/Contents/Developer/Platforms/iPhoneOS.platform」に配置後、Xcodeを再起動。 github.com
エラー2(dyld_shared_cache_extract_dylibs failed)
macOS CatalinaにアップグレードしたらCordovaのビルドで失敗するようになった
前提
- macOS Sierraからのアップグレードした
- 念のためTime Machineでバックアップ取ってたが、ファイルシステムの違いから復元が簡単ではなくなった
- OSがアップグレードされることで、Xcodeのバージョンアップも必要となった(11.4をインストール)
- Command Line Tools for Xcodeも合わせてバージョンアップした
- CordovaプロジェクトではSwift3でプラグイン実装をしていた
- Xcodeのバージョンを上げたことからSwiftも4以上にする必要がでてきた
最初にやったこと
- XcodeのSwiftのバージョンを4.2へ変更
- バージョン変更によってビルドエラーになるプログラムを修正
cordovaビルドエラーが発生と解決策
- cordovaのビルドで以下のエラーが発生
FIXME: Implement XCBuild support for macros in overriding parameters with condition sets: CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer
- Xcode10から正式採用されたビルドシステムの変更のためのエラーらしい。
- Xcodeの設定でビルドシステムを元のものに戻す
- 改めてcordovaのビルドをすると正常に流れる
Swiftのプラグイン関数が未定義となり実行時エラーと解決策
- アプリを実機動作確認中に発覚
- JavaScriptからSwiftの関数を呼び出していたが、以下のような実行時エラーが発生(Xcode上のログで確認)
ERROR: Method '関数名:' not defined in Plugin 'プラグイン名'
Swift関数名の先頭に、@objc(関数名:) を付与する
修正前
func hoge(_ command: CDVInvokedUrlCommand) { }
修正後
@objc(hoge:) func hoge(_ command: CDVInvokedUrlCommand) { }
- Swiftファイルを修正して改めて実機デプロイ確認すると正常プラグイン呼び出しできるようになる
バーコード読み込み機能が動かない事象と解決策
- Swiftプラグインで実装しているバーコード読み込み機能が動かない
- 具体的には、機能の初期処理は動いているものの、バーコード読み込みされたときのイベント関数が動いていない
- 修正前は、AVCaptureMetadataOutputObjectsDelegate の captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) を利用していた。
- 関数名が変わっていたため、metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) に変更
- 上記変更で正しくバーコード読み込みされたときに、metadataOutput関数が実行されるようになる
cordovaビルドで再びエラーが発生と解決策
- cordovaのビルド解決していたと思いきや、再びエラーが発生。内容は以下のメッセージ
Cannot read property 'toLowerCase' of undefined
- Xcodeのバージョンが11になったことで、cordovaプラットフォームのiOSのバージョン(このときは、4.5.5を指定していた)を5系に上げなければいけないらしい
- 以下のようなコマンドでプラットフォームを作り直そうとしたが、既存の独自プラグインやらXcode上で指定していたライブラリの依存関係、CocoaPosdの設定などが消えてしまい、この方法は時間があるときに改めてやることにした。
cordova platform rm ios cordova platform add ios@latest
Nuxt.js (generate)とNetlify Forms (recaptcha)の問題と一時的な解決案
概要
- Nuxt.jsのプロジェクトでNetlify Formsを利用して、簡単なForm画面とrecaptcha機能を試したときに、ハマった点をメモ docs.netlify.com
前提
- nuxt.js (v2.11.0)
- 「npx create-nuxt-app」で生成したプロジェクトをNetlify上にデプロイ
- NetlifyでGitHubのリポジトリと関連付けして、「npm run generate」するようにdeploy設定
ハマった点(その1)
問題点
- Nuxt.jsプロジェクトの「pages」ディレクトリにForm画面用のvueファイルを作成しNetlify上で確認したところ、Form画面は普通に表示できているように見えるが、Submitしても対象のAPIが用意されていなく、エラーとなってしまう。(Dev Toolsで確認すると、html要素は正しく出力されているように見える)
- ローカルで、「npm run generate」して生成されたdistディレクトリ内を確認すると、生成されたForm画面のhtmlには「netlify」属性のついた要素は出力されていなく、jsファイルの読み込みのみになっている。
- generateされた結果として「netlify」属性が出力されていないため、NetlifyとしてもForm用のAPIが作成されていないと思われる。
解決案
- Nuxt.jsの「pages」で追加していたForm画面のtemplate要素の記述を、「components」ディレクトリのvueファイルとして移動。
- 「pages」で追加していたForm画面では、上記で作成したコンポーネントを読み込むように改修
- 改めてローカルで「npm run generate」し、distディレクトリ内を確認すると、Form画面のhtmlには「netlify」属性が付与されている要素が出力されている
- Netlify上にデプロイして試しても、FormのSubmitが正しく動作することを確認。(Netlify上の管理画面でも送信されたパラメータが確認可能)
ハマった点(その2)
問題点
- 「その1」でForm送信が可能となったので、以下のコードを追加して強制的にrecaptcha機能を表示しようとしたが、うまく表示されない。
<div data-netlify-recaptcha="true"></div>
- 初期表示画面からForm画面に遷移すると、Form要素は正しく表示されているが「recapthca」機能は表示されていなく。DevToolsで確認するとdivタグ自体は存在するものの、heightが0になって見えていない状態となっている。
- 試しにForm画面をリロードすると、「recaptcha」機能が見える状態でForm画面が表示される。(ページ遷移を挟むとうまく表示されない)
- ページ遷移を挟むことで、「recaptcha」に必要なjsファイルの読み込みや実行がされていなく、うまく機能していない。
解決案
- なんとかページ遷移がされたときに「recaptcha」のjsを動作するようにしたかったが、まだ解決案が見つからず。
- 一時的な回避案としては、「static」ディレクトリにForm画面のhtmlを用意して、そちらを利用。
- Nuxt.js内のページ遷移にするとうまくい表示できないので、Form画面を開くときにはブラウザの別タブで表示するような流れにしてあげることで、一応は表示させたいForm画面が見えるようになった。
- ここについては抜本的な解決案を検討していく必要がある。
受託系システム開発会社がリモートでアジャイル開発したふりかえり
概要
Blogについて
プロジェクトについて
- クライアント社内で利用する業務システムの開発(数年継続して受けている案件)
- クライアントとはロケーションが離れているため、コミュニケーションはGitHubとSlackを利用
- 基本的にはクライアント側で必要な機能や要件をissueベースで作成し、こちらの開発メンバと各々やり取りして進めていく流れ
- 会社的には受託開発がメインとなっているが、以下の理由からSESに近い契約形態としている(常駐はせず、自社で作業)
- 開発が長く続く前提
- 連続した受託開発で発生する契約調整の簡素化(開発のスピード感を落としたくない、要件の優先度変更に柔軟に対応したい)
- メンバを固定した開発効率化(同じメンバで継続的に開発をすることによる生産性の向上)
2018年からの変化
- 2018年の前半はまだ開発メンバも少なく、昔から知っているメンバ3名で進めていた
- 2018年の後半に掛けて、自社の別なロケーションにいるメンバやパートナーさんなどに参画してもらい開発メンバが6名まで増員
- SESに近い契約なので毎月の作業実績工数時間に上限下限があるものの、2018年は可能であれば別な受託系案件を掛け持ちしつつ対応していたが、コンテキストスイッチによる生産性低下を考慮し、このチームで受ける案件は1本に集中するように調整
- 古くから知っている3名のときは何となくできていた開発も、初めて一緒に開発するメンバも増えたことによって、改めてシステム開発と向き合う必要があり、アジャイル、スクラム、エンジニアリングマネジメントとは?というところに立ち返り改善を検討
チーム構成
- クライアントも6名程度のチームで動いているが、そのチームリーダーがPO(プロジェクトオーナー)の役割と実作業(要件定義や設計)を兼務している状態
- 開発チームは6名のチームで対応していて、自分がSM(スクラムマスター)の役割と実作業(設計やコーディング)を兼務している状態
- 開発チーム内だけでもロケーションは分かれていて、自宅からの対応も可能なためリモート作業前提
2018年時点でのプロジェクト背景
- リモートでの作業が基本
- 作業時間もバラバラ(コアタイム的なものは特にない、但し慣習的に一般的な業務時間で仕事をしている)
- タスクに対して、振り返りみたいなものができていない
- 各メンバが独立して作業してる
- プロダクトオーナーは、別会社(チーム)になるが、アジャイルやスクラムには理解がある(但し、こちらで自由に何でもできるわけではなく説明責任は必要)
2019年のアプローチ
スクラム導入
- スクラムの存在は知っているものの、ちゃんと向き合ったことはなく自分を含めて開発チームでどういうものか理解する必要があった。
- 何冊か自分で本を読んでみて、スクラムの全体像が掴みやすく読みやすいと感じたSCRUM BOOT CAMPをチームで読み回し、知識のベースをチームで合わせるようにした。
- いきなりクライアントのチームを巻き込んでスクラム導入するのは難しく、開発チーム内でスクラムの良いと思うイベントや仕組みを導入しつつ、徐々に改善されるように進めた。
チーム定例
- これまでクライアントチームと開発チームの定例はあったが、開発チームのみでの定例は実施していなかった。
- 開発チーム専用のSlackチャンネルは設けていて、当初は何かあればそこでやり取りしましょうとしていた。
- 各メンバからヒアリングすると、Slackで言いにくい雰囲気はなく必要があれば発言しているとは言うものの、実際にface-to-faceで仕事しているときの質問や発言の量と比べると、何か見えない壁があるように感じ、ハングアウト(ディスプレイ越し)にはなるが、週1で開発メンバ内でMTGする場を設けた。
- 実際にやってみたところ、ラフな質問や発言が出やすくなった。Slackで確認するにも、分からないことを分からないなりにテキストでまとめるのにはそれなりに時間が掛かっていて、「もしすぐ分かるなら」みたいな質問ができるようになった。その場で完全な解決まで繋がらなくても、解決のきっかけやヒントに繋がり一人で悩む時間が減った。
- 開発チームには、何かあったからといって人を罵ったりする人はいないが、各々がエンジニアというプライドをもって作業する中で、基本リモートで年何回かしか会わないチームでは心理的安全性がうまく築けていなかったのかなという反省があった。
- 結果やってみて良い試みではあったが、この後にチーム設計というイベントを実施するようになり、そこで色々話す機会が増えたのでチームMTG単体でのイベントは廃止となった。
ふりかえり
- 2018年より前から続いているプロジェクトだが、「ふりかえり」を実施したことはなかった。
- 何か新しいイベントを始めようとすると、誰しもどういう体で参加すればいいか戸惑うこともあり、初回ふりかえりに対して開発メンバは漠然とした不安を感じているように見えた。
- これまでの開発サイクルで考慮しているイベントではないため、どうしても優先度が低くなってしまい、何回かのリスケの後に実現した。
- ふりかえりには「KPT、YWT、Fun/Done/Learn」などの手法を試そうかと考えていたが、いきなりやるにはメンバの気持ち的にハードルが高くなると感じたため、予め明確な質問をリストアップしてそれぞれの思いを話してもらうような流れで実施した。(パネルディスカッションに近い形式だと思う)
- 実際にやってみると、各メンバ間で普段思っている感謝の言葉が出てきたり、クライアントに対してこういうアプローチをすることで仕様調整がうまくいったとか、製造で詰まっているときの解決案だったりと、これまであえて口にすることがなかったものが表面化し絆が深まったように感じた
- 何となくうまくいっているようなプロジェクトでも、以下の理由から定期的に実施した方がいいと思った。
- 改めて言語化して相手に伝えることで、自分の思いを再認識することができる
- 定期的な実施とすることで、各メンバがそれぞれ相手のことを気にかけるようになる(良いところを見つけるような視点を持てる)
チーム設計
- これまではissueが作成されたら開発メンバのアサインを決めて、各々クライアントと話を詰めてスケジュールに落として対応を進めるという流れだった
- このやり方で露呈した問題は、大きく2つあった
- issueが各担当者で閉じてしまい、対応が人依存になりやすい
- 有識者のソースレビュータイミングから戻り作業が生じることがあり、スケジュール管理が厳しい
- 上記の問題解決としてチーム設計を実施する運用を開始した
- issue単位でまずは設計担当をアサインし、修正方針などをまとめた段階でチーム全体で設計レビューをする
- チーム全体で実施することで、どのissueも全員の目に触れることになり、アサインの柔軟な変更が可能となった
- チーム設計のタイミングで方針も合わせているので、レビューからの想定外の戻り作業が抑えれるようになった
- チーム設計が通ったものは、スクラムのストーリーポーカーのように相対見積もりを実施(リモートで実施するため、試しに以下のWebサイトを利用させていただいてます。)
- 相対見積もりは、数値ではなく「XS、S、M、L、XL」から選択するようにした。これまで各々スケジュール作成していたときは、作業に対して何時間掛かるかを見積もっていた。相対見積もりを数値で表現するとどうしても頭の中では作業時間を連想させてしまうため、手が早い人と遅い人の認識をうまく合わせるため抽象度を上げたものを利用した。また、XLになった場合には、対応の分割が可能なものは分割してissueを小分けにするようにしている。
- 相対見積もり後、製造担当者を改めてアサインし、スケジュールは各々にお願いして引くようにしている
かんばん(GitHub)
- GitHubでは各issueにマイルストーンを設定し、どのissueがいつリリースされるものか管理している。
- これまでは、issueの属性(どういうタスクか?)と一部の工程(現在、レビュー中なのかどうかなど)も全てラベルで視覚的に管理していた。
- マイルストーンに入るissueの量が増えてきたことから、マイルストーンの一覧だけではざっくりとした進捗がわかりにくくなり、GitHubのプロジェクトにマイルストーンと同様のものを設定し、かんばんで管理できるようにした。
- 利用することに対してデメリットはなく、単純にもっと早くから利用していればよかったと感じている
1on1
- 開発チームメンバと1on1させてもらうようにした。
- 最初は自社メンバのみに限定して実施していたが、いまは契約形態に関わらずプロジェクトメンバ全員を対象に話す機会をもらうようにしている。人によってばらつきはあるが、1時間/月を目安に実施している。
- 実際にやってみて思うのは、意外に話すことがなさそうかと思っていても、毎月定期的に時間を設けているとお互いの心も開けてきて、回数を重ねるごとに深い話ができると感じている。プロジェクトや会社に対する思いなどは変動するもので、モチベーションや成長意欲(キャリア設計)に対する補正のためには必須のイベントと考えている。普段から顔を合わせて話しているからこそ、改めて時間を作って話せることがある気がする。
- 基本的にはマイクロマネジメントは避けたいと思っているので、はじめる当初は実施すべきか迷うこともあったが、マイクロマネジメントしないために必要なものだと今は考えている。マイクロマネジメントしないためには、意思や価値観を共有する必要があり、そのためにはお互いがどういう思考で行動するのか、何を大切に思っているかなど双方で共有する必要がある。また、それは一度共有すれば永久的に続くものではなく、価値観などは時間とともに変わるので、そのためにも定期的な実施が必要になると考えている。
TDDBC
- 今年参加したイベントで一番面白く、為になったと感じている。
- いまのプロジェクトではUnit Testの文化がなく、これまでも何回か導入しようとチャレンジはしたもののうまくいかなかった。うまくいかないポイントは大きく3つ。
- これまでよりも開発スピードが落ちてしまうのではないかという不安。
- 開発メンバ内でやったほうがいいとは思いつつも、実際に書いていないのでそこのメリットが漠然としている。(結果、やったほうがいいと心から思えていなかった)
- クライアントとしても、Unit Testを導入するコストメリットがはっきりしないので、そこに急にお金を掛けて良いという判断ができない。
- 実際にTDDBCに参加するまではUnit Testを書くことで、単純にその分のコストが増えると考えていた。そのため、クライアントに説明するときにもUnit Testを書くことで発生するコストの増加と、書くことで保守性や障害発生率が下がるコストの減少から導入メリットを説明しなければと思っていた。それが会場でペアプロしつつ実際に手を動かしてみると、これは手を動かしながら設計を進めているということに腹落ちして、これまでの固定概念が崩れた。複雑な機能を作るときに必要な設計手法であり、それをやることで直接コストが高くなることはなく、思考を整理しつつ安全なコードがかけるという感動を覚えた。実際に自分で体験すると、今後のクライアントへの説明の仕方も変わり説得力も増すのではないかと考えている。
- 但し、とてもいいものと分かったからといって、いきなり大きく開発方針を変えるのは難しく、いまは自分ひとりでコツコツできることから始めている。テストコードが増えていくことで、他の開発メンバでも恩恵を感じる機会が増えると考えており、実際に体験すると自ずと書き始めてくれるのではないかと考えている。
2020年の課題
- CI/CDの強化
- 現在CIの方はある程度仕組み化されているが、CDの方はまだ改善の余地がある。リリースサイクルを今よりも早めることを可能にするため、システム無停止で出来る仕組みを用意していきたいと考えている。
- チーム設計前の設計準備のスケジュール管理
- チーム設計をすることで、ある程度想定したスケジュール通りに開発が進めれるようになったものの、チーム設計準備の期間については、うまくスケジュールして進めれていない。要件自体が明確な場合はスケジュール可能だが、クライアントと調整しつつ進めるものや、複雑な機能で製造を進める上で考慮する点が多いものなどは、概算で工数出していてもブレることが多く、このフェーズに関しては進め方を迷っている。
まとめ
monolog(RotatingFileHandler)のログ出力エラーに対してプルリク作成してみた
概要
- Lumenを使ったAPIサーバで日別にログ出力している(日付ごとにディレクトリを分けてログ出力している)
- 数年間のシステム運用をしている中で、特に問題なくログ出力できていた
- ある日、ログ出力できないというエラーが発生したので、調査内容をまとめる
エラーについて
- 「Could not be opened: failed to open stream: No such file or directory in」というエラーが発生する
- エラーメッセージで調べると、ログ出力周りの権限が不正とかばかりで、数年間運用している自分の環境とは要因が別と判断
- 日付が変わった直後のタイミングでエラーが発生する傾向がある
結論
- 日付が変わったときに、新しい日付のディレクトリが存在しなくエラーとなっている。
エラーが発生するケース
原因(コード)
- RotatingFileHandler の write関数内では、出力するログの日時を条件にローテートが必要か判定している。必要な場合には、現在開いているファイルをクローズしてから改めてログ出力処理をするような流れとなっている。このときにクローズするファイルというのは、LumenではHTTPリクエストを受けて対象のControllerを生成し、初めてログ出力するときにファイルが開かれ、HTTPレスポンスが返されるまでは、開いたファイルを利用している。この辺は、〜Handler(今回はRotatingFileHandler)のインスタンスを保持していることで制御している。
- RotatingFileHandler の write関数では、親クラスであるStreamHandler の wriite関数が呼ばれる。StreamHandler の write関数では、初回ログ出力時であれば出力先のディレクトリやファイルを生成し、既にファイルが開かれていれば、そのままファイルにログ出力するという流れになっている。初回ログ出力時にディレクトリを作成するのは、同一クラス内のcreateDir というprivate関数が呼ばれて処理される。createDir関数では、最初に、dirCreatedというクラス変数を条件に既に作成済み(true)であればすぐにreturnするような分岐となっている。このdirCreatedというクラス変数は、privateで宣言されており、trueが入るタイミング(値が変更されるタイミング)は、createDir関数の一番最後のみ。
原因(説明)
- 日付が変わる前にログ出力したときは、RotatingFileHandlerを保持して再利用する仕組みとなっている。(都度、ファイルを開いて書き込んで閉じるだと、パフォーマンスに悪いため、このような実装になっていると思われる)
- ログに書き込むタイミングで、ログ出力時間を見て、ローテートするべきかどうかの判定処理は正常に動いている(RotatingFileHandler#write)
- StreamHandlerのcreateDir関数では、一度ディレクトリが作成されると、dirCreatedというフラグがtrueに変わるため、RotatingFileHandlerを再利用している間は新しいディレクトリが作成できない処理になっている
- RotatingFileHandler#writeでローテートが必要と判断したら、dirCreatedというフラグも下げて、新しい日付のディレクトリを作成できるようにする必要がある
プルリクエスト
- StreamHandler の close関数でdirCreatedのフラグを下げるプルリクを作成してみた。レスなどの反応があると嬉しい。対応としては、dirCreatedをprotectedにして、RotatingFileHandlerから変更するのと迷ったけど、まずは様子見してみたいと思う。
- 英語での説明がかなり自信がないけど、、、
OAuth2対応したPWAをiOSでホーム画面に追加すると認証が厳しい
概要
- 社内アプリをPWAで構築したときのはなし
- 社内の諸々はGoogleアカウントで管理しているので、認証はGoogleのOAuth2を使用
- PWAはNuxt.jsを使って作成し、APIはPHPのLaravel(社内サイトがLaravelを使ってたので流用)を使って作成
- Google OAuth 2.0 認証を使用
結論(感想)
- Nuxt.jsを使うことで、単純なPWA構築はSPAの開発経験者であればいける(キャッシュ機構、バックグラウンド実行、オンラインとオフラインの切替時の制御とか、この辺のナレッジを溜めることでPWA経験者としての優位性が出てくると思う)
- OAuth2を使用する場合、認証画面を開くときに単純なリダイレクト(ブラウザの別タブで表示される)にするよりかは、window.openで認証画面を開き、認証成功時にwindowを自動で閉じるような流れにするほうが、UI/UX的にいい感じな気がする
- OAuth2の認証を使用すると、Androidでは想像通りの動きでいける。
iOSではSafariで動かしているときにはAndroidと同様で問題ないが、ホーム画面に追加してPWAアプリとして起動するとOAuth2の認証が以下の理由で厳しい。 - 以下のfirebaseのGitHubでも同様のissueが上がっているので、もう少し様子見していこうと思う github.com
- 何か迂回策とかあれば教えてほしいです!
Google OAuth2.0のざっくりとしたフロー
- PHPでログイン状態を確認して未ログイン状態であれば、Googleアカウントのログイン画面へリダイレクト
- Googleアカウントで認証後、コールバックURLとして設定した自分のサイトへリダイレクトされる
- 自分のサイト内で、認証情報などを取得して、DBなりCookieなりに保存する
Androidの場合(ケース1)
- 「ホーム画面に追加」したPWAの処理について
- PWAで用意した認証ボタンをクリックしたときに、API側でGoogle認証画面へリダイレクト
- PWAとは別に、通常のChromeブラウザで別タブが開かれて、Google認証画面が表示される
- Chromeブラウザ上で認証したときに、Cookieに認証情報登録
- PWAに戻ると、(同一ドメインで構築の場合)Chromeで登録した認証情報にアクセスできるので、それを使い認証が必要な機能の使用が可能となる
Androidの場合(ケース2)
- Googleの認証画面へリダイレクトするAPI呼び出しをするときに、window.openを使用する
- PWA上でwindowがフルサイズで開き、Google認証画面が表示される
- ケース1とほぼ同じで、認証後はその情報を使い認証が必要な機能の使用が可能となる
- ケース1よりも良い点としては、認証後のタイミングをpostMessageなどを使用することでイベント検知できるので、認証完了したときにwindowを自動で閉じて、認証後の初期ページに遷移など制御をすることが可能
iOS safariの場合
- 「 ホーム画面に追加」したPWAを使用して検証
- PWAからOAuth2を使用するためにGoogle認証画面にリダイレクトすると、iOS safariの別タブとして起動される(window.openを使用してもiOS safariが起動される)
- Androidでは、PWAとChromeブラウザでは、同一ドメインのCookieであればお互い参照することが可能だったが、iOSのPWAとsafariではお互いのCookieにはアクセスできないような制御となっている
- iOSのPWAでは、アプリが起動中でもメインで動いていない場合には、バックグラウンドでの実行ができないため、iOS safariからpostMessageを使って認証後情報を連携しようとしてもできない
- 上記理由から、現時点ではiOSでOAuth2を使いPWAの構築は厳しそう
- もちろん、ホーム画面に追加せず、そのままsafari上ではOAuth2を使った制御も可能なため、解決方法が見つかるまではそれで進めたい
PHPでGuzzleHttpを使用して並列処理(同時実行数の制御)の確認
概要
知りたかったこと
- GuzzleHttpでPoolの使用時に設定するconcurrency(並列処理数)の制御
- 具体的には、全部で10リクエスト投げる処理でconcurrencyに2を設定して実行した場合、以下のどちらになるか調査
試した環境
- lumen: v5.5.2
- GuzzleHttp: 6.3.3(lumen導入時に一緒に入ったもの)
試したサンプルコード
routes/web.php
$router->get('/wait', 'SampleController@wait');
app/Http/Controllers/SampleController.php
- リクエストパラメータの秒数分Sleep後にレスポンスを返す関数
public function wait(Request $request) { $wait = $request->input("sec"); sleep((int)$wait); return "ok" . $wait; }
app/Console/Kernel.phpの$commandsに追加
- API呼び出しをartisanを呼び出すため、実行クラスを追加
protected $commands = [
\App\Console\Commands\Sample\ApiCaller::class,
];
app/Console/Commands/Sample/ApiCaller.php
<?php namespace App\Console\Commands\Sample; use Illuminate\Console\Command; use GuzzleHttp\Pool; use GuzzleHttp\Client; use GuzzleHttp\Psr7\Request; class ApiCaller extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'apicall'; /** * The console command description. * * @var string */ protected $description = 'API呼び出し処理'; /** * API呼び出し処理 */ public function handle() { $this->multiReq(); } private function multiReq() { $uri = [ 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=10', 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=1', ]; $client = new Client(); $requests = function () use ($uri) { foreach ($uri as $u) { yield new Request('GET', $u); } }; $pool = new Pool($client, $requests(), [ 'concurrency' => 2, 'fulfilled' => function ($response, $index) { // this is delivered each successful response \Log::info("成功::index::" . $index); }, 'rejected' => function ($reason, $index) { // this is delivered each failed request \Log::info("失敗::index::" . $index); }, ]); // Initiate the transfers and create a promise $promise = $pool->promise(); // Force the pool of requests to complete. $promise->wait(); } }
実行コマンド
php artisan apicall
結果
- 以下のようなログが出力される
- 2つ目(indexが1)の10秒掛かるリクエストが返されるまでに3つ目のリクエストが投げられなか確認したところ、レスポンス待たずにどんどんリクエストしているので並列処理としては常時2つを保つように動いていることがわかった
[2018-12-05 17:17:34] lumen.INFO: 成功::index::0 [] [] [2018-12-05 17:17:36] lumen.INFO: 成功::index::2 [] [] [2018-12-05 17:17:38] lumen.INFO: 成功::index::3 [] [] [2018-12-05 17:17:40] lumen.INFO: 成功::index::4 [] [] [2018-12-05 17:17:42] lumen.INFO: 成功::index::5 [] [] [2018-12-05 17:17:43] lumen.INFO: 成功::index::1 [] [] [2018-12-05 17:17:44] lumen.INFO: 成功::index::7 [] [] [2018-12-05 17:17:44] lumen.INFO: 成功::index::6 [] [] [2018-12-05 17:17:45] lumen.INFO: 成功::index::8 [] [] [2018-12-05 17:17:45] lumen.INFO: 成功::index::9 [] []