SSブログ

Desquanderの問題点 [プログラミング]

「Macの手書き説明書」さんに取り上げられると一気にヒット数が一桁あがる。さすが人気サイトは違う。

ところで、Desquanderに昨日コメントで指摘してもらったバグと、その他にもうひとつ問題があることがわかった。

まず簡単な方から。

unixシステムコール中のシグナルにまつわる問題

unixではアプリから見たとき、シグナルは非同期的に、つまりプログラムの流れとは無関係にやってくる。ということはたまたまunixのシステムコールを発行しているときにやってくることもあり得る。

普通のシグナルは重大問題が発生したことを伝えるためにあるので、ちまちまとファイルを読んだりしてる場合ではないことが多く、システムコール中だろうがどうだろうが、その作業はやめてしまってさっさと終了するのが普通である。

ところがこのSIGSTOP/SIGCONTの場合、重大問題の発生を伝えているわけではない。従ってシグナルが来た後はもとの流れに戻って作業を継続することになる。

ところが、ここに問題がある。シグナルが来たときたまたまシステムコールを発行していたとしよう。例えばread()を呼んでファイルを読み込んでいたとしよう。unixの仕様ではこのread()は失敗してEINTRというエラーコードを返す。プログラム側から見たとき、それまで何の問題もなく読めていたデータが突如としてエラーを起こしたようにみえる。

最初の1バイトが読めたファイルの読み込みが、その途中で失敗することはほとんど無いので普通のプログラマはあまりread()が失敗することを前提にコードを書く人は少ない。EINTRは正確には失敗ではないので、もういちどread()をやり直せば読み込みは継続できる。バッファのポインタの制御をちゃんとやっていれば無視しても問題はない。が、read()が必ず要求したサイズの読み込みを成功させる前提で書いてしまうプログラマもいて、その場合バッファにゴミデータを作るか、あるいは臆病なプログラマはEINTRが帰った時点でエラーが発生したと判断して読み込みをやめてユーザに問題が発生したと伝えるか、などということも起こる。

普通のCocoa/Objective-Cでアプリを書いている場合はこういう問題は出ない(unixのシステムコールを直接呼ぶことは少ないし、Foundationフレームワークではシグナルにまつわる問題はフレームワーク内で解決されている)はずだけど、例えば古いunix用のコードをそのまま流用してUIを被せただけのアプリの場合、問題が出る可能性がある。

ドックにしまわれたウィンドウを持つアプリの場合

昨日コメントを貰ったバグレポートの問題。こっちはけっこう致命的。どういうものかというと
  1. ウィンドウをひとつだけ持つアプリをDesquanderでチェックする
  2. そのアプリのウィンドウを「ウィンドウ」メニューの「しまう」でドックに入れる
  3. 他のアプリに切り替える(この時点で最初のアプリはCPU時間がわたらなくなる)
  4. ドックにしまったアプリのアイコンをクリックして戻そうとしても戻らない
ウィンドウを複数持つアプリの場合でも全部のウィンドウをドックにしまってしまうと同じことになる。逆にひとつでも残っていれば残ったウィンドウをクリックすることでしまったウィンドウが戻ってくる。

この原因はドックの動作にある。ウィンドウをしまう動作はアプリ側のプロセスが実行している。ジーニーエフェクトのアニメーションもプロセスの担当としてはアプリ側で、ドックはそれに答えてタイルの場所を開けたりするアニメーションをしている。

ドックにあるアプリのアイコンタイルをクリックしたとき、ドックはまずアプリにウィンドウをデスクトップに戻させたあと、そのウィンドウを最前面に指定する。アプリのウィンドウが戻るエフェクトアニメーションを実行している間、ユーザがそのウィンドウを操作することはあり得ない。だからドックは戻ったのを確認してからそのウィンドウを最前面に持ってくる、ということをしていて、これはユーザインターフェイスの動作としては正しい。

ところが、Desquanderで止めてしまっているアプリは、最前面にならない限りCPUが割り当てられない。ドックでクリックされてもCPUを割り当てられないのでデスクトップに戻る動作ができない。ドックはウィンドウが戻るまで待って最前面に持ってこようとする。このせいでデッドロックすることになってしまう。

これはしかたがない。ドックが「ウィンドウをデスクトップに戻す」という通知(NSNotification)をしてくれると解決するけど、それはない。NSWorkSpaceのオブジェクトは「あるアプリが最前面に来た」と言う通知(NSWorkspaceDidActivateApplicationNotificationという長い名前の通知をDesquanderでは利用している)は来るけど、「これから最前面にするよ」と言う通知(言うならばNSWorkspaceWillActivateApplicationNotificationとでも。注意して欲しいけどこんな通知は存在しない)があれば解決するが、これは出してくれない。当のアプリにはドックから通知が行ってるけど、止まっているので受け取りようがない。

ということでこの解決は難しい。一番単純な解決法は、ドックを経由しない、ということ。例えばCmd-Tab(コマンドキーとタブキーを押すと実行中のアプリが横一列に表示されて、タブ、シフトタブ、あるいはマウスクリックで選択できる)で止めたアプリを選択して、そのあとドックにしまったウィンドウを回復する。これはうまくいく。しかしこれは本当の解決法ではない。

もうひとつの手段は、Desquanderを書き直してドックにフックをかける、ということ。ドックに固有のメソッドをつかまえてそれが呼ばれたとき、余分の動作をする(例えばさっきの通知を発行する)ように書き換えればいい。このためには昔ならNSObjectのposeAsClassメソッドが使えたが、10.5でdeplecatedになってしまった(10.6でもまだ動作するけど)。そのほかにはインプットメソッド経由というのがあるがこれも塞がれてしまった(こっちは10.6では動かない)。

そもそも僕はあまりこういう裏技を使うのは好きではない。OSのバージョンが違うと動作が違ったり、それ以前に微妙なリビジョンの違いでクラッシュしたりするのでめんどくさいだけ。でもいい裏口を見つけると他の人が真似できないユーティリティが作れるので有料化してもユーザがつく、と言う利点はある。もちろん僕にはそんな気はさらさらない。

正攻法でやろうとするなら、アプリを完全に止めるのではなく、ほんのちょっとだけCPU時間を与えてやる、という方法が考えられる。例えば1秒に10ミリ秒だけCPU時間を与えてやる。CPUの消費は百分の1になるので0ではないにしてもバックグラウンドが重いということはなくなる。その10ミリ秒の間にドックからの通知を受けさせる。ジーニーエフェクトのようなアニメーションは10ミリ秒では全然終わらないのでまったく足りないけど、ドックからの通知をアプリが受け付けることができれば何か手はあるはず。

この土日に考えてみよう。
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

献立02/24献立02/25 ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。