ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

Perlbrew で同じバージョンの Perl を別名で複数インストールする方法

Perl のバージョン管理ツール Perlbrew で、既にインストールしている Perl のバージョンを別名で新規にインストールしたい場合、--as オプションを使うことで別名を付けられる。 (いつもわからなくなって id:aereal 氏に教えてもらってばかりいるのでいい加減書き残しておく。)

perlbrew install perl-5.8.8 --as perl-hoge

perlbrew --help すればすぐ下のような情報が出てくるんだけど、何故かいつも --help を見ずにぐぐっていい情報が出てこなくて悩む、みたいなことをしてる。

--as        Install the given version of perl by a name.
            ex. C<perlbrew install perl-5.6.2 --as legacy-perl>

最近の Perl のバージョン管理

最近は plenvPerl のバージョン管理をするのが便利そうという情報も得た。

Perl においてサブルーチンがどのパッケージで定義されているのかを調べる方法

Perl で、とあるパッケージの中で使用できるサブルーチン (あるいはメソッド、関数、CODE ref) がどこで定義されているのかわからないということがたまにあります。 use を使って別のパッケージのサブルーチンをインポートした場合などがその最たる例でしょう。

Devel::Sub::Which パッケージを使用してサブルーチンの定義場所を調べる

CPAN モジュールの Devel::Sub::Which を使用すると、指定したサブルーチンが定義されたパッケージがわかります。

use Devel::Sub::Which qw( which );

# AAA::BBB::CCC パッケージで使用できる createXXX というサブルーチンがどこで定義されているのか調べる
warn which( 'AAA::BBB::CCC', 'createXXX' );

# CODE ref が定義されたパッケージも調べることができる
my $codeXXX = sub {
};
warn which( $codeXXX );

便利!

Kyoto.pm Tech Talks 02 で WebSocket のことを話しました

2012 年 8 月 18 日に開催された Kyoto.pm Tech Talks 02 で WebSocket のことを LT してきました。

Plack::Middleware::WebSocket

Plack で WebSocket プロトコルを扱うための Middleware を motemen さんが 2 年ほど前に書かれていた のですが、それから WebSocket プロトコルの仕様も色々変わっていてそのままだと使えなかったので、4 月ごろに RFC 6455 に適合するように書き換えました。

今回の発表では、これを使ったデモも行いました。 急いで作ったのであまり綺麗なコードじゃないですが、下記リポジトリ (20120818-Kyoto.pm-2/master ブランチ) に置いてありますので、興味があれば実行してみてください。

下記コマンドで clone できます。

$ git clone -b 20120818-Kyoto.pm-2/master git://github.com/nobuoka/presentation.git

今は Plack::Middleware::WebSocket の中に、WebSocket プロトコルのパース処理なども書いているのですが、発表後に 「Protocol::WebSocket って CPAN モジュールがあるよ」 って motemen さんに教えてもらって、「確かに Plack::Middleware::WebSocket にパースとかの細かい処理を書くのは微妙だしそれを使って書き直した方が良いかなー」 と思ったりしました。

evernote-sdk-perl で Thrift::TException が発生する原因

Evernote Cloud API を使用するためには Evernote が提供するライブラリを使用する必要があります。 (Evernote Cloud API の詳細については 公式ドキュメント をご覧ください。) Perl 用のライブラリは evernote-sdk-perl です。 内部的には Thrift が使われているものの、それなりにラッピングされているので、Thrift であることをほとんど意識せずに使うことができます。

それはいいのですが、HTTP 通信周りでエラーが発生した場合には原因がなんであれ Thrift::TException が送出されてしまうため原因の特定が困難である、という問題があります。 今日もそれではまってしまったので、Thrift::TException が発生する原因としてよく遭遇しそうなものを 2 つあげておきます。

その 1 : LWP::Protocol::https モジュールがインストールされていない

Thrift モジュールの内部では LWP::UserAgent モジュールが使われています。 もし、LWP::UserAgent モジュールがインストールされていない状態で evernote-sdk-perl ライブラリを使用すると、「LWP::UserAgent がインストールされていない」 というようなエラーが出るので、LWP::UserAgetnt をインストールすればよいということがわかります。

で、cpanm などで LWP::UserAgent モジュールをインストールすると思うのですが、そのとき普通は LWP::Protocol::https モジュールはインストールされません。 しかし、Evernote との通信には HTTPS が使われるので、LWP::Protocol::https モジュールも必要となります。

LWP::Protocol::https モジュールがインストールされていないと、以下のようなオブジェクトが例外として送出されます。 わかりにくいですね。

bless( {
    'code' => 0,
    'message' => 'Missing version identifier'
}, 'Thrift::TException' )

その 2 : タイムアウトの発生

EDAMUserStore::UserStore オブジェクトを得るためには以下のようなコードを書くと思います。

my $evernote_host = 'sandbox.evernote.com';
my $user_store_url = 'https://' . $evernote_host . '/edam/user';

my $user_store_client = Thrift::HttpClient->new( $user_store_url );
my $user_store_prot = Thrift::BinaryProtocol->new( $user_store_client );
my $user_store = EDAMUserStore::UserStoreClient->new( $user_store_prot, $user_store_prot );

このとき、Thrift 内部で行われる HTTP 通信のタイムアウト時間は、デフォルトで 100 ms となっています。 (Thrift::HttpClient の $self->{sendTimeout} の値。 Thrift::HttpClient#flush メソッド内で LWP::UserAgent->new に渡される。) 100 ms というタイムアウト時間だとかなり短いですので、環境によってはタイムアウトばかりしてしまいます。 また、タイムアウトするときもあればタイムアウトしないときもある、というような現象も起こりえます。

タイムアウト時にも、上で書いたのと同じ Thrift::TException 例外が送出されます。 これも原因を見つけるのが困難なエラーのひとつだと思います。

このエラーを避けるためには、以下のようにそれなりに長いタイムアウト時間を設定すれば良いでしょう。 setSendTimeout で設定されたタイムアウト時間は、上で書いたように LWP::UserAgent->new に渡されて使用されます。 setRecvTimeout で設定されたタイムアウト時間は、現在のところ使用されている様子はありませんが、念のため設定しておくと良いかと思います。

my $evernote_host = 'sandbox.evernote.com';
my $user_store_url = 'https://' . $evernote_host . '/edam/user';

my $user_store_client = Thrift::HttpClient->new( $user_store_url );
# default timeout value may be too short
$user_store_client->setSendTimeout( 10000 );
$user_store_client->setRecvTimeout( 10000 );
my $user_store_prot = Thrift::BinaryProtocol->new( $user_store_client );
my $user_store = EDAMUserStore::UserStoreClient->new( $user_store_prot, $user_store_prot );

その他の原因

基本的にはネットワーク周りに問題がある場合に、Thrift::TException が発生するのかなーと思います。 もうちょっとわかりやすい例外が送出されればうれしいのですけどねぇ。

perlbrew を使って Ubuntu 12.04 に perl 環境を構築する

Ubuntu 12.04perl 環境を構築しようと思って作業していたのですが、perlbrew を使って perl のインストールをしようとしたところ以下のようなエラーが発生してビルドに失敗してしまいました。

(略)

<math.h> found.

Checking to see if your libm supports _LIB_VERSION...
No, it does not (probably harmless)

(略)

cc -fstack-protector -L/usr/local/lib -o miniperl \
    gv.o toke.o perly.o pad.o regcomp.o dump.o util.o mg.o reentr.o mro.o keywords.o hv.o av.o run.o pp_hot.o sv.o pp.o scope.o pp_ctl.o pp_sys.o doop.o doio.o regexec.o utf8.o taint.o deb.o universal.o globals.o perlio.o perlapi.o numeric.o mathoms.o locale.o pp_pack.o pp_sort.o   \
    miniperlmain.o opmini.o perlmini.o~
pp.o: In function `Perl_pp_pow':
pp.c:(.text+0x364e): undefined reference to `pow'
pp.o: In function `Perl_pp_modulo':
pp.c:(.text+0x4415): undefined reference to `floor'
pp.c:(.text+0x45af): undefined reference to `floor'
(略)
collect2: ld はステータス 1 で終了しました
make: *** [miniperl] エラー 1

cc -fstack-protector -L/usr/local/lib -o miniperl \
    gv.o toke.o perly.o pad.o regcomp.o dump.o util.o mg.o reentr.o mro.o keywords.o hv.o av.o run.o pp_hot.o sv.o pp.o scope.o pp_ctl.o pp_sys.o doop.o doio.o regexec.o utf8.o taint.o deb.o universal.o globals.o perlio.o perlapi.o numeric.o mathoms.o locale.o pp_pack.o pp_sort.o   \
    miniperlmain.o opmini.o perlmini.o~
pp.o: In function `Perl_pp_pow':
pp.c:(.text+0x364e): undefined reference to `pow'
pp.o: In function `Perl_pp_modulo':
pp.c:(.text+0x4415): undefined reference to `floor'
(略)
collect2: ld はステータス 1 で終了しました
make: *** [miniperl] エラー 1

どうも libm のリンクがうまくいってない感じがするなー、ということでそっち方向で検索をかけてみたもののあまりいい情報が得られず途方に暮れていたのが先週の話。 さっきなんとなく 「perlbrew Ubuntu」 で検索してみたら色々情報がありました。

Ubuntu Nasty から libm などの位置が変わったが perlbrew の Configure がデフォルトで見に行くパスにそれが含まれていない、ってことが原因のようです。 なので、perlbrew コマンドのオプションとして、libm が入っているパスを指定すれば良い、と。 うちの環境では /usr/lib/x86_64-linux-gnu に libm が入っているので、以下のようにパスを指定してやることで無事ビルドできました。

$ perlbrew install perl-5.14.2 -Dplibpth=/usr/lib/x86_64-linux-gnu

ライブラリの位置の確認方法など、詳細は上に挙げたページをご覧ください。

perl 環境構築の流れ (メモ)

Ubuntu 12.04 に perl 環境を構築する時の流れをメモしておきます。 まずは perl のビルドに必要なパッケージを apt-get でインストール。

$ sudo apt-get build-dep perl

続いて perlbrew をインストール。 詳細は http://perlbrew.pl/

$ curl -kL http://install.perlbrew.pl | bash

インストール後 ~/.bashrc に以下の 2 行を追加。 この後で端末を新しく開く (とか source コマンドで読み込みなおすとかする) と perlbrew を使えるようになっているはずです。

PATH=$HOME/perl5/perlbrew/bin:$PATH
[[ -s "$HOME/perl5/perlbrew/etc/bashrc" ]] && source "$HOME/perl5/perlbrew/etc/bashrc"

あとは perlbrew を使って cpanm をインストールしたり perl をインストールしたり。

# cpanm のインストール
$ perlbrew install-cpanm

# インストール可能なバージョン一覧を表示
$ perlbrew available
# インストール (上で書いたようにライブラリパスをオプションで指定; 以下は 64-bit の場合)
$ perlbrew install perl-5.14.2 -Dplibpth=/usr/lib/x86_64-linux-gnu
# 32-bit の場合 /usr/lib/i386-linux-gnu