Money Forward Developers Blog

株式会社マネーフォワード公式開発者向けブログです。技術や開発手法、イベント登壇などを発信します。サービスに関するご質問は、各サービス窓口までご連絡ください。

20230215130734

最近のruby-core (2016年4月)

こんにちは。卜部です。

ruby-coreというRuby本体の開発の議論がされているメーリングリストがあります。

新機能やバグ報告などがだいたいここに集約されてくるので購読しておくとRubyの動きが分かります。

最近興味深かったトピックを紹介します。先月長いと言われたので今回はちょっと厳選気味です。

[#12241] super end

4月といえばエイプリルフールですね。今年は end end end ... とか書かなくてもいいようにするパッチが投稿されていました。もちろん冗談です。しかしこの投稿はだた「できるといいな」じゃなくて実際に実装してみせているので、技術的には見るものがある。PythonとかHaskellとかみたいなインデントでブロック表現するやつだと、改行が暗黙のsuper endになってるわけですね。

以前、Rubyのバグトラッカーにはこの種の提案のためのカテゴリjokeがわざわざ用意されていて、歴代のエイプリルフールなどが閲覧できたんですが、いつのまにかカテゴリなくなっちゃいましたね。やや残念を感じます。

[#12240] circle number arguments

今年の4/1その2。ブロック引数名をiとかjとかつけるの面倒なので、デフォルトですでに変数に代入されてると便利なはずだという意見は(普段から)よく出てくるので、 とか とかいう変数を導入したよという提案です。もちろん冗談。ただ⑳まで実装したという提案に対して、Unicodeには㊿まであるので中途半端ではないかというコメントが付いていて、これは提案者が負けという感じです。

書いてて思ったけどジョークを解説するとか無粋にも程がありますね…

[#12244] Add a way to integer - integer % num

これは、もうちょっと言い方を変えると、より一般化した(10進じゃない)floorが欲しいという話です。10進じゃないfloorというのはすごく具体的な例でいうと、MySQLでcreated_atで86,400秒単位のpartitioningしている場合、とかそういうやつですよ。需要分かりますよね。これは機能としては誰も反対はない気がします。

ただ提案されてる実装だと現状のfloorより10倍遅くなるというのは…このままだと厳しそうかなあ。

[#12225] Remove inline assemblers and always enables USE_MACHINE_REGS

前回、「最近のコンパイラは賢い」と解説したこのチケットですが、Microsoft Visual C++ 2013以前でさほど賢くない感じでコンパイルされてしまうことが発覚、採用されずになってしまいました。

なんでそんな古いコンパイラに対応しなきゃならんのか、という話ですが、残念ながらMicrosoftさんはVisual C++のバージョンを上げるたびにmsvcrなんとか.dllとかvcruntimeなんとか.dllとかいうファイルを都度、しかも時にバイナリ互換性のない形で、破壊していかれるわけですよ。そんな中でも2015の時の変更は非互換がとても大きく、これまでRubyの側では対応できない状態になっていたのです。

この話は月内に他のチケットで進展がありました。下に続く。

[#12235] URI.encode issue with square brackets

この連載ではUnicode関連部分に闇ぽい現象が観測されるのはお気づきかと思いますが、実はUnicode以外にURIも結構その、あれです。

この報告者の意見としてはURI.escapeが http://example.com/resource[1].doc といったURIをRFCに準拠した形でエスケープしてくれないのはバグ、という主張です。まあ、たしかにRFCに準拠しないのはその通りです。

しかしながら、そもそもURIというのは構造を持った文字列で、構造のどこの部分かで使える文字が違う。なので「エスケープ」と一言で言っても、この部分だとこれ、みたいなルールが構造の中でそれぞれに違うわけです。なのにエスケープする必要があるということは、そもそもの入力の文字列そのものはURIとしてはぶっ壊れてる(ぶっ壊れてるからこそエスケープする必要がある)わけですよね。つまり文字列の中に構造を見つけることができない。できないのに構造がないとエスケープできない。

つまりこれは詰んでるわけです。URIを「エスケープ」するという発想そのものが実は無理筋なのです。URI.escapeは公式見解としてobsoletedです。

と、いうところまでが前史なんですけども、それはじゃあそういうことだとして、この http://example.com/resource[1].doc という文字列を、たとえばWebブラウザのアドレスバーに突っ込んでみると、どういう挙動になるんだろうか? という疑問が湧いてくるのは人情というものですよね。で、やってみると、案外どのブラウザもエスケープとかしない…つまり、Rubyの今の挙動は実情としてはブラウザと一緒なんじゃないの、という。RFCとはなんだったのか。疑問がふつふつと湧いてくるわけですね。

というわけでこの件はWeb Hypertext Application Technology Working Groupに「どーなってんの」と報告されています。続きはWHATWGで進行することと思います。

[#12236] Introduce mmap managed heap

内部構造に大きな変更を入れる提案です。

いま、メモリの中でオブジェクトが16KBのかたまりでmalloc(正確にはposix_memalign(3posix))されているのですが。これは逆に言うと、確保した16KBが全部GCされるまでシステムに戻されないということでもあります。ずっと昔はもっと大きくて3MBとかだったわけですが、それだと大きすぎてなかなかメモリが解放されないということがあり、16KBというかなり小さいサイズに小分けにされた経緯があるのですが、今度は小分けの領域を管理するコストが結構馬鹿にならないよねという問題意識があります。

この提案ではそこを一気に256MBの連続したメモリアドレスをmmapすることで解決しようとしています。mmapのいいところは一発で確保したアドレス空間を小分けにOSに返していくことができるという点で、これはmallocがfreeと必ず1対1で対応している必要があるのとは違う特徴です。このため、小分けの領域を管理するコスト、とかは削減できる一方で、なかなかメモリ返せない問題は4KB単位でちょっとでも空いたらすぐOSに返すことができる、一挙両得という提案になっています。

ただ明らかな問題として256MBもメモリ確保できるのかというのが常にはそうと限らないわけで、そのへんどうするのか未解決です。

[#12273] Time.parse incorrectly parses Russian months

「Timeがロシア語の月名に対応してません」「してませんね。そうですね」心温まるスレッド。

[#11547] remove top-level constant lookup

Rubyの定数参照は結構複雑なルールになっていて、その複雑なルールに従っていくと Foo::Bar という定数参照式はFooの継承関係を遡ることもあるため、ずっと見つからないと最終的にObject::Barまで遡って、これはようするにトップレベル定数::Barなわけですね。しかしちょっと待ってください、常識的に考えて、わざわざFoo::Barと書いたのに、修飾しない::Barが取れるというのは期待した動作でしょうか? 何が嬉しいの?

という議論をしたところ、とりあえずこれはトップレベル定数までは遡らないようにしようという話になりました。

[#12274] accessing to instance variable should be fast.

急に来た。インスタンス変数へのアクセスを高速化したよというパッチです。

これですが、そもそもRubyのインスタンス変数はいまの実装だと、オブジェクトの中に配列のようにパックされて詰まっています(中とも限らないがまあ配列ではある)。一方で、プログラム上は当然、変数名でアクセスするわけですから、名前から配列のインデックスに変換する解釈系が必要になっているわけです。ちなみにRubyのインスタンス変数というものは実行中にどんどん増えたりする(減るのはないけど)ため、静的な変換エンジンなどというものはありえません。

で、とはいえ常識的に使ってればインスタンス変数なんざそうどんどん増えていったりしないわけでして、その解釈を一発やったらあとでまた使う時用に取っとこうよ、という発想はまあ自然かと思います。なので処理中にインラインでキャッシュする仕組みがあって、それ自体は実装済みです。

この提案は、今のキャッシュ機構に問題があって、本来ならすでに取得できているはずのキャッシュをうまく見れてないんじゃないかという点の修正です。ある程度大きなメソッドになってくると、同じメソッド内で同じインスタンス変数に複数回触りに行くことがありえるのですが、そういう場合のキャッシュを(これまでなってなかったけど)一つにまとめた、ということのようです。

[#12277] Coding rule: colum number

弱視者対応をどうするか問題。ソースコードの横幅を、いまどき80文字とかないでしょ、とばかりに横に長いコードを書いていた人に対して、自分は目が弱くて横にあんまり長いと辛いんだ、という反論が来ているところです。

辛い人と辛くない人がいるなら辛い人が辛くなくなるようにするのが基本的には良いことだと思います。ただし、教条的にやってしまうと意味不明のところでぶった切ってインデントする人が出てきそうだし、Cでインデントというとごく最近でもやらかした会社 (CVE-2014-1266)がある程度には微妙な領域なので、これはやや慎重になるのもやむをえないところです。

個人的な意見ですが、方向としては横幅の制限を入れつつ、無理のある改行にならないようにしていくといった折り合いがつくといいかなと思っています。

[#9569] SecureRandom should try /dev/urandom first

SecureRandomという標準添付ライブラリがあって、だいたいの場合でOpenSSLを呼ぶようになっているのですが。それはそれとして、OpenSSLがないときには回り回って /dev/urandom というデバイスを読もうと試みる(こともある)ように現在はなっています。

この部分に問題があって(という主張で)、ユーザーランドの乱数生成器よりはカーネルで乱数作ったほうがエントロピーをじかに吸い出せるぶん安全(暗号的な意味で)であり、したがって乱数生成は常にurandomを使うべきで、OpenSSLよりも先にurandomを試して、なければOpenSSLにフォールバックすべきというふうに主張されているのです。

ここまでは別に問題ないというか、傾聴に値する意見かと思うのですが、問題は、Linuxのurandom(4)に、「(urandomは)暗号論的擬似乱数生成器のために非常に高品質なごく少量の種を提供するものであって、その読み出しは非常に経済的になされる必要がある」と、たいへんに強い口調で書いてあるわけです。

実際には、Linuxの実装を読む限りにおいては、そんな制限はなくて、理解が正しければエントロピーが読んでる途中で枯渇してもちゃんとセキュアな感じのバイト列が返って来そうに読めるんですが。ただ問題はそういうふうに見えるからといって、実際にmanに「やめなさい」と書いてある行為に及んでいいのか、というところで。

現在の開発側の見解としては、本当にそういうのOKという意図があるならドキュメントの更新を待ってもいいのではないかということ、スレッドで騒いでる人達の見解としてはurandomには実際に問題がない以上urandomを使うべきということ、見事に平行線で、たぶんお互いに「話が通じない」と思っていて、やや険悪な雰囲気になっています。

時を同じくしてLKMLで /dev/random - a new approach なる乱数生成器書き直しスレッドが盛り上がっており(LKML updates誰か頼む)、ひょっとしたらそもそもの状況が数年後にはドラスティックに変わっているかもしれませんね。

[#12124] Use Automake

Automake移行しようというこの件、以前の回でWindows対応の問題があることをお伝えしていたかと思うのですが、動きがあって、なんと投稿者はRedmondのVisual Studioチームにこの件を要求、(Visual Studioでの)autotoolsのサポートに非常にポジティブな反応を得たとのこと。これはすごい。またnmakeをMITライセンスか何かで公開するかも、とも書いてあります。

この投稿の時点でまだBuild 2016の前だったため、「/bin/shがないとconfigureは動かないのでは?」という素朴な疑問は当然生じていたわけですが、今から考えるとそのへんは回避されるのかなー本気かもなーという感じです。

いずれにせよ、現状まだなにも確かなことは言えませんが、今後の動きは要注目ですね。

[#12020] Documenting Ruby memory model

前回も紹介したこの件は引き続き議論が進んでいます。

投稿しているのが全員並列プログラミングの専門家で、自分はそうではないため、かなり勘違いをしている可能性もあるんですが、おおまかな論点としては、提案者は並列実行のための新たな抽象化をRubyに入れたいモチベーションがあるようですね。当然、そのような実装をするにはshared-everythingで厳密なメモリモデルが定義されていなければならないという話になるのはそうだろうと思いました。「このメモリモデルは専門家が使うためのものであって一般の読み物としては適さないだろう」と言ってます。

とはいえ厳密なメモリモデルを実装するというのがそもそもあからさまにとても難しいため、定義するのはさておき可能かという面で疑問が提示されていること、及び「新たな抽象化」っていうのが現在の(難しいなりにそれなりに知見がある)スレッドを使ったやつと比べてどのくらい困難な話なのかに関して、まったく知見がなくて、本当に現実的にあり得るのかという疑問が提示されているように読めます。

一家言ある人はそれぞれの意見を表明しあった感じでしょうか。どういう結論になるのかわかりませんが、大きな方向性を決める話だと思うので注目していきたいです。

[#12307] File.new and File.open change permissions even if the file exists on Windows

Windows系の環境で存在するファイルに向けてopenすると、そのファイルのモードが変わってしまうことがある、という指摘です。MSVCRTの実装しているopenにおいて、書き込みモードで開きつつも書き込みビットを落としているというなかなかレアなケースでは、関数呼び出しは成功してファイルに書き込めるディスクリプタが返ってくるけど、モード自体は書き込み禁止に変更されてしまって、次回以降は書けなくなるという話ですね。

が、それはそうなんですが、問題になっているのは「それって禁止されてないよね」という話で。POSIXのopenの仕様を読む限りにおいてはopenに第三引数をつけて呼んだ時にその引数を尊重すべきか無視すべきかあまり決まってないのですよ。もちろんファイルが存在しなかった時にO_CREATはモードを見ると書いてあるけど、ファイルが存在した時にモード変えちゃいかんとか書いてないわけです。

ファイルのオープンのような操作はランタイム依存がどうしても発生する箇所で、MSVCRTがそう作ってある以上はしょうがない、回避不能だし処理系依存というしかないということになったようです。

[#11118] Unable to build Ruby with Visual Studio 2015 RC

上でも言及した通り最近のVisual Studioでコンパイル(正確にはリンク)できなくなってしまっていたわけですが、そもそもMSVCRTの何がそんなに問題だったかというと、IOを統一的に扱う部分なわけです。Ruby(に限らずだいたいどこでもそうだと思うんですが)ではIO多重化ということをやっています。これは、なんでかというと、そういうことをしないと読み込めないファイルを読んだ時にブロックしてしまうし書き込めないファイルに書こうとした時にブロックしてしまうからで、ブロックするのはたいへん困って、そういう操作をキャンセルできるようになってほしい。なのでブロックしそうな操作というものはブロックしないことが確実になるまで実際の操作を行わないということが行われているわけです。

こういうブロックしそうな操作というのはUnix系のOSでは(ちょっと例外あるけど)おおむねの場合でファイルディスクリプタとして抽象化されているため、fdを統一的に扱えればそれでだいたい事足りるんですが、残念なことに歴史的な経緯からWindowsではソケットに対するIOがfd経由じゃないんですな。型が。なので統一的に扱いづらいのです。じゃあWindowsでは普通はどうするかというと最近はCancelIoExというやつがあって、IOを発行した後からキャンセルするということが可能になっています。けれども、残念ながらこいつは割と最近(Vista以降)に実装されたものなので、もっと古いXPとかが今ほど積極的に魔女狩りにあっていなかった時代には使いづらかった。

そこをなんとかするために過去のRubyではソケットを開いた時に一緒に"NUL"CreateFileしておいて、このHANDLEからたどれる構造体の中でソケットのディスクリプタを無理やり上書きするという行為に及んでいました(もう少し詳細な解説)。なので、そりゃあランタイムが変わると動かなくなるよねという感じですよね。

この件に関してはすでにMicrosoft Connectに問い合わせ済みなのですが、先方からは「俺達が用意したAPIだけ使ってりゃ問題ないよ。XP? 捨てなさい」みたいな「ですよねー」としか言いようがない返答が返ってきており、まあ、どうにもならんなという感じです。

で、しょうがないのでこの件はDLLを開いて、_isattyのちょっと後ろの辺に対象の構造体がいるはずだ!とmemcmpで検索して発見するという、なかなかひどいリバースエンジニアリングで解決されました。プルリクエストにThis is not intended to merge って書いてあるからもっとソフィスティケートさせていくのかと思ったらそのまま突っ込まれてた…

[#12283] Obsolete ChangeLog and commit message in Git-style

ChangeLogを捨てようという提案です。RubyではChangeLogというファイルが伝統的にメンテナンスされてきていました。これはまだgitやsvnはおろかcvsすら使ってなかった時代からの伝統で、いろいろなバージョン管理システムを渡り歩いた中で守られてきた「いつ何をやったかリスト」です。

…という説明をしなければならないあたりに時代の流れを感じるわけですが、昔はどのプロジェクトでもやってたんですよ。ただ最近も続けているのは少ない。なんでかというとこのファイルは1コミットごとに毎回編集されることからも分かる通り、ものすごい勢いでマージコンフリクトするんですね。ChangeLogそのものはコミットの本質ではない部分なのに、そこでコンフリクトするからマージできないというのはストレスがたまる行為です。

それと、指摘されているのは、ChangeLogは「何月何日、今日はこのファイルをいじった」みたいな日記形式になっているわけですが(Logとはそもそも日記のことである)、これが夏休みの絵日記みたいな中身のない記述を促進しているのではないか、記録されるべきなのは何をしたかではなくてその背後にある意図なのではないか、という点で、その部分でChangeLogは弱いというふうに主張されています。

とりあえず機能的な面でChangeLog相当のものをsvnのコミットログから生成することは可能なので、ChangeLogを手で書く必要はないよねという意見に反対の人はいないようです。ただ、ChangeLogの形式で一覧するのに慣れている人もいるので、移行パスをどのようにするかが問題なのではと思います。

[#12324] Support OpenSSL 1.1.0 (and drop support for 0.9.6/0.9.7)

この報告があるまで知らなかったんですが、OpenSSL 1.1.0で多くの新機能というか非互換が入るんですね。詳細

なかでも興味深いと個人的に思うのはSSL_CTX_set_security_level というものが導入されていることですね! これをセットするとたとえば短すぎる鍵を使えなくなるとか、古いバージョンのTLSが拒否されるようになるとか、そういうのが入るらしいです。つまりこれまで「おすすめ」と言われながらもなぜかデフォルトに入ってなかった「安全な設定」がとりあえず提供されるようになったと理解しています。

いい話なんじゃないでしょうか。4月末時点でだれも反応してないのが気がかりですが…

[#6647] Exceptions raised in threads should be logged

Rubyはマルチスレッドプログラムが書けるのですが、例外機構もあります。そこで、スレッドの中から例外が上がってきたらどうなるのかという問題があります。

どこか他のスレッドから上がってきた例外でメインスレッドが停止してしまうように設定すると、ようするに本当にコントロール不可能な任意のタイミングでメインスレッドが横から止められてしまうことになって、これは基本的には安全なプログラミングというものがほぼ不可能になってしまいます。なので普段はそういうことはしません。

しかしながら逆に、スレッドの中でいつのまにか例外が一番上まで突き抜けてて、スレッドの処理が止まってしまっているという時に、それが他から非破壊的に観測できずに、「なんかよくわからんけど、動いてなさそうなスレッド」がいつのまにかできてしまうというのも困った挙動です。

というわけでとりあえず観測できないのは困るから例外が出たことをなにかに出力してくれという提案になっています。このニーズの部分に反対する人はあまりいません。

とはいえたとえばログを出力するにしてもIOというのはようするに副作用ですから、たとえばそのログ出力のせいでIOが詰まったりしちゃったらメインスレッドにも影響はあるわけです。なので出力しつつも完全に影響を排除するというのも難しい。

何年も議論が続いていて、今のところはスレッド単位で例外のことをなにか吐くかどうか制御できるようにしよう、という提案になっています。が、それをデフォルトで有効にするかどうかがまだ決まっていません。

[#12306] Implement String #blank? #present? and improve #strip and family to handle unicode

多くのRubyユーザーがblank?というメソッドを使ったことがあると思うのですが、あれはライブラリのメソッドで、本体に含まれているわけではありません。それを本体に入れて欲しいという提案です。

同様の提案は以前にもなんども行われなんどもリジェクトされてきましたが、今回新しいのは対象をStringに限っていてスコープを狭めている(==着地点が明確で議論が発散しづらい)こと、それから議論の中で「String#blank?がなくて困ってるケースが、ほらこんなにあるじゃないですか」とばかりに実在のコードを示してきているところです。

その示しているユースケースが本当にblank?で解決するのかという点に議論があるようですが、ともあれこれまでとは一線を画す感じで議論が建設的に進行しているので、ひょっとしたらこれは入るかもなと感じさせてくれます。

最後に

マネーフォワードでは、ruby-coreが気になって夜も眠れないエンジニアを募集しています。 ご応募お待ちしています。

【採用サイト】 ■マネーフォワード採用サイトWantedly | マネーフォワード

【プロダクト一覧】 ■家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 iPhone,iPad家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 Androidクラウド型会計ソフト『MFクラウド会計』クラウド型請求書管理ソフト『MFクラウド請求書』クラウド型給与計算ソフト『MFクラウド給与』経費精算システム『MFクラウド経費』消込ソフト・システム『MFクラウド消込』マイナンバー対応『MFクラウドマイナンバー』創業支援トータルサービス『MFクラウド創業支援サービス』お金に関する正しい知識やお得な情報を発信するウェブメディア『マネトク!』