どうも、最近はインフラエンジニアを目指しているayuayuyuです。
今回は、2年前のハッカソンで開発をしていたときに起きた「奇妙なバグ」について話そうと思います。滅多に遭遇しない現象かもしれませんが、もし同じような沼にハマってしまった人がいたとき、解決の手助けになればと思って書いています。
あのハッカソンでは、Server-Sent Events(SSE)を使ってリアルタイム通信を行う対戦型プログラミングゲームを作りました。SSEはWebSocketやWebRTCみたいなリアルタイム通信の一種なんですが、双方向通信というよりは「あることがトリガーとなって、サーバーから接続しているクライアントにリクエスト(データ)が送れる」というものです。
開発したのは、楽しく学びながら成長できるゲームです。プログラミングの基本プロセスである「仕様を読む」「コードを読む」「コードを消す」「コードを書く」を順に体験しながら、実践的なスキルを身に付けることを目的としています。
プレイヤー同士には同じコードと仕様が渡され、4つのターンでゲームが進行します。仕様とコードを読むターン、わざとコードを動かなくするターン、互いに壊したコードを交換して修正するターン、そして最後に話し合いのターンです。
これらは制限時間制で、両プレイヤーがリアルタイムに同じターンを進行する必要があったため、リアルタイム通信が必須でした。そこでSSEを使い、互いにターン終了のリクエストをサーバーに送り、両方から受け取ったサーバーがSSEで「次のターンに行く合図」を出して、ゲームが同期して進行するようにしました。
僕はバックエンドを担当していたんですが、実装が終わったので、試しにデプロイして友達と別々のPCでフロントエンドを立ち上げてテストしてみました。結果はバッチリで、ちゃんと動いたので「これで完成だ!」と思っていました。
でもそのあと休憩していると、チームメンバーが「自分のPC1台だけで試したら同期がうまくいかない」とバグを見つけてきたんです。制限時間がすぐに終了してターンが一瞬でスキップされ、あっという間にゲームが終わってしまうという謎の現象でした。
バックエンド担当の自分が変なミスをしたんだと思い、必死にコードを見直しましたが、どこをどう見ても原因がわかりません。発表は翌日に迫っていて、時間はすでに24時越え。朝早く新幹線で東京に向かわないといけないという、超ギリギリの状態でした。
焦りに焦って、気づけば時間は深夜2時。眠たい目をこすりながら頭をフル回転させ、「なぜ友達と試しにやった時はできたのか?」を必死に考えました。そこでふと、今までみたいに同じChromeで画面を分割して試すのではなく、試しに「Chrome」と「Firefox」を開いて通信させてみたんです。
そしたら……なんと、うまくいきました。
ついに原因を特定しました。犯人は「時間の管理にローカルストレージを使っていたこと」でした。
ローカルストレージで時間を管理していたせいで、同じブラウザの別タブ(別ウィンドウ)だとデータが共有されてしまい、それぞれの時間が0.1秒などのわずかな誤差で競合して、タイマーがバグっていたことが判明したんです。友達と別々のPCでやった時は、当然ローカルストレージも別なので問題なかったんですね。
ローカルストレージに頼ること自体は全然いいんですが、「1台のPCで通信テストをする時は、互いに違うブラウザを使わないといけない」という、ものすごく身になる教訓を得たハッカソンでした。