2025/05/16 08:42 Setenv() isn't threadsafe and even safe Rust didn't save us

やあ、ロボ子。EdgeDBのネットワークI/OコードをRustに移植したら、ARM64 CIランナーでクラッシュしたらしいのじゃ。

それは大変ですね、博士。デッドロックのように見えたとのことですが、実際にはプロセスがクラッシュしていたのですね。

そうなんじゃ。原因は`setenv`関数がマルチスレッド環境で安全じゃなかったことらしいぞ。`openssl-probe`ライブラリが`SSL_CERT_FILE`とか`SSL_CERT_DIR`を設定するために使ってたみたいじゃ。

`openssl-probe`が環境変数を設定するために`setenv`を使用していたのですね。`setenv`がメモリを再配置する際に、別のスレッドが`getenv`を呼び出すと競合状態が発生するとのことですが、具体的にはどのような状況でクラッシュに繋がるのでしょうか?

`setenv`がメモリを再配置している最中に、別のスレッドが`getenv`を呼ぶと、メモリの状態がおかしくなってクラッシュする、というわけじゃな。タイミングが悪いと起こる、なかなか厄介な問題じゃ。

なるほど。ARM64 Linuxでのみ発生したのは、クラッシュの再現に多くの条件が揃う必要があったからなのですね。しかし、なぜ`setenv`がスレッドセーフでないのでしょうか?

`setenv`は、環境変数を格納するメモリ領域を操作する際に、ロックなどの排他制御を行っていないからのじゃ。複数のスレッドが同時に環境変数を変更しようとすると、データ競合が発生して、メモリが破壊される可能性があるんじゃ。

修正として、`reqwest`のバックエンドを`rust-native-tls`/`openssl`から`rustls`に移行したとのことですが、これはどのような効果があるのでしょうか?

`rustls`は、OpenSSLに依存せずにTLSを実装したライブラリじゃ。`openssl-probe`を使わなくなるから、`setenv`の問題を回避できるというわけじゃな。

なるほど、根本的な解決ですね。Rustプロジェクトは環境変数のsetter関数をunsafeにする予定とのことですが、これはどういうことでしょうか?

環境変数のsetter関数をunsafeにすることで、開発者に対して、その関数がスレッドセーフでない可能性があることを明示的に警告するんじゃ。使う場合は注意が必要、ということじゃな。

Glibcプロジェクトも`getenv`にスレッドセーフティを追加する予定とのことですが、これは素晴らしいですね。これにより、より安全なマルチスレッドプログラミングが可能になりますね。

そうじゃな。しかし、今回の件で、環境変数の扱いは意外とデリケートだと分かったのじゃ。気をつけないと、思わぬ落とし穴にはまることがあるぞ。

本当にそうですね。今回のEdgeDBのケースは、私たちエンジニアにとって非常に良い教訓になりますね。

ところでロボ子、`setenv`が危険なら、`unsetenv`も同じように危険だと思うか?

うーん、どうでしょう?`unsetenv`もメモリ操作を行うので、同じように競合状態が発生する可能性があるかもしれません。

正解!…って、実は私もよく分かってないのじゃ!えへへ。
⚠️この記事は生成AIによるコンテンツを含み、ハルシネーションの可能性があります。