Supervisor Tree を Alloy で記述してみた

今ひとつ Alloy を理解できていないので、ErlangScala で使う Supervisor Tree の仕様を記述

まずは、集合を定義してみる。

abstract sig Actor {
  parent : lone Supervisor
}
sig Worker extends Actor {}
sig Supervisor extends Actor {
  child : Actor
}
  • Worker は、Actor である
  • Supervisor は Actor である
  • Actor は、親として高々1つの Supervisor を持つ
  • Supervisor は、子として0個以上の Actor を持つ

個人的に、1 Application : 1 Supervsior Tree だろうと思っているので、下記を追加する。

one sig RootSupervisor extends Supervisor {}

Actor は、高々一つの親を持つけれど、RootSupervisor は絶対に親を持たないので、下記の制約を追加する。

one sig RootSupervisor extends Supervisor {} {
  no parent
}

ここまでで、Supervisor Tree に登場する集合は、全て定義できた。ここに制約を追加していく。
現状だと、自分自身を親や子にできるので、下記の制約を追加する。

fact parent {
  no (^parent & iden)
}

fact child {
  no (^child & iden)
}

親と子の関係はペアとなるので、下記の制約を追加する。

fact pair {
  parent = ~child
}

RootSupervisor を除く全ての Actor は絶対に親が必要であるため、全ての Actor は RootSupervisor から辿れるという制約を追加する。

fact {
  Actor in RootSupervisor.*child
}

最後に確認用に空の動作を追加して実行する。

pred show () {}
run show

assert や check は勉強中なので、また今度。
最終的には、下記の通り。

module SupervisorTree

abstract sig Actor {
  parent : lone Supervisor
}
sig Worker extends Actor {}
sig Supervisor extends Actor {
  child : Actor
}
one sig RootSupervisor extends Supervisor {} {
  no parent
}

fact parent {
  no (^parent & iden)
}

fact child {
  no (^child & iden)
}

fact pair {
  parent = ~child
}

fact {
  Actor in RootSupervisor.*child
}

pred show () {}
run show

突っ込み大歓迎。

RabbitMQ のクラスタリング機能にキューのミラーリングが追加されたので RabbitFoot (AnyEvent::RabbitMQ) から試してみる

クラスタリングやキューのミラーリングの詳細は、下記参照の事。

RabbitMQ をクラスタリングする

今回はサーバを複数用意できなかったので、一つのサーバ上で RabbitMQ を二つ起動する。

% RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit1 rabbitmq-server
% RABBITMQ_NODE_PORT=5673 RABBITMQ_NODENAME=rabbit2 rabbitmq-server

起動したら、rabbit2 の方を一旦停止(ErlangVM は停止しない。RabbitMQ だけ停止)して、クラスタリングの設定を行ってから再起動する。

% rabbitmqctl -n rabbit2 stop_app
% rabbitmqctl -n rabbit2 reset
% rabbitmqctl -n rabbit2 cluster rabbit1@`hostname -s`
% rabbitmqctl -n rabbit2 start_app

クラスタリング構成が正しく組まれているか確認するには、下記のコマンドを実行する。

% rabbitmqctl -n rabbit1 cluster_status
Cluster status of node rabbit1@ljob04 ...
[{nodes,[{disc,[rabbit1@ljob04]},{ram,[rabbit2@ljob04]}]},
 {running_nodes,[rabbit2@ljob04,rabbit1@ljob04]}]
...done.

ミラーリングされたキューを作る

今回は、Perl Script からキューを作る。

use Coro;
use Net::RabbitFoot;

my $rf = Net::RabbitFoot->new(
    verbose => 1
)->load_xml_spec()->connect(
    host    => 'localhost',
    port    => 5672,
    user    => 'guest',
    pass    => 'guest',
    vhost   => '/',
    timeout => 1,
);

my $ch = $rf->open_channel();

$ch->declare_queue(
    queue     => 'test_q',
    arguments => {
        'x-ha-policy' => 'all',
    },
);

$rf->close();

arguments テーブルを使用しているので AMQP 0-8 から使える。
x-ha-policy に 'all' を指定しているので、クラスタリングされている全てのノード上にキューがミラーリングされる。
x-ha-policy の詳細は、RabbitMQ - Highly Available Queues 参照の事。

キューのミラーリング状態を確認するには、下記のコマンドを実行する。

% rabbitmqctl -n rabbit1 list_queues name messages pid slave_pids synchronised_slave_pids
Listing queues ...
test_q  0       <rabbit1@ljob04.2.5022.0>       [<rabbit2@ljob04.1.4932.0>]     [<rabbit2@ljob04.1.4932.0>]
...done.

上記の場合、マスターとなるキューは rabbit1 上に存在し、コピーが rabbit2 に存在している。

念のため、幾つかメッセージをキューに追加しておく。

use Data::Dumper;
for (1..2) {
    $ch->publish(
        routing_key => 'test_q',
        body        => 'Hello HA Queue.',
        on_return   => unblock_sub {die Dumper(shift)},
    );
}

ノードを停止・起動してみる

キューのマスターが存在している rabbit1 を停止する。

% rabbitmqctl -n rabbit1 stop

状態を確認してみると…

% rabbitmqctl -n rabbit2 list_queues name messages pid slave_pids synchronised_slave_pids
Listing queues ...
test_q  2       <rabbit2@ljob04.1.4932.0>       []      []
...done.

キューのマスターが rabbit2 に移っており、コピーは空になっている。
この状態でキューにメッセージを追加しておく(この時点でメッセージ数 4)。追加の方法は、前述を参照の事。

次に、rabbit1 を起動する。

% RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit1 rabbitmq-server

クラスタリングの設定は保存されているので、これだけで良い。
状態を確認してみると…

% rabbitmqctl -n rabbit2 list_queues name messages pid slave_pids synchronised_slave_pids
Listing queues ...
test_q  4       <rabbit2@ljob04.1.4932.0>       [<rabbit1@ljob04.3.225.0>]      []
...done.

コピーがノード上に作られているが、同期は行われていない。
詳細は、RabbitMQ - Highly Available Queues の Unsynchronised Slaves を参照の事。

この状態で、更にキューにメッセージを追加しておく(この時点でメッセージ数 6)。
ここでメッセージを 4 つ受信すると同期状態となる(1 つずつメッセージを受信した方が解りやすい)。

for (1..4) {
    my $response = $ch->get(queue => 'test_q');
}

感想とか

クラスタリングを構築してもキューがミラーリングされるわけではなかったので、今までは、クラスタリング機能を使わずに運用していた。(2 ノード以上で運用し、1 ノード停止してもシステム全体が止まらないような設計を行っていた)
今後は、積極的にクラスタリング機能を使って行こうと思う。

CCCrypt(Objective-C) で暗号化したデータを Crypt::OpenSSL::AES(perl) で復号化する

iPhone/iPad/iPod で暗号化(AES256 を使用)したデータをサーバ側で復号化するのに半日ハマったのでメモ。

まず、ObjC 側。CCCrypt の解説は方々に存在しているので割愛。IV に NULL を指定しているので 0x00 が 16 Byte という事になる。

CCCrypt(
  kCCEncrypt,
  kCCAlgorithmAES128,
  kCCOptionPKCS7Padding,
  key, keySize,
  NULL,
  data, dataSize,
  buffer, bufferSize,
  &bufferBytes
);

次に、Perl 側。

Crypt::CBC->new(
    -key         => $key,
    -literal_key => 1,
    -cipher      => 'Crypt::OpenSSL::AES',
    -header      => 'none',
    -iv          => pack('C*', map {0x00} (1..16)),
)->decrypt($data);

ポイントは下記の通り。

  • Crypt::CBC を使う
  • literal_key に true を指定しないと key に指定した値の MD5 が KEY として使用される
  • header を none にして IV を明示的に指定する
  • CCCrypt(ObjC)側で IV を NULL にしたなら 0x00 が 16Byte なバイナリ列を指定する

ScalaTest で FixtureFlatSpec を使う場合の疑問点など

STM の動作確認のため、下記のようなコードを書いたのですが…*1

import org.scalatest.fixture.FixtureFlatSpec
import org.scalatest.matchers.ShouldMatchers

import scala.concurrent.stm._

class RefSpec extends FixtureFlatSpec with ShouldMatchers {
  type FixtureParam = (Ref[Int], Ref.View[Int])

  def withFixture(test: OneArgTest) {
    val n = Ref(0)
    val v = n.single
    test((n, v))
  }

  "A Ref.View" should "get the latest value" in ({case (n, v) =>
    v() should equal (0)

    atomic { implicit txn =>
      n() = 1
      v() should equal (1)
    }

    v() should equal (1)
  }: PartialFunction[FixtureParam, Unit])
}

もう少し簡単に、複数の Fixture を Test case に渡せないものでしょうか?
多分、Fixture の為に Inner class を宣言するのが正しいような気もするのですが、何となく Tuple を使いたかったので…。

*1:実際に動作するコードから抜粋したので、動作確認していません

List, Either, Option を for 構文で使う話しの続き(toSeq, toRight, toOption を使いこなしてますか?)

Scala for = Haskell do (Java の例外を Either で包んだ後の話し) が少し好評だったので、調子に乗ってちょいネタ。
for 構文を使うと match case のネストを避けられるけれど、じゃー、下記のように書けるのかと言うと…

for {
  a <- Some(1)
  b <- Right(2).right
} yield a

"type mismatch" と怒られるハズです。何故か?
上記を map と flatMap を使った形式に変換すると下記のようになります。*1

Some(1).flatMap(a => Right(2).right.map(b => a))

Option の flatMap の型は下記の通りであり…

def flatMap [B] (f: (A) ⇒ Option[B]) : Option[B]

Option[B] が求められている箇所に Product with Either[Nothing,Int] with Serializable を与えているので "type mismatch" で怒られます。
では、どうするかと言うと…、下記のように 型 を揃える以外に方法はありません。

for {
  a <- Some(1)
  b <- Right(2).right.toOption
} yield a

当然、下記の for 構文の値は None です。

for {
  a <- Some(1)
  b <- Left(3).right.toOption
} yield a

None ではなく、詳細なエラーの内容が必要な場合は、下記のようにします。

for {
  a <- Some(1).toRight(new Exception("None")).right
  b <- Right(2).right
} yield a

toRight には、left の値を指定します。*2
下記の for 構文の値は Left(new Exception("None")) と等価です。

for {
  a <- None.toRight(new Exception("None")).right
  b <- Right(2).right
} yield a

ちなみに、List には toRight や toOption は存在しないので、Option や RightProjection/LeftProjection の toSeq を用いる必要があります。

*1:for 構文が foreach, map, flatMap, filter の糖衣構文である話しは有名ですよね

*2:逆に、toLeft は right の値を指定します

Mochikit で sendXMLHttpRequest する際に Content-Type を設定しないと、CGI.pm で URL エンコードされた POST データを受け取れない

サーバの入れ替えを行う際に、時間を無駄にしたのでメモ。

旧環境では、下記のコードで問題なく動作していたのだが…

// ..snip..
var r = getXMLHttpRequest();
r.open('POST', CGI_PATH, true);

var args = formContents('form_id');
this.d = sendXMLHttpRequest(r, queryString(args[0], args[1]));
this.d.addCallbacks(
  bind(this.call_back, this),
  bind(this.error_back, this)
);
// ..snip..

新環境では、CGI.pm を new すると "POSTDATA" というキーにごそっと POST したデータが入ってしまう。
CGI.pm のコードを読んでみると…

if ($meth eq 'POST'
    && defined($ENV{'CONTENT_TYPE'})
    && $ENV{'CONTENT_TYPE'} !~ m|^application/x-www-form-urlencoded|
    && $ENV{'CONTENT_TYPE'} !~ m|^multipart/form-data| ) {
    my($param) = 'POSTDATA' ;
    $self->add_parameter($param) ;
  push (@{$self->{$param}},$query_string);
  undef $query_string ;
}

どうやら、好きな Parser を指定できるようにするための措置らしい。
"CGI.pm", "POSTDATA" などのキーワードでググると、古い記事が沢山引っかかるので、相当前からの仕様らしい…。

という事で、setRequestHeader を使って "Content-Type" してみた。

// ..snip..
var r = getXMLHttpRequest();
r.open('POST', CGI_PATH, true);
r.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// ..snip..

FireFox の場合、open の前で setRequestHeader するとエラーとなるので注意。

きっと一般的な Perl Web エンジニアの間では常識なんだろうなぁ orz

Scala for = Haskell do (Java の例外を Either で包んだ後の話し)

最近、こっそりと RabbitMQ Java ClientScala Wrapper を書いています。
Scala から Java のライブラリを使う場合、例外処理の扱いに困るのですが、当然、RabbitMQ Java Client も例外投げまくりです。
そこで scala.util.control.Exception.allCatch を使って例外を包んでみました。*1

  class ConnectionFactory (factory: MQFactory) {
    def connect(): Either[Throwable, Connection] = allCatch either {
      new Connection(factory.newConnection)
    }
  }

  object ConnectionFactory {
    def apply(setter: (ConnectionConfig => Unit)): Either[Throwable, ConnectionFactory] = {
      allCatch either {
        val factory = new MQFactory
        setter(new ConnectionConfig(factory))
        new ConnectionFactory(factory)
      }
    }
  }

コードの詳細はさておき「ConnectionFactory クラスの connect メソッドと、ConnectionFactory オブジェクトの apply メソッドが Either を返している」というのがポイントです。

さて、ここからが本題ですが、これを match case で処理*2しようとすると…

  object Connection {
    def apply(setter: (ConnectionConfig => Unit)): Either[Throwable, Connection] = ConnectionFactory(setter) match {
      case Right(factory) => factory.connect() match {
        case Right(connection) => Right(connection)
        case other => other
      }
      case other => other
    }
  }

こんな感じで match case がネストしまくりな状態に陥ります。
今回のパターンでは、ネストが二段ですが、これが三段・四段と深くなると更に難読化していきます。

そこで、実際に ConnectionFactory を使う所では、下記のようにしています。

  object Connection {
    def apply(setter: (ConnectionConfig => Unit)): Either[Throwable, Connection] = for {
      factory    <- ConnectionFactory(setter).right
      connection <- factory.connect().right
    } yield connection
  }

Scala の for は、Haskell の do 記法のように使えます。

突っ込み・添削 大歓迎。

*1:コードの断片であるため、package や import などが抜けています

*2:この箇所は、デバックしていないので、正しく動作するか不明です