既存クラスを拡張(したように見せる)ひとつの方法

Rubyの特異メソッド/特異クラスみたいなことが出来ないのか、と思って調べていたところ、初期段階として「既存のクラスを拡張できるか」という点を掘ってみることになりました。

Scalaはもともと、RichStringというクラスがあり、java.lang.Stringクラスに存在しない便利メソッドが定義されており、必要に応じてRichStringに変換されて呼び出せるようになっています。

scala> "100"
res24: java.lang.String = 100

scala> "100".r
res25: scala.util.matching.Regex = 100

java.lang.Stringには r というメソッドは定義されていません。ここでは暗黙のうちにRichStringクラスへの変換が行われています。どのようなimplicit defが起こり得るか、を実行時にうまく知る方法があればいいのですが、私はよくわかりません。いろいろ実験してみた結果。

scala> val a = "100".r _
<console>:5: error: _ must follow method; cannot follow scala.util.matching.Regex
       val a = "100".r _
                     ^

scala> val a = ("100":RichString).r _
a: () => scala.util.matching.Regex = <function>

いささか明示的に追いかけてみたところですが、前者がエラーになるのが悲しいところです。
後者ではどのクラスに変換されうるかを知っていて、それを明示的に示している例です。実際には "r"メソッドを期待しているということは、何か特定のclass/traitを相手にしていることは間違いないのでさほど問題はないのですが。

さて、上記の例を見ると Stringクラスに rというメソッドを追加/拡張しているように見えます。これと同じことを自分でもやりたい!

$ ruby
class String
  def hoge; 100 end
end

puts "hoge".hoge
^D
100

とかやりたい!という時にどうするか、が今回のテーマです。

いろいろ方法はありそうですが、RichStringの例に倣ってみます。

scala> class HogeString(val self:String) {
     |   def hoge = 100
     | }
defined class HogeString

scala> implicit def stringAsHogeString(src:String):HogeString = new HogeString(src);
stringAsHogeString: (String)HogeString

scala> "fuga"
res1: java.lang.String = fuga

scala> "fuga".hoge
res2: Int = 100

うまくいきましたね!キモは implicit def です。この宣言がされたスコープ内では引数型と関数の戻り値の型の間で暗黙のうちに変換がおこなわれる、という仕掛けのようです。正直、かなりコワい機能でもあると思いますが。

さらに突っ込んで簡単にする方法もあります

scala> implicit def stringToHogeable(src:String) = new { def hoge = 100 }
stringToHogeable: (String)java.lang.Object{def hoge: Int}

scala> "hogehoge".hoge
res0: Int = 100

ここまで来るとかなり激しい感じがしますね。
上記の例では implicit defを解釈した時点で、戻り値となっている無名のクラスが hogeメソッドをもっていることはコンパイル時点で分かっているようです。これならどのimplicit def を呼び出すのかはScalaにはわかるわけですね。

そこで、こんな実験をしてみました

scala> implicit def hogeable(src:String) = new {
     |   println("hogeable.new src="+src);
     |
     |   def hoge = 100
     | }
hogeable: (String)java.lang.Object{def hoge: Int}

scala> implicit def fugable(src:String) = new {
     |   println("fugable.new src="+src);
     |
     |   def fuga = 200
     | }
fugable: (String)java.lang.Object{def fuga: Int}

scala> "abc".hoge
hogeable.new src=abc
hogeable.new src=abc
res0: Int = 100

scala> "def".fuga
fugable.new src=def
fugable.new src=def
res1: Int = 200

scala> "ghi".naiyo
<console>:7: error: value naiyo is not a member of java.lang.String
       "ghi".naiyo
             ^

というわけで、

  • implicit def後も存在しないメソッドを呼び出すようなケースでは、定義済みのimplicit defは試されない (これは当たり前というか実行効率上好都合ですね)
  • 存在しないメソッドを呼ぶ際には、必要な変換に該当するimplicit defただ1つを特定して呼び出す
  • 謎なのが、なぜ上記の例では new が2回走っているのかということ。つまり implicit def は二回評価されているのか...?

という感じになりました。

最後の「なぜ2回呼ばれているのか」はまだわかりません… 識者のコメントがいただけるとハッピーです!

ちなみに無名クラスでなければ一回のようです。

scala> class Test {
     |   println(this);
     |
     |   def piyo = 100
     | }
defined class Test

scala> implicit def toTest(a:String) = new Test
toTest: (String)Test

scala> "hoge".piyo
line5$object$$iw$$iw$Test@10c0fa7
res1: Int = 100

突然oracleプロセスのCPU利用率が100%になってデータベースが固まる事象について

ある時、突然、サーバ上のoracleプロセスのCPU利用率が100%になって、問い合わせはおろかログインもできなくなる…そんな事象に遭遇しました。

前提:うちの環境は8コア(後述) 専用サーバモードで動作/利用していると思ふ

SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle Database 10g Release 10.2.0.3.0 - 64bit Production
PL/SQL Release 10.2.0.3.0 - Production
CORE    10.2.0.3.0      Production
TNS for Linux: Version 10.2.0.3.0 - Production
NLSRTL Version 10.2.0.3.0 - Production

ある時、突然データベースサーバがうんともすんともいわなくなりました。正確には、SQL問い合わせを投げると帰ってこない。ログインもできない。
で、調べてみると、pingsshなどは普通に通ります。topコマンドその他を確認してみると、CPU利用率のうち「あるCPU(コア)のみがusertime 100%になっている」ことが解りました。wait%はほぼゼロ。その他のコアの利用率は数%程度、といたって平穏です。

なのに、他のコアに割り当てられているはずのoracleプロセス群の問い合わせもすべて通らない。IO待ちではないことも明らか。どうやらoracleの中で「固まって」いるようです。

結局、待機系にfail-overしてこの時は事なきを得た(ことなきってことはないか)。で、結果としては以下のようなものでした。

結論:「Oracle10gのバグ ただしPSRは公開済み」

要するに

  • PSR10.2.0.4あてればよろしい

もう少しまとめると

  • PSR10.2.0.4 では修正されている。それ以前(うちの10.2.0.3とか)では発生する
  • STATSPACKを利用/仕掛けていて、statspack snapをレベル6以上で取得しているとだめ。
    • レベル5に設定していれば出ないらしい
    • "実行計画の取得"がレベル6以上では実施されてしまう。で、バグの発生条件に触れる。
  • 固まる理由は "library cache latch"の保持しっぱなし
  • 保持しっぱなしの理由はOracle内での利用メモリの枯渇・・らしい
  • 利用メモリの枯渇の理由は、どうやら「メモリリーク」らしいが細かい説明割愛

同種の問題に遭遇しているかもしれないあなたへ、

  • trcファイルとかで MMONの情報で
  memory info: free memory = 0.00M

とか

  swap info:   free = 0.00M alloc = 0.00M total = 0.00M

とか出ている場合はあやしいです

なんかいまいち情報がオープンにされていないのですが、 PSR10.2.0.4を適用していなくて、STATSPACK利用している人は遭遇する可能性がありますので、エントリとしてまとめておきました。