<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Promise</title><link>https://blog.ast.moe/tags/promise/</link><description>Recent posts from 五里霧中</description><generator>Hugo</generator><item><title>BoaでPromiseを保持し忘れてmallocが壊れた</title><link>https://blog.ast.moe/blog/2026-09-10-4/</link><pubDate>Thu, 10 Sep 2026 20:37:07 +0900</pubDate><guid>https://blog.ast.moe/blog/2026-09-10-4/</guid><description>&lt;p&gt;BoaのTest262を回していたら、たまにテストが終了コード134で落ちました。&lt;/p&gt;
&lt;p&gt;ログには、こんなエラーが出ています。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;malloc(): unsorted double linked list corrupted
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Issueは&lt;a href="https://github.com/ieee0824/omoikane/issues/638"&gt;通常Test262のrelease実行がallocator整合性エラーで中断する&lt;/a&gt;です。&lt;/p&gt;
&lt;p&gt;最初は、最近入った変更のどこかでメモリを壊しているのだろうと思いました。ただ、Test262は並列に大量のテストを実行するので、ログだけではどのテストが原因なのか分かりません。&lt;/p&gt;
&lt;h2 id="まずは変更前後を比べる"&gt;まずは変更前後を比べる&lt;/h2&gt;
&lt;p&gt;最初にやったのは、変更前と変更後の比較です。&lt;/p&gt;
&lt;p&gt;Boaの変更前revisionと、問題が見つかったrevisionをそれぞれreleaseビルドして、同じ条件でTest262を実行しました。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Ubuntu x86_64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;release build
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2 MiB worker stack
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;4 workers
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;この条件では、変更前後ともに50,595ケースを最後まで実行できました。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;47,606 passed
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2,056 ignored
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;933 failed
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;0 panic
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;allocatorの異常終了も再現しませんでした。&lt;/p&gt;
&lt;p&gt;実行ファイルのSHA256も一致していたので、少なくともこの実行結果からは、別の変更によって壊れたとは言えません。&lt;/p&gt;
&lt;p&gt;ただ、問題のCIでは2回続けて終了134になっています。再実行が通ったからといって、問題が解決したとは扱えません。&lt;/p&gt;
&lt;h2 id="小さいケースに絞る"&gt;小さいケースに絞る&lt;/h2&gt;
&lt;p&gt;ログを見ていると、落ちる直前には次のようなsuiteが実行されていました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GeneratorFunction/prototype&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;classのasync private method&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;どちらもPromiseやasync処理に関係しています。&lt;/p&gt;
&lt;p&gt;そこで、全Test262を何度も回すのではなく、Promiseの解決処理でGCが動く条件を小さいテストにしました。&lt;/p&gt;
&lt;p&gt;問題になったのは、Promiseの解決中に&lt;code&gt;resolve&lt;/code&gt;や&lt;code&gt;reject&lt;/code&gt;を取り出し、そのあとで&lt;code&gt;then&lt;/code&gt; getterを呼ぶような処理です。getterの実行中には、ユーザーコードが動く可能性があります。そこではGCも発生します。&lt;/p&gt;
&lt;h2 id="promiseへの参照が消えていた"&gt;Promiseへの参照が消えていた&lt;/h2&gt;
&lt;p&gt;Promiseの処理では、内部の共有captureから&lt;code&gt;resolve&lt;/code&gt;や&lt;code&gt;reject&lt;/code&gt;を取り出します。&lt;/p&gt;
&lt;p&gt;このとき、Promiseを追跡可能な参照として保持している場所まで一緒に失われていました。&lt;/p&gt;
&lt;p&gt;通常はすぐに問題が起きません。処理が短く、GCも動かなければ、たまたまメモリ上に残った状態で処理が進むからです。&lt;/p&gt;
&lt;p&gt;しかし、そのあとに&lt;code&gt;then&lt;/code&gt; getterやhost hookを呼び出すと、ユーザーコードが実行されます。その間にGCが走ると、Promiseが不要なオブジェクトだと判断され、回収される可能性があります。&lt;/p&gt;
&lt;p&gt;その状態で後続処理がPromiseを使おうとすると、解放された領域を参照することになります。&lt;/p&gt;
&lt;p&gt;最終的に見えていたのが、allocatorの内部データ構造が壊れたというエラーでした。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;malloc(): unsorted double linked list corrupted
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;エラーがmallocから出ているので、最初はヒープのどこかを直接壊しているように見えます。実際には、GCによる回収と参照の保持が正しく対応していないことが入口でした。&lt;/p&gt;
&lt;h2 id="回帰テストで再現する"&gt;回帰テストで再現する&lt;/h2&gt;
&lt;p&gt;修正前のrevisionに回帰テストだけを追加して実行すると、ARM64 LinuxでSIGSEGVを再現できました。&lt;/p&gt;
&lt;p&gt;このテストは、次のような条件を含めています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;then&lt;/code&gt; getterがcallableを返す&lt;/li&gt;
&lt;li&gt;&lt;code&gt;then&lt;/code&gt; getterがcallableではない値を返す&lt;/li&gt;
&lt;li&gt;&lt;code&gt;then&lt;/code&gt; getterが例外を投げる&lt;/li&gt;
&lt;li&gt;Promise解決中に再入して&lt;code&gt;resolve&lt;/code&gt;や&lt;code&gt;reject&lt;/code&gt;を呼ぶ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;単純にPromiseが解決するだけなら問題が出ないので、getterの呼び出しとGCが入り得る箇所を組み合わせる必要がありました。&lt;/p&gt;</description></item></channel></rss>