カテゴリ: ruby

最近Rubyの著名デベロッパのakrさんがAPIデザインケーススタディという書籍を出版された。隙を見ては読んでいるのだけどこれが激烈に面白い。
役にも立たない一般原則とかいっさいすっ飛ばし、実例実例、また実例である。そしてすべての例がOSの制限やら過去との互換性やらいろいろな都合でトレードオフを勘案しながら取りうる選択肢のなかでのベストを見つけていくという題材になっているので話がどれもこれもが超面白い

みんな買おう。


それはさておき、一点だけちょっと微妙な記述をみつけたのでメモっておく。

P128からはじまる Column async-signal-safe関数 だけど
また、実際のところ、子プロセスでasync-signal-safeでない関数を使用す
るのは、珍しいことではありませんでした。たとえば、ネットワークサー
バの作り方の一つとして、クライアントからの接続のたびにforkを行い、
子プロセスでクライアントと通信する形態があります。この場合、子プロ
セスはexecしないので、POSIX的にはasync-signal-safeな関数しか使えませ
ん。しかし、実際にはさまざまな関数がかなり好き勝手に使われます。
 そのようなプログラムをサポートするため、多くのUnixではシングルス
レッドプログラムからforkで子プロセスを作った場合、async-signal-safeで
ない関数も問題なく動作するようになっています。ただし、その動作は
POSIXに反しているため、将来的にも動作が保証されるかはわかりません。

と書いてある(強調は筆者)んだけど、Open Group(現代ではPOSIXと同義)のforkのページを見ると(強調は筆者)
A process shall be created with a single thread. If a multi-threaded process calls fork(), the new process shall contain a replica of the calling thread and its entire address space, possibly including the states of mutexes and other resources. Consequently, to avoid errors, the child process may only execute async-signal-safe operations until such time as one of the exec functions is called. 
なので、async-signal-safeの制限があるのは規格上も multi thread なプログラムだけである。わたしが規格をなにか見落としていないかぎり。




今日、なるせさんが




とかつぶやいていて、意味がわからないので突っ込んだところ。svn ポッキー問題と呼ばれる大きな問題が隠れている事がわかった(詳細はコチラ http://togetter.com/li/706683) ので、コミット時に ChangeLogの日付を自動更新する git hookを書いた。これで自動的にChangeLogの時刻がコミット時時刻に一致する。

ソースはこちら。これを .git/hooks/pre-commit というファイル名で保存し、実行可能権限をつける

いまさら確認するまでもないが、Rubyコミッタは全員 git svn を使っていて、コミット時にはなるせさんの
コミットメッセージ自動整形スクリプト(http://d.hatena.ne.jp/nurse/20100413)を使ってる。
異論はみとめない(キリッ

さて、これはこれで大変いいものであるのだが、脊髄反射的に git commit -av <リターン> とか打ってることがあって、他のプロジェクトと行ったり来たりしていると今ひとつなところがあった。
そういうわけで、上記URLのスクリプトをgit hook に移植して、git commit -a したら自動的にChangeLogからコミットメッセージを生成するようにしてみた

以下のスクリプトを、/.git/hooks/prepare-commit-msg というファイル名でセーブして、実行権限をつけるとよい。


意外と簡単だった

https://gist.github.com/kosaki/9864928

require 'mail'

Mail.defaults do
options = { :address => "smtp.gmail.com",
:port => 587,
:user_name => 'kosaki.motohiro',
:password => 'パスワード',
:authentication => 'plain',
:enable_starttls_auto => true }

delivery_method :smtp, options
end

mail = Mail.new do
from 'kosaki.motohiro@gmail.com'
to 'kosaki.motohiro@gmail.com'
subject 'ruby mail test'
body 'body'
end

#puts mail.to_s #=> "From: mikel@test.lindsaar.net\r\nTo: you@...
mail.deliver




このへん参考にした
stackoverflow: How to send email via smtp with Ruby's mail gem?
http://stackoverflow.com/questions/12884711/how-to-send-email-via-smtp-with-rubys-mail-gem

https://groups.google.com/forum/#!topic/fluentd/dXc4weNEdCg

# New core committer

Motohiro Kosaki is now a Fluentd Committer.
Welcome!!

He is a Linux Kernel and Ruby committer, so
his experience and knowledge are very helpful for Fluentd and Fluentd community :)
We can improve and grow the Fluentd more quickly!


Enjoy Fluentd!



なにやら、Motohiro Kosakiという New Committerがやたらと持ち上げられてるが、
わたしの知っているこさきさんとはキャラクターが大きく異なるのできっと別人だろう。

この記事は Ruby の Advent Calendar に参加しようとして用意しましたが、間に合わなかったものです。
Ruby 2.0でdtrace対応が入りました。この機能はLinuxのsystemtapからもアクセス出来ます。でも、どこにもドキュメントがないのでいっちょ使い方を解説しようという、そういう趣旨の記事です。

まず、基本の使い方ですが、以下の様に

process(プロセス名).provider("ruby").mark(Rubyで定義されてるprobe名) で引っ掛けるイベント名を
記述して、tapscriptを記述します

ruby.stp
probe process("./ruby").provider("ruby").mark("find__require__entry")
{
printf("%s %s %d\n", user_string($arg1), user_string($arg2), $arg3)
}


test.rb
require "thread"


んで、 sudo stap TAPスクリプト -c 任意のコマンド として動かします。
(コマンドは全体を""で括らないとおかしくなるので注意)

$ sudo stap ../ruby.stp -c "./ruby --disable-gems ../test.rb"
enc/encdb.so ./ruby 0
enc/encdb.so 3
enc/trans/transdb.so 3
rubygems.rb 1
rbconfig /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 98
rubygems/compatibility /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 105
rubygems/defaults /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 107
rubygems/deprecate /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 108
rubygems/errors /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 109
rubygems/specification /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 1076
rubygems/version /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/specification.rb 39
rubygems/requirement /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/specification.rb 40
rubygems/version /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/requirement.rb 11
rubygems/deprecate /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/requirement.rb 12
rubygems/platform /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/specification.rb 41
rubygems/deprecate /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/platform.rb 1
rubygems/deprecate /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/specification.rb 42
rubygems/exceptions /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 1079
rubygems/defaults/operating_system /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 1088
rubygems/defaults/ruby /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 1097
rubygems/core_ext/kernel_gem /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 1107
rubygems/core_ext/kernel_require /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 1108
thread /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb 45



おおー。見事に preludeコードから暗黙で呼ばれているencdb, transdbも含めてrequire関係がとれましたね。

では、使用出来るprobeはどこを見ればいいかというと、見るべきところは2つあって、1つはrubyソースコードのprobe.d、なにせコンパイル時に実際に使ってる定義だから嘘がない。もう1つは RubyのBTSにある wiki情報で簡単な解説つき。http://bugs.ruby-lang.org/projects/ruby/wiki/DTraceProbes

さて、ここでこの記事を終わらせてしまってもいいのですが、もうちょっと頑張りましょう。このままではちょっと使いにくいですよね。

まず、すぐ気づくダメな点は probe.d に引数の情報がないので、見ても参考にならなさすぎなのです。こんな感じ

https://github.com/ruby/ruby/blob/afb02bbe92e55f877d50ed8705c95a41d541458d/probes.d

#include "vm_opts.h"

provider ruby {
probe method__entry(const char *, const char *, const char *, int);
probe method__return(const char *, const char *, const char *, int);

probe cmethod__entry(const char *, const char *, const char *, int);
probe cmethod__return(const char *, const char *, const char *, int);

probe require__entry(const char *, const char *, int);
probe require__return(const char *);

probe find__require__entry(const char *, const char *, int);
probe find__require__return(const char *, const char *, int);

probe load__entry(const char *, const char *, int);
probe load__return(const char *);

probe raise(const char *, const char *, int);

probe object__create(const char *, const char *, int);
probe array__create(long, const char *, int);
probe hash__create(long, const char *, int);
probe string__create(long, const char *, int);

probe parse__begin(const char *, int);
probe parse__end(const char *, int);

#if VM_COLLECT_USAGE_DETAILS
probe insn(const char *);
probe insn__operand(const char *, const char *);
#endif

probe gc__mark__begin();
probe gc__mark__end();
probe gc__sweep__begin();
probe gc__sweep__end();
};

#pragma D attributes Stable/Evolving/Common provider ruby provider
#pragma D attributes Stable/Evolving/Common provider ruby module
#pragma D attributes Stable/Evolving/Common provider ruby function
#pragma D attributes Evolving/Evolving/Common provider ruby name
#pragma D attributes Evolving/Evolving/Common provider ruby args


実はDTraceの仕様としては引数名は書けるけども無視されるという仕様なので、書いてしまいましょう。
こんな感じ。

#include "vm_opts.h"

provider ruby {
probe method__entry(const char *classname, const char *methodname, const char *sourcefile, int sourceline);
probe method__return(const char *classname, const char *methodname, const char *sourcefile, int sourceline);

probe cmethod__entry(const char *classname, const char *methodname, const char *sourcefile, int sourceline);
probe cmethod__return(const char *classname, const char *methodname, const char *sourcefile, int sourceline);

probe require__entry(const char *filename, const char *sourcefile, int sourceline);
probe require__return(const char *filename);

probe find__require__entry(const char *filename, const char *sourcefile, int sourceline);
probe find__require__return(const char *filename, const char *sourcefile, int sourceline);

probe load__entry(const char *filename, const char *sourcefile, int sourceline);
probe load__return(const char *filename);

probe raise(const char *classname, const char *sourcefile, int sourceline);

probe object__create(const char *classname, const char *sourcefile, int sourceline);
probe array__create(long capacity, const char *sourcefile, int sourceline);
probe hash__create(long size, const char *sourcefile, int sourceline);
probe string__create(long length, const char *sourcefile, int sourceline);

probe parse__begin(const char *sourcefile, int sourceline);
probe parse__end(const char *sourcefile, int sourceline);

#if VM_COLLECT_USAGE_DETAILS
probe insn(const char *insns_name);
probe insn__operand(const char *val, const char *insns_name);
#endif

probe gc__mark__begin();
probe gc__mark__end();
probe gc__sweep__begin();
probe gc__sweep__end();
};

#pragma D attributes Stable/Evolving/Common provider ruby provider
#pragma D attributes Stable/Evolving/Common provider ruby module
#pragma D attributes Stable/Evolving/Common provider ruby function
#pragma D attributes Evolving/Evolving/Common provider ruby name
#pragma D attributes Evolving/Evolving/Common provider ruby args


github にもアップしといた。

https://github.com/kosaki/ruby/blob/b93717702e3db5d9a6c900afbb61422ef4970f89/probes.d

んで、ここまであれば、systemtapのライブラリ(tapsetといいます)が自動生成できます。こんな感じ

#!/usr/bin/ruby
# -*- coding: us-ascii -*-

def set_argument (arg, nth)
# remove C style type info
arg.gsub!(/.+ (.+)/, '\1')

# use user_string if arg is a pointer
if (arg[0,1] == "*")
"#{arg[1, 9999]} = user_string($arg#{nth})"
else
"#{arg} = $arg#{nth}"
end
end

ruby_path = "./ruby"

text = ARGF.read

# remove preprocessor directives
text.gsub!(/^#.*$/, '')

# remove provider name
text.gsub!(/^provider ruby \{/, "")
text.gsub!(/^\};/, "")

# probename()
text.gsub!(/probe (.+)\( *\);/) {
probe_name = $1
probe = <<-End
probe #{probe_name} = process("ruby").provider("ruby").mark("#{probe_name}")
{
}
End
}

# probename(arg1)
text.gsub!(/ *probe (.+)\(([^,)]+)\);/) {
probe_name = $1
arg1 = $2

probe = <<-End
probe #{probe_name} = process("ruby").provider("ruby").mark("#{probe_name}")
{
#{set_argument(arg1, 1)}
}
End
}

# probename(arg1, arg2)
text.gsub!(/ *probe (.+)\(([^,)]+),([^,)]+)\);/) {
probe_name = $1
arg1 = $2
arg2 = $3

probe = <<-End
probe #{probe_name} = process("#{ruby_path}").provider("ruby").mark("#{probe_name}")
{
#{set_argument(arg1, 1)}
#{set_argument(arg2, 2)}
}
End
}

# probename(arg1, arg2, arg3)
text.gsub!(/ *probe (.+)\(([^,)]+),([^,)]+),([^,)]+)\);/) {
probe_name = $1
arg1 = $2
arg2 = $3
arg3 = $4

probe = <<-End
probe #{probe_name} = process("#{ruby_path}").provider("ruby").mark("#{probe_name}")
{
#{set_argument(arg1, 1)}
#{set_argument(arg2, 2)}
#{set_argument(arg3, 3)}
}
End
}

# probename(arg1, arg2, arg3, arg4)
text.gsub!(/ *probe (.+)\(([^,)]+),([^,)]+),([^,)]+),([^,)]+)\);/) {
probe_name = $1
arg1 = $2
arg2 = $3
arg3 = $4
arg4 = $5

probe = <<-End
probe #{probe_name} = process("#{ruby_path}").provider("ruby").mark("#{probe_name}")
{
#{set_argument(arg1, 1)}
#{set_argument(arg2, 2)}
#{set_argument(arg3, 3)}
#{set_argument(arg4, 4)}
}
End
}

print text


おなじくgithubにUpしといた

https://github.com/kosaki/ruby/commit/0d2653e849d5352d8287e97bce053c07f6e84384

これで以下のようなライブラリが生成される

$ tool/gen_ruby_tapset.rb probes.d > ./ruby-tapset.stp



ruby-tapset.stp

probe method__entry = process("./ruby").provider("ruby").mark("method__entry")
{
classname = user_string($arg1)
methodname = user_string($arg2)
sourcefile = user_string($arg3)
sourceline = $arg4
}

probe method__return = process("./ruby").provider("ruby").mark("method__return")
{
classname = user_string($arg1)
methodname = user_string($arg2)
sourcefile = user_string($arg3)
sourceline = $arg4
}


probe cmethod__entry = process("./ruby").provider("ruby").mark("cmethod__entry")
{
classname = user_string($arg1)
methodname = user_string($arg2)
sourcefile = user_string($arg3)
sourceline = $arg4
}

probe cmethod__return = process("./ruby").provider("ruby").mark("cmethod__return")
{
classname = user_string($arg1)
methodname = user_string($arg2)
sourcefile = user_string($arg3)
sourceline = $arg4
}


probe require__entry = process("./ruby").provider("ruby").mark("require__entry")
{
filename = user_string($arg1)
sourcefile = user_string($arg2)
sourceline = $arg3
}

probe require__return = process("ruby").provider("ruby").mark("require__return")
{
filename = user_string($arg1)
}


probe find__require__entry = process("./ruby").provider("ruby").mark("find__require__entry")
{
filename = user_string($arg1)
sourcefile = user_string($arg2)
sourceline = $arg3
}

probe find__require__return = process("./ruby").provider("ruby").mark("find__require__return")
{
filename = user_string($arg1)
sourcefile = user_string($arg2)
sourceline = $arg3
}


probe load__entry = process("./ruby").provider("ruby").mark("load__entry")
{
filename = user_string($arg1)
sourcefile = user_string($arg2)
sourceline = $arg3
}

probe load__return = process("ruby").provider("ruby").mark("load__return")
{
filename = user_string($arg1)
}


probe raise = process("./ruby").provider("ruby").mark("raise")
{
classname = user_string($arg1)
sourcefile = user_string($arg2)
sourceline = $arg3
}


probe object__create = process("./ruby").provider("ruby").mark("object__create")
{
classname = user_string($arg1)
sourcefile = user_string($arg2)
sourceline = $arg3
}

probe array__create = process("./ruby").provider("ruby").mark("array__create")
{
capacity = $arg1
sourcefile = user_string($arg2)
sourceline = $arg3
}

probe hash__create = process("./ruby").provider("ruby").mark("hash__create")
{
size = $arg1
sourcefile = user_string($arg2)
sourceline = $arg3
}

probe string__create = process("./ruby").provider("ruby").mark("string__create")
{
length = $arg1
sourcefile = user_string($arg2)
sourceline = $arg3
}


probe parse__begin = process("./ruby").provider("ruby").mark("parse__begin")
{
sourcefile = user_string($arg1)
sourceline = $arg2
}

probe parse__end = process("./ruby").provider("ruby").mark("parse__end")
{
sourcefile = user_string($arg1)
sourceline = $arg2
}



probe insn = process("ruby").provider("ruby").mark("insn")
{
insns_name = user_string($arg1)
}

probe insn__operand = process("./ruby").provider("ruby").mark("insn__operand")
{
val = user_string($arg1)
insns_name = user_string($arg2)
}



probe gc__mark__begin = process("ruby").provider("ruby").mark("gc__mark__begin")
{
}

probe gc__mark__end = process("ruby").provider("ruby").mark("gc__mark__end")
{
}

probe gc__sweep__begin = process("ruby").provider("ruby").mark("gc__sweep__begin")
{
}

probe gc__sweep__end = process("ruby").provider("ruby").mark("gc__sweep__end")
{
}


$arg1 みたいなくそ使いにくい引数を probes.d の記述にもとづいて sourcefile とか sourcelineとか
いった変数に変換してくれる。これによって最初のスクリプトは以下のようにも書けるようになる

sudo stap -I. ../ruby2.stp -c "./ruby ../test.rb"



-I はさきほど作った ruby-tapset.stp があるディレクトリを指定すること。SystemTapのデフォルトサーチパス
(普通は /usr/share/systemtap/tapset/とかそのへん)に入れてしまえば不要だけどおすすめしない。

ruby2.stp

probe find__require__entry {
printf ("%s %s %d\n", filename, sourcefile, sourceline)
}


probe名からプロセス名とプロバイダ名が消え、引数アクセスもuser_string($arg1) といった記述が
消えているのが分かるだろうか。

言い換えると、最初の例が2.0での書き方、最後の例が2.1での書き方(の候補として検討中のもの)というわけである。

おしまい

まつもとゆきひろ名言集。

http://bugs.ruby-lang.org/projects/ruby/wiki/DevelopersMeeting20121210

[16:08:17] And I want to make sure as long as I live on this mortal
state, you need my approval before adding something as official
Ruby
[16:08:30] lol
[16:08:34] and don't try to kill me ;-)

ぼくが生きてる間は、Rubyの言語仕様に何かを加える前に僕の承認を得る必要があるよ。
でも、だからといって僕を殺そうとはしないでね

なんか以下の方法でビルドできるらしい。メモ

https://groups.google.com/forum/#!topic/rubyinstaller/A92K_EYJX2A/discussion


Hello,

Since I started RubyInstaller 4 years ago, ensuring that Ruby worked on every
single release took time.

Back then, my hardware was limited, so running Ruby tests took a considerable
amount of time (more than one cup of tea using tea cups scale).

Not only that, even after 4 years and different versions of Ruby and Windows,
Ruby still have some failures:

Finished tests in 526.522392s, 21.2128 tests/s, 3665.2629 assertions/s.
11169 tests, 1929843 assertions, 9 failures, 2 errors, 80 skips

Wouldn't be awesome that Ruby own tests were all green on Windows?

The truth is that having Ruby achieve `0F0E` (0 failures, 0 errors) will help
detect real errors, not to mention automate the testing process, perhaps
allowing teams have *nightly builds* of Ruby to play with.

Following a similar idea from Ruby 1.9.3 Documentation Challenge [1], I would
like to propose some test-fixing hunt!

## Objective

Reach 0 failures and 0 errors for Windows (MinGW) in the upcoming weeks, is
not that hard: 9 failures and 2 errors.

The target is Ruby **trunk** (upcoming 2.0), but I expect these fixes be
backported to Ruby 1.9.3 too.

## I have no idea how to compile things...

I heard *I would like to help but have no idea how to compile Ruby*, and I
know is not easy, so that is why I present to you the **Ruby Challenge Kit**

Those of you who are well versed in the art of compiling can skip to the next
section (Compiling).

### Ruby Challenge Pack

The Ruby Challenge Pack is a single-file package that provides all the tools
require to compile, test and fix Ruby. Once extracted, it provides:

- Ruby 1.9.3-p194 (required as base to compile Ruby).
- 32bits DevKit GCC 4.6.1 (based on TDM builds).
- 32bits support libraries (OpenSSL, zlib, libffi, libyaml, etc).
- Easy 1-click batch to start a tailored Command Prompt
- Easy 1-step batch to compile Ruby and run basic tests
- A document (README) describing how compile and send your fixes.

What is not included and you need is:

- A working Git installation on your computer (recommended is msysGit).
- A clone of Ruby source from GitHub [2]

Download here:
http://cdn.rubyinstaller.org/archives/experimental/RubyChallengePack-v1.exe

MD5 for verification:
0e6259c256beb5fba7e7ec53bd9e5c21

After download of the package, extract into a directory *without spaces*
(e.g. C:\RubyChallenge) and check the included README for instructions (or
keep reading)

## Compiling

For those using the Ruby Challenge Pack, simply run `quick-compile.bat`
as indicated in the documentation.

For others, do what you do and always verify Ruby core tests pass:

make test

Compiling will take long, depending on your system. You can go and grab
a cup of tea while it completes.

## Running and fixing tests

Once you've compiled Ruby, you can run the tests:

make test-all TESTS="-q"

Above command will run **all** Ruby tests, which could take around 10
minutes.

Once it completes, you should get a list that only contains the errors. For
example, let's look at the following one:

[ 3348/11169] TestEnv#test_win32_blocksize = 0.00 s
4) Failure:
test_win32_blocksize(TestEnv)
[C:/Users/Worker/Code/ruby/ruby/test/ruby/test_env.rb:404]:
Errno::EINVAL expected but nothing was raised.

Notice the file where the test failed: `test/ruby/test_env.rb` and the line
too.

Now open your editor/IDE, find that file, open it, and locate the failing
test.

Figure out if the test is valid and Ruby is failing or is test that is not
meant to be run on Windows.

At any time, you can email RubyInstaller group [3] and ask questions,
everybody will be pleased to help you out.

Once you find a solution, modify the file and run the tests again, but this
time just for a single file:

make test-all TESTS="-q ruby/test_env.rb"

## Sending your patches

You fixed a test, great! Now is time to send this to Ruby developers so they
can apply it.

Open a ticket on Ruby-trunk [4] tracker and assign it to me (luislavena).
I'll review and merge accordingly.

To reduce changes people stepping into each other, please email RubyInstaller
group [3] before to announce or see if others are working on that particular
failure.

## A big thank you

There is no such thing as small contributions, all contributions are valuable.

Help us make a better Ruby.

Thank you.
--

[1] http://blog.segment7.net/2011/05/09/ruby-1-9-3-documentation-challenge
[2] http://github.com/ruby/ruby
[3] https://groups.google.com/forum/?fromgroups#!forum/rubyinstaller
[4] http://bugs.ruby-lang.org/projects/ruby-trunk/issues/new


↑このページのトップヘ