アペフチ

EPUB Parser v0.3.7リリース

EPUB Parserv0.3.7をリリースした。

二か月ぶりですね。

EPUB 2の表紙画像対応

たまにEPUB Parserをフォークしているのを見掛けて、その中で結構、EPUB 2のやり方で表紙を抜き出すパッチを当てているのがあるので、対応することにした。

EPUB仕様のバージョンは、日本では大体3系を使っていると思うんだけど、英語圏ではまだ2系を見る(O’Reillyの本とか)。EPUB 3の一つの特徴が日本語を含むCJKの慣習に寄ったことで、ルビや縦書きが導入された。[1]逆に言うと英語圏ではこれまでのツールやワークフローを変更する嬉しさがないわけで、EPUB 2が現役なんだろう。

CJKとは関係ないけど、EPUB 3になって表紙画像の指定方法も変わっている(というか今まではっきりしていなかったのをはっきりさせた)。そんなわけで、表紙画像を参照できそうな #cover_image というメソッドが、手元の(EPUB 2の)本では nil を返してしまうのをなんとかしたくてパッチを当てていたのだろうと思う。EPUB ParserはEPUB 3の為のライブラリーなので長いこと無視していたけど、状況が一向に変わらないようなので対応した。

表紙画像を抜き出すコマンド

% epub-cover APIデザインケーススタディ-――Rubyの実例から学ぶ。問題に即したデザインと普遍の考え方_00.epub
Cover image output to cover.jpg

使いでがあるようなないような。

ホームページの変更

これまでrubydoc.infoのページをホームページにしていたのだけど、GitLab Pagesに変更した。

あたりを解決するのに、GitLab CIを使ってビルドして、GitLab Pagesに上げることにした。カスタムドメインで運用したいとかあるんだけど取り敢えず今回はここまで。

あとはバグ直したりMarkdownからAsciiDocに移行したりドキュメントを修正したりしてた。


1. 個人的には、ビデオやSVGなんかのメディア対応が一番大きいと思ってる。

Re: Rocketでチャネルをうまく扱いたい

この前の「Rocketでチャネルをうまく扱いたい」には少し反応を貰えました。ありがとうございます。

確かに! すごい! よく気付きましたね!!

これは、mpsc::channel のドキュメント`mpsc::Sender`のAPIリファレンス読んで分かってるつもりだったのですが、指摘されると考えが進みますね。ありがたや。この前の日記では、自分でも何に釈然としていないのか分からない状態で書いていたのだなあ、ということがよく分かりました。

ポイントはこうです。

  • 全部自分で書けるならSenderclone() して、ウェブリクエストを処理する各スレッドに渡すことができる

  • RocketStateはパラメーターに SendSync を要求する(が、 SenderSync を実装しない)

    • これは多分、一般にマルチスレッド対応させるため、つまり複数のワーカー間で状態(ここでは Sender )を共有するため

  • Rocketがスレッドを作るところに割り込むことはできない

    • ので sender.clone() してスレッドに渡すことができない

ので、「(全部自分で書ければ)理論上は clone できるのになあ」という気持ちと「まあ一般的に作るフレームワークの立場としては最初からマルチスレッド対応になっている構造体を要求せざるを得ないよね」という気持ちとがぶつかってもやもやしていたのでありました。あーすっきりした。

最後の「Rocketがスレッドを作るところに割り込むことはできない」のはちょっと重たくて、実はRocketではなくて、Rocketが使うHTTPライブラリーのhyperのレイヤーの話なので(server/listener.rs#L31)Rocketに求めるのはちょっと厳しいなという気がします。特に、hyperを使わなくなることも検討されているようですし(Stabilize HTTP library · Issue #17)。

  • 自動で clone を呼ぶようなラッパー構造体を作って、

  • hyperでスレッドに変数をムーブする時に呼び出される「前処理フック」用のトレイトを作って、

  • ラッパー構造体でそのトレイトを実装させる

とかやればうまくいくんですかねえ。……それだったら SenderSyncSender に変えて使う方が楽ですよね。

Multi-producer multi-consumer channels for message passing

Channels are concurrent FIFO queues used for passing messages between threads.

Crossbeam’s channels are an alternative to the std::sync::mpsc channels provided by the standard library.

(メッセージパッシング用のマルチプロデューサー・マルチコンシューマーチャネル)

(チャネルとはスレッド間メッセージパッシングのために使われる並行FIFOです。)

(クロスビームのチャネルは標準ライブラリーで提供されているstd::sync::mpscのチャネルの代替となります。)

だそうで、crossbeam_channel::Sender のAPIリファレンス見るとしっかり Sync も実装しててRocketの State に渡せるし、いいかも。

特に、今はRustの学習という目的が大きくて「とりあえず動くようにして、後でどんどん直していこう」というスタンスでやってるからライブラリーとかあんまり比較してないで今回も取り敢えず標準ライブラリーの mpsc を使ったけど、将来的には複数のワーカースレッドを作って、チャネルはワーカー間で共有したい、という気持ちになるだろうから、その時にマルチコンシューマーというのは活きてくるはず( mpsc はコンシューマーの Receiverclone もせずに一つだけ使う)。

情報ありがとうございます。


余談ですけど、Rustでググってると(最近はDuck Duck Goを使うことも多いけど)、Redditが引っかかるのが多くて驚いています。Redditってプログラミングについて議論するイメージなかった。

Rocketでチャネルをうまく扱いたい

細々とRustの勉強を続けています。なんか、一応解決したけど、これでいいのかなあ、っていう問題に遭遇したので、もっといい方法あるよっていう方はぜひ教えてください。

Rocketでウェブアプリケーションを動かしつつ、リクエスト処理が終わったら別スレッドのやつからウェブフックとか送りたい、と思ってチャネルを使ってみたのですが、はじめうまくいきませんでした。

// ...
fn main() {
    let (sender, receiver): (mpsc::Sender<String>, mpsc::Receiver<String>) = mpsc::channel();
    let web thread::spawn(move || {
        rocket::ignite()
            .manage(sender)
            .mount("/", routes![process_request])
            .launch();
    });
    let webhook = thread::spawn(move || {
        start_webhook(receiver);
    });
    let _ = web.join();
    let _ = webhook.join();
}

#[post("/", data = "<file>")]
fn process_request(notifier: State<mpsc::Sender<String>>, file: Data) -> Result<> {
    // ...
}
// ...

と、RocketのState機能を使ってみようとしたところ、

error[E0277]: `std::sync::mpsc::Sender<std::string::String>` cannot be shared between threads safely

と怒られる。 State はパラメーターに Send トレイトと Sync トレイトを要求して(Struct rocket::State)、 SenderSync を実装しない(Struct std::sync::mpsc::Sender)ので型から見るとそれは分かるんですけど、では何故そんな要求になってるの?

というのはRocketはマルチスレッドで動くから、スレッドをまたがって共有するために必要なことだったんでしょう。ということは分かるけど、公式ドキュメントの「並行性」の章では Senderclone() して各スレッドに渡すサンプルが載ってるので釈然としません。(まあ、今はまず動かして、後からどんどん直していこうというつもりで作ってるから先に進むけど。)

Launching a URL Shortener in Rust using Rocket」とか「Fearless Concurrency with Rust」とか読んで、最終的にMutexを使って解決することにしましたが、あれ、今考えたら Mutex<mpsc::Sender<String>> なんて作るくらいだったらSyncSenderを使えば同じなのでは? 帰ったらやってみよう。

追記

Mutex<mpsc::Sender<String>>mpsc::SyncSender<String> にしたらコンパイル通ったし問題なく動いた!