背景
個人的なプロジェクトでTauriを使った画像解析(WaveformやVectorScopeの計算)アプリを作ろうとしていた。
しかし、フロントエンド側(WebView)で画像を表示/更新する度にアプリのメモリ使用量が増え、プロセスが死ぬまで解放されないことに気がついた。
結論としては、「Safari自体が、表示しなくなった画像のメモリを解放していない」というのが原因のようだった。
発生環境
CPU: Apple M1 Pro
OS: macOS 13.4.1 (22F82)
Safari: Version 16.5.1 (18615.2.9.11.7)
再現方法
以下のStackOverflowの回答にあるRun CodeSnipetを連打すると確認できる。
何が問題かって、「同じ内容を再描画するだけでもメモリ使用量がどんどん増える」ってとこ
なぜSafariが悪いと気づいたか(原因調査)
Tauri / WRY
初めはTauriの問題かと思い調査
TauriとWryのGitHubで情報を集め、似たようなメモリリークのIssueを発見
このIssueでは、TauriのAPIでバックエンドから大量のデータを送信する度にメモリ使用量が増えるというものだった。
残念なことに、このIssueはまだOpenであり、根本解決はできていないようだ。
ただコメントにWebViewが悪いという指摘があったりして、役立つ情報は結構あった。
Rust
バックエンドからデータを送信する際に起きる問題であれば、Rust側には問題がないかも調査する必要がある。
通常フロントエンドから呼び出す処理を、バックエンドでループさせてメモリ使用量を確認した。
fn main() {
loop {
create_big_blob();
}
...
}
一秒間におおよそ10MB~20MB程度のBlobが生成される処理スピードだったが、一分間回して250MB程度のメモリ使用量で落ち着くことが確認できた。(ピーク時でも300MBを超えない程度)
つまりRust側ではメモリリークは発生していない。(流石Rust)
Safari
結局これ単体で問題は発生した。
Tauriも使わず、HTMLファイルだけでBase64エンコードした画像を表示するだけでも発生した。
WRYはWebViewを使いGUI表示をするため、MacにおいてはSafariが使われることになる。
Safariのコードを変えることはできないので、JSなどの実装をどうにかして対策するしかなさそう。
10年以上前からある問題?
色々対策のため調査をしていたところ、以下のStackOverflowの質問を見つけた
なんと2011年12月の投稿である。
I have created a webpage that receives base64 encoded bitmaps over a Websocket and then draws them to a canvas. It works perfectly. Except, the browser's (whether Firefox, Chrome, or Safari) memory usage increases with each image and never goes down. So, there must be a memory leak in my code or some other bug.
この人の報告では、画像を描画する度メモリ使用量が増えるが、下がることはないとのこと。
(SafariだけでなくChromeやFirefoxでも発生していたらしい)
実装が悪いようにも見えないけど、なんでこれでリークするんだろうか?
一応Updateで解決したとは言ってるけど、ワークアラウンド対応も面倒そうで困る・・・
疑問
まだGUIについて詳しく無いが、色々調べた結果Safariが悪いという結論に至った。
WebViewとして画像をずっと保持するっていうのは正常な動作なのかもしれないけど、
何GBもキャッシュするのはどうなんだろうか?
そもそもページを切り替えてないからこれはメモリリークとは言わないとかあるかも?
もしこの記事に間違いがあったり、技術的背景がわかる人がいらっしゃったら是非ご教授願います。
2023-07-04追記
WebkitのBug報告としても2010年から上がっていました
しかし、この挙動が仕様として正しいというような回答もあり、やはりそういうものだと諦めるしかないのでしょうか・・・
同様にElectronでもImageを多く読み込むとメモリ使用量が増え続けるという相談も多々見つけられました
WebViewが嫌いになりそう