LoginSignup
12
6

More than 1 year has passed since last update.

Rails で無理やりRustを使う

Last updated at Posted at 2016-12-13

Ruby on Rails Advent Calendar 13日目です

注意

  • :warning: 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秒まつコードにしました。

src/afterslackffi.rs
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はこんなかんじです。

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作りましょう

posts_controller.rb
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側コードです。これも自分で書きました。次の通りです。

app/services/after_slack.rb
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使わないでやると

src/afterslackffi.rs
...

#[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さんです。個人的な面識などはありませんが、常日頃名前はお見かけします。
投稿タイトルも気になります。期待しています。

12
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
6