Supervisor Tree を Alloy で記述してみた
今ひとつ Alloy を理解できていないので、Erlang や Scala で使う 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'); }
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);
ポイントは下記の通り。
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 を用いる必要があります。
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 Client の Scala 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 記法のように使えます。
突っ込み・添削 大歓迎。