Rails で無理やりRustを使う

[Ruby on Rails Advent Calendar](http://qiita.com/advent-calendar/2016/ruby_on_rails) 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](https://crates.io/crates/slack-hook) というクレート(rustのライブラリの呼び方)を使いました。
確認しやすいように3秒まつコードにしました。


```rust: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はこんなかんじです。

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

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

```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使わないでやると


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