2008年5月アーカイブ

chmとは

chmはWindowsで使われているヘルプファイルの形式です。
chmを使うとローカルでマニュアルが参照できて、検索もできるのでとても便利です。
僕がWindowsを使ってて感じる唯一のメリットなんですが、Macでもchmが見れるようにしてみました。

Chmox

Macで使えるchmビューワ。
検索すると必ず出てくる。
確かにChmoxでchmは見れるようになるのですが、このアプリ検索ができないんですよね。
検索ができないとわざわざchmを使うメリットがないので、Chmoxは見送り。

他のビューワも探したんですが、あんまりいいのがなかった。
Macではそんなに需要がないようで。

xchm

X用に作られたchmビューワ。
これは検索もできてバッチぐ〜〜〜。
インストールはMacportsを使えば簡単にできます。

% sudo port install xchm

X関係のパッケージが芋づる式に大量にインストールされるので、インストールにかなり時間がかかりましたが、無事にインストール終了。
表示もバッチリだし、検索もできて大満足です。

日本語のヘルプは文字化け

日本語のヘルプファイルを表示させてみましたが、文字化けしました。
単にLANGの設定が悪いのかもしれないけど、当面は困ることはないので特に対策はしない。

メッセージキューとは

異なる処理の間でキューを用いてメッセージ交換を行う仕組みのことです。
生産者(キューを登録する)側は単純にキューに対してメッセージを追加します。
消費者(キューを消費する)側は単純にキューからメッセージを取り出して処理を行います。

PerlだとTheShwartzやGearmanが有名。
身近なところではcodereposでTheShwartzが使われています

PHPでやるには

akkyさんがJavaのActiveMQを使う方法を紹介してくれてます。

秋元@サイボウズラボ・プログラマー・ブログ: PHPでメッセージキューを使う

pseudoQueue作った

もっと簡単にできないかなと思ってpseudoQueueというクラスを作ってみました。

pseudoQueue

pseudoQueueの特徴

  • 必要なのはsqliteのみ(ほとんどの環境で動く)
  • PHP4でもPHP5でも動く
  • デーモン不要
  • webアプリに組み込むだけですぐに使える

使い方

requireして、pseudoQueueを継承したクラスを定義する。

require_once('pseudoQueue.php');
class myPseudoQueue extends pseudoQueue
{
}

生産者側

sendメソッドを使うとキューにメッセージを登録できます。

$queue = new myPseudoQueue();
$queue->send('foo', 'bar');

消費者側

継承したクラスでsubscriberメソッドを上書きします。
キューにメッセージが登録されると順番にキューからメッセージを取り出して、subscriberが呼び出されます。

class myPseudoQueue extends pseudoQueue
{
    // overwrite subscriber method
    function subscriber($key, $message)
    {
        echo "key: {$key}, message: {$message}\n";
        flush(); @ob_flush();
    }
}

仕組み

インスタンスを生成したプロセスが終了した時点で、消費者プロセスを生成します。
なので、消費者側のプログラムを書く必要はなくて、subscriberメソッドを上書きするだけですみます。

堅牢性

消費者プロセスはsingleExecutionクラスを使っているので、複数プロセスが生成されることはありません。
また、仮にプロセスが落ちてもすぐに消費者プロセスは復活します。

消費者プロセスの生成

消費者プロセスの生成には pcntl_fork() を使っています。
なので、pcntl拡張が必要です。
pcntl拡張がない場合は、その場でキューを取り出して処理を実行しようとするので、pcntl拡張がなくてもそれなりに動作するはずです。

昨日作ったsingleExcutionですが、思いのほか好評なようでよかった。^o^

昨日作ったバージョンだと通常通りにスクリプトが終了すれば問題ないのですが、何らかの原因でプロセスが終了した場合にロックが残ってしまい、手動でロックを削除しないといけなかったのですが、これでは使いづらくてしょうがないので、ロックも自動で削除するように修正しました。

single_execution.php

シグナルハンドラを登録するようにした

次のようにしてシグナルハンドラを登録して、強制終了させられたタイミングでunlockするようにしてみました。
でもなぜか動かない...
なんでやねん!!

       if (singleExecution::loadExtension('pcntl')) {
            $this->key = $key;
            pcntl_signal(SIGTERM, array($this, 'unlockExit'));
            pcntl_signal(SIGHUP,  array($this, 'unlockExit'));
        }

ロックしたプロセスが存在するか調べるようにした

ロックディレクトリにプロセスIDを保存するようにしました。
このプロセスIDを使ってプロセスが存在するどうか調べて、存在しなければunlockするようにしました。

プロセスが存在するかどうか調べる

プロセスIDからプロセスが存在するかどうかは、シグナル番号0を送信してみれば分かります
phpの場合はposix拡張がないとシグナルが送れないので、その場合はpsコマンドを使って調べます。
このやり方だとpsコマンドの出力結果が変わると動かなくなるので、あまりよくないんですが...

    function processExists($pid)
    {
        if (!$this->loadExtension('posix')) {
            return posix_kill($pid, 0);
        } else {
            return intval(exec("ps -e|grep '^ *{$pid} '"));
        }
    }

ロックが不正な場合はunlock

これでロックが不正かどうか調べる準備が整ったので、不正な場合はunlockするようにしました。
不正なロックのunlock処理は不整合が起きないようにクリティカルセクションに入れました。

    function unlockIfInvalid($key)
    {
        $common_key = 'common';
    
        // spin lock
        $common_lock = $this->lockDirectory($common_key);
        while (!$this->mkdir($common_lock)) {
            usleep(10000);
        }
    
        // critical section
        if (!$this->isInvalidLock($this->lockDirectory($key))) {
            $this->unlock($key);
        }
    
        // unlock
        $this->unlock($common_key);
    }

かなり堅牢になった

想定されるケースはつぶしたので、かなり堅牢になったはず。
何かうまく動かない場合がありましたら、ご連絡ください。

PHPの拡張とは

PHPの機能を拡張するためにCで書かれた動的ライブラリのこと。

拡張を有効にするには

拡張を有効にする方法はphp.iniに設定する方法とdl()から動的に呼び出す方法の2つ。
動的に拡張を追加するにはdl()を使います。

dl()を使う問題点

dl('foo'); のように拡張名だけ指定して有効にできると便利なのですが、ちゃんと拡張のファイル名を指定しなければいけません。
で、ファイル名がOSによって異なるので、呼び出し方が異なります。

UNIX系OSの場合、拡張子が "so" になる。

dl('foo.so');

Windowsの場合、拡張子が "dll" になって、さらにファイル名の先頭に "php_" という文字列がつく。

dl('php_foo.dll');

LLなんだから、こういう基本的なところは簡便にできるようにして欲しいな〜。
それとも動的に拡張を読み込むのは特殊ケースなのかしら?

PHP_SHLIB_SUFFIXを使う

最近のPHPではPHP_SHLIB_SUFFIXという定数に拡張の拡張子が定義されているので、それを使えばOK。

$prefix = PHP_SHLIB_SUFFIX === 'dll' ? 'php_' : '';
dl($prefix.'foo.'.PHP_SHLIB_SUFFIX);

PHP_SHLIB_SUFFIXが定義されてない場合

古いPHPではPHP_SHLIB_SUFFIXが定義されてない。
そういう場合はPHP_OSを見て、Windowsかどうかで無理矢理処理する。

$suffix = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? 'dll' : 'so';
$prefix = $suffix === 'dll' ? 'php_' : '';
dl($prefix.'foo.'.$suffix);

Mac OS Xで動かない><

これで完璧だと思ったんだけど、Mac OS Xで動かない><
PHP_SHLIB_SUFFIX が "dylib" なのに、拡張が ".so" でインストールのが原因。
どっちやねん(怒

でも、将来的に ".so" から ".dylib" に変更されるかもしれない可能性を考えると、あまりアドホックにやりたくない。
というわけでいくつか候補となるファイル名をいくつか試してdl()を呼び出すことにして回避。

loadExtension()

というわけで、最終的にできた完成系がこれ。

function loadExtension($extension)
{
    if (extension_loaded($extension)) {
        return true;
    }
    
    if (defined('PHP_SHLIB_SUFFIX')) {
        $suffix = PHP_SHLIB_SUFFIX;
    } else if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
        $suffix = 'dll';
    } else {
        $suffix = 'so';
    }
    
    $prefix = $suffix === 'dll' ? 'php_' : '';
    $libraries = array("{$prefix}{$extension}.{$suffix}");
    $libraries[] = "{$extension}.so";
    $libraries[] = "php_{$extension}.dll";
    $libraries = array_unique($libraries);
    
    foreach ($libraries as $library) {
        if (@dl($library)) {
            return true;
        }
    }
    
    return false;
}

LinuxでもWindowsでもMac OS Xでもうまくいくはず。
できるだけ汎用的に書いているので、多少環境が変わっても(Mac OS Xで拡張子がdylibに変更とか)大丈夫なようになってるはず。

どこでもsymfonyコマンド実行できるようにするのって流行ってるみたい

ぷぎがぽぎ
symfonyコマンドwrapper
symfonyコマンドラッパのあれこれ
symfonyコマンドがプロジェクトトップディレクトリでしか使えない件
symfonyコマンドをどこでも使えるようにする - ゆどうふろぐ

みんなわざわざ外部コマンド作ってるんだな〜。
コマンド呼ぶだけのシンプルな機能だったら、aliasかshell functionでrcファイルにサクっと書いた方が便利な希ガス。

ゆどうふさんのはaliasだけど、プロジェクトが増えるたびに追加しないといけないのが面倒だよね。

symfony shell function

これを .zshrc に貼付ける。
"command" を使ってるので、たぶん zsh でしか動きません。

if [ ! -z `whence symfony` ]; then
  symfony(){(
    if [ -f symfony ]; then
      ./symfony $*
    elif [ $PWD = / ]; then
      command symfony $*
    else
      cd ..; symfony $*
    fi
  )}
fi

ゆどうふさんのaliasを自動追加してみる

こんな感じでいけるんじゃないのかな(未検証)。
"$()" を使ってるので、これも zsh じゃないとダメかも。

for i in `locate "$HOME/*/symfony"`;do
  alias $(basename $(dirname $i))symfony=$i
done

cronにジョブを登録して、バックグランドで定型処理を実行することをよくやるかと思います。
その時に必ず一つのプロセスだけが実行されることを保証したい時があります。
こういう時にみなさんはどのようにやっているでしょうか。

案1: cronに時間間隔をある程度あけて実行する

毎分実行するとかじゃなくて、5分くらい間隔を開けて実行するようにする。
5分以内に処理が終われば、複数プロセスで実行されることはありません。
cronの設定をちょこっと変えるだけで簡単にできます。
でも、こういうことするとメンテナンスが面倒だし、いつの間にか複数プロセスが立ち上がってたりするんですよね ToT

案2: ロック処理をいれる

ロック処理を入れて、ちゃんと排他処理してやれば大丈夫です。
ただ個人的な感覚でしかないんですが、排他処理をまともに書ける人って想像以上に少ない気がする。
基本的なプログラミングではあるとは思うのですが。
あとスクリプトが増えてくると統一されてないいろんなやり方で実装されてたりして、だんだんメンテナンスが大変になってくる。

案3: daemontoolsのsetlockを使う

これはかなりおすすめ。
setlockを使うと簡単に実現できます。
個人的にはdjbのソフトウェアはライセンスがアレゲなので、ほとんど使いませんが。
daemontoolsはバイナリ提供できないので、インストールがちょっと面倒かもしれません。

singleExecution作った

もっと簡単に実現できたらいいなと思ってsingleExecutionなるクラスを作ってみました。
codereposに登録したので、よかったらどうぞ。

single_execution.php

使い方

プログラムの先頭で、require して new するだけです。
同じプロセスが実行されてない場合は、続きの処理を実行しますが、既に他のプロセスで実行されてた場合は直ちに終了します。

require_once('single_execution.php');
new singleExecution();

例えば

時間のかかる処理を常に実行しておきたい。

プログラムの先頭で singleExecution を呼び出す。

require_once('single_execution.php');
new singleExecution();

cron には毎分実行するように登録。

* * * * * php /path/to/command/foo.php

どんなに時間のかかる処理だろうが、実行されるプロセスは1つだけ。

個人的な趣味ですが、LLはパッケージは使わずに手動でコンパイルしてインストールするのが好きです。

パッケージ管理ツールとライブラリ管理ツールで整合性をとるのが難しい

LLは大抵の言語で独自のライブラリ管理ツールを提供してくれています。
PEAR, gems, CPANみたいな。
パッケージ管理ツールを使うと、この辺のライブラリ管理ツールと整合性がとれなくなってしまうのが嫌なんですよね。

ありがちなシナリオ

・パッケージがないので、手動でライブラリをインストール
・ある日そのライブラリのパッケージができる
・依存関係でいつの間にか上書きインストール
・バージョンが変わって動かなくなった ToT

/opt/pkgs/package-x.y.z にインストールする

というわけで最近は /opt/pkgs/package-x.y.z にインストールするのが個人的なトレンドです。
インストール後は

sudo ln -s pkgs/package-x.y.z /opt/package

としてシンボリックリンクを張ります。
こうしておくとシンボリックリンクで複数のバージョンの切り替えが簡単にできる。

環境変数は

環境変数は自動で設定されるので

source ~/.zsh/.zlogin

を実行するだけですぐに使えるようになる。

アンインストール

/opt/pkgs/package-x.y.z に全てのファイルが入っているので、丸ごと削除するだけ

sudo rm -rf /opt/pkgs/package-x.y.z

パッケージは

面倒なので作らない。どうぜ個人で使うだけだし。
なんやかんやパッケージ作る手間考えると、サクっと手動でやった方が早いと思う。

でも、やっぱちゃんとパッケージでやった方がいいと思うよ。
なので他人には全くおすすめしない。

[Think IT] 第1回:スクリプトはどうやって見つける? (1/3)

ThinkITに記事を書きました。
僕が書いたのは第一回だけで、残りはウノウの別のエンジニアが担当します。

5月の特集で毎週木曜更新予定です。
よろしければご覧ください!!

ウェブページ

Powered by Movable Type 4.21-ja

このアーカイブについて

このページには、2008年5月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2008年4月です。

次のアーカイブは2008年6月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。