Ruby on Rails Advent Calendar 13日目です

注意

FFIを初めて使ってみたネタレベルなので本番でやってはいけません。
threadの処理がかなりまずい気がします
helixやruruは使っていません。使ってみたいです。
.trb形式でのinline rustも使ってないです。

モチベーション

今年の9月から新しい会社に転職し、 railsを使い始めました。
その前はjava書いていたので、嬉しさ反面、並列処理まわりで少し不満もあります。
rubyを書き出してGiant VM Lockという言葉を初めて知ったのですが、rubyのthreadって実際はIO系の処理しか並列で動かせないので、なんとも…

たとえばcontrollerでなにか処理をしたあと、httpレスポンスを先に返して、バックグラウンド処理でメールやslackに処理を飛ばす処理は簡単には書けません。
やるとしたらdelayed jobやsidekiq等を使うことになると思います。

現状それが現実解だと思いますが、sidekiqなどを使うなら、

    • Gemfileに追加

 

    • bundle install

 

    • capistrano等デプロイ機構でsidekiqプロセスを動かくように設定

 

    sidekiqプロセスの監視機構を用意

といった作業が必要です。cron等で定時バッチでやるコードを書くという手段はもっとだるいです。

それぐらいやれよというのは、そのとおりなのですが、
ようは軽い気持ちでバックグラウンド処理やりたいですよね!

ということでcontrollerで処理を飛ばしたあと、(軽い気持ちで??)rustコードを書いてslack通知をやってみたいとおもいます。

rust側

railsのルートディレクトリ直下にに rust/ というディレクトリを切ってその中で

$ cargo new afterslack

しました。これで rust/afterslack/ というディレクトリができます。
afetslackというのがrust側のプロジェクト名です。
(ネーミングおかしいのは見逃してください…)

rust側でslackに通知してみます。
slack-hook というクレート(rustのライブラリの呼び方)を使いました。
確認しやすいように3秒まつコードにしました。

extern crate slack_hook;
use slack_hook::{Slack, PayloadBuilder};
use std::time::Duration;
use std::thread;

#[no_mangle]
pub extern fn send_msg_ffi() {
    thread::spawn(move || {
        send_msg();
    });
}

fn send_msg() {
    println!("rust-slack-start");
    let webhook_url = "<自分のslackのwebhook url>";

    let slack = Slack::new(webhook_url).unwrap();
    let p = PayloadBuilder::new()
        .text("rustrustrust")
        .channel("<#ではじまるチャンネル名 or @ではじまる宛先ユーザー名>")
        .username("rust-slack-notify")
        .build()
        .unwrap();

    let res = slack.send(&p);
    match res {
        Ok(()) => println!("ok"),
        Err(e) => println!("ERR: {:?}", e)
    }
	
	// あえて3秒まってみる
    thread::sleep(Duration::from_millis(3000));
    println!("rust-slack-end");
}

Cargo.tomlはこんなかんじです。

[package]
name = "afterslack"
version = "0.1.0"
authors = ["なまえ <めあど>"]

[lib]
name = "afterslackffi"
crate-type = ["dylib"]

[dependencies]
slack-hook = "0.2"

でbuildします。

$ cargo build --release

成果物はrailsのルートディレクトリからみると
rust/afterslack/target/release/libafterslackffi.dylib
というところにできあがります。

rails側

rails new はもうやっている前提なので
controller作りましょう

class PostsController < ActionController::Base
  # POST /posts
  def create
    render text: ':create' # ここDEPRECATION WARNINGなの見逃してください
    AfterSlack.send_msg_ffi
  end
end

AfterSlack.send_msg_ffiってというのがafterslackを呼び出しているruby側コードです。これも自分で書きました。次の通りです。

module AfterSlack
  extend FFI::Library

  ffi_lib Rails.root.join 'rust/afterslack/target/release/libafterslackffi.dylib'

  attach_function :send_msg_ffi, [], :void, blocking: false
end

リクエストなげてみる

まずはthread使わないでやると

...

#[no_mangle]
pub extern fn send_msg_ffi() {
    // ためしにさっきのコードをスレッド使わずにやる
    // thread::spawn(move || {
        send_msg();
    // }); 
}

...

結果
curl -XPOST localhost:3000/postsをやってみるとrailsサーバーのログにこんな結果がでました。

Completed 200 OK in 3347ms (Views: 6.2ms | ActiveRecord: 0.0ms)

Threadの中でやってみる

threadを使うようにして、再度、cargo build –releaseしてcurlを叩いてみます。

結果

Completed 200 OK in 25ms (Views: 5.4ms | ActiveRecord: 0.0ms)

うぇーい!! 3347 ms -> 25 ms
slackにもちゃんととんでいました。

#再度注意

すいません。これうぇーいとよろこんでいますが
joinしていないthread処理をFFI経由で実行した場合、後処理はどうするんだ?
たぶん、いけないものが残る….気が..

まとめ

FFIやrustのthread処理については詳しく把握する必要がありますが、
ノウハウがたまれば、sidekiq等を用意するよりも気軽にバックグラウンド処理ができます。
本当に必要なビジネスロジックだけ実行したら、さっさとユーザーにレスポンスを返して、他の処理は後でやりたいです!

明日は

あしたはjoker1007さんです。個人的な面識などはありませんが、常日頃名前はお見かけします。
投稿タイトルも気になります。期待しています。

广告
将在 10 秒后关闭
bannerAds