Skip to content

Instantly share code, notes, and snippets.

@at-grandpa
Created November 4, 2016 23:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save at-grandpa/ce8789df955f4a5252b8f99412a0a119 to your computer and use it in GitHub Desktop.
Save at-grandpa/ce8789df955f4a5252b8f99412a0a119 to your computer and use it in GitHub Desktop.
isucon5q-crystal を公開するまでのメモ
自分の環境を構築する
anyenvを用いてcrystalをinstallする
https://github.com/riywo/anyenv
crenvをinstall
crenv global 0.19.2 を忘れない
isuconユーザーがcrystalを使えるように、rubyなどと同じようなpathを設定する
べつに大丈夫かな、isuconユーザーがcrystal使える
簡単なwebページを制作する
まずは、普通にkemalとecrを使って作る
http://kemalcr.com/docs/getting_started/
ローカルホストのcurlで返ってくるか確かめる
返ってくる
vimのエラーってなんだろう
while requiring "kemal": can't find file 'kemal' relative to '/home/isucon/isucon5q-crystal/src'
ライブラリを読み込めていない
crystal env
CRYSTAL_CACHE_DIR="/home/isucon/.cache/crystal"
CRYSTAL_PATH="/home/isucon/.anyenv/envs/crenv/versions/0.19.2/src:libs"
CRYSTAL_VERSION="0.19.2"
あー、crystal_pathがおかしいのかな
.bashrcに書くか
goは書いてあるんだよなー
書いてみたが、crystal env に反映されなかった
うーん
あーーーーー
vimのcurrent_dirを常に変えているからっぽいな
CRYSTAL_PATHを変えたいなー
うーん
vimで set noautochdir をすると、一応できたが、不便だ
まー今のところはいいかー
リポジトリに crystal app init の結果を入れる
いれた
templateを持ってきた
nginx経由でアクセスできるか調べる
crystalは何番ポートで待ち受けるか
$ curl http://127.0.0.1:3000
Hello World!
8080に変えられる?
すでにrubyが動いていて、8080が取られていたので、rubyを切る
# systemctl stop isuxi.ruby
# netstat -apn | grep LISTEN
消えた
./isucon5q-crystal -p 8080
[development] Kemal is ready to lead at http://0.0.0.0:8080
変えられた
goのときのnginx.confを真似よう
そのままでよさそう
ブラウザからアクセスしてみる
http://192.168.33.10/
見れた
簡単
http://192.168.33.10/login とかすると、kemalのエラー画面になる
systemctlに登録して、起動/再起動ができるか調べる
isuxi.crystalを作る
普通にファイルを作ればいいんだっけ?
help見るか
unitというものがあるらしい
とりあえず、goをパクるか
cd /etc/systemd/system
cp isuxi.go.service isuxi.crystal.service
何が書いてある?
こうした
```
[Unit]
Description=isuxi-crystal
After=syslog.target
[Service]
WorkingDirectory=/home/isucon/webapp/crystal
ExecStart=/home/isucon/webapp/crystal/app
[Install]
WantedBy=multi-user.target
```
起動してみるか
起動PATHを既存のものにした
root@vagrant:/home/vagrant# systemctl start isuxi.crystal
root@vagrant:/home/vagrant# systemctl | grep isu
● isuxi.crystal.service loaded failed failed isuxi-crystal
root@vagrant:/home/vagrant#
WorkingDirectoryが合ってないからかな
ログってどう見るんだろう
journalctl -f?
でてるでてる
Failed at step CHDIR spawning /home/isucon/isucon5q-crystal/isucon5q-crystal: No such file or directory
ディレクトリに、実行ファイルが存在しないよ
存在するディレクトリに変えてみるか
お、いけたいけた
出た!!!!!!!!
isucon5q-crystal[686]: [development] Kemal is ready to lead at http://0.0.0.0:8080
OK
あとは、アプリの配置ディレクトリっぽいな
/home/isucon/webapp/crystal/app を置いただけ
テンプレートもバイナリに含まれるからいいだろう
なんかあったら、こっちらへんも調べよう
```
[Unit]
Description=isuxi-crystal
After=syslog.target
[Service]
WorkingDirectory=/home/isucon/webapp/crystal
ExecStart=/home/isucon/webapp/crystal/app -p 8080
[Install]
WantedBy=multi-user.target
```
ecr使ってみるか
最小構成でやってみよう
/で
<%=…%>は、返り値を記述する
<%…%>は、crystalの制御構文を扱う
あー、なるほど。
def_to_sはマクロであり、.ecrの内容を返すto_sメソッドを定義するだけか
embedメソッドもある
あー、sinatraのerbメソッド的なものは無いのかな
http://kemalcr.com/docs/views/
あるやんか!!!
普通あるよ!!!
ドキュメント見ろよ!!
renderでできた
いいのでは?
before/afterってあるのかな
たしかあったはず
http://kemalcr.com/docs/filters/
これだ
before_all -> before_x -> X -> after_x -> after_all
before_get “/footprints”とかもあるのか
mysql接続
webapp/rubyのコードを見つつやっていく
https://github.com/waterlink/crystal-mysql
helpers do ってないのかな
sinatraのhelpersってなんだ?
ヘルパーメソッドを定義できる
crystalにはなさそうかな
適当にメソッド生やすか
うへー、適当に生やしたメソッドが参照できない
helpers使うと、can't declare def dynamically と怒られる
Kemalの作者に、kemalのドキュメントのリンクが404になっていることをTwitterで伝えた。
英語緊張した。
未だにhelpersはわからない
作者に聞く?
https://github.com/crystal-lang/crystal/issues/2355
あれ?このバグでは?
なんかcrystalの型制約っぽいなー
http://ja.crystal-lang.org/docs/syntax_and_semantics/type_restrictions.html
返り値がわからないんだ!
Mysqlのコネクションをメソッドで返すようにしたらエラーが出る
あああああああああああああ
mysqlをinstallしていないんだ!!!
あれ?やってもだめだった
buildすると、エラー内容わかりやすい
ああああああああああ
引数の数がおかしいのか!!!
mysql接続できたわ
エラーはbuildしてみて、そこで出たものを見るの良いね
エラー文が優しい
ちょっと dbとかのメソッド周りを整理したい
クラスメソッドにしたら、普通に使えた
queryの引数バインドってどうやるんだ
https://github.com/waterlink/crystal-mysql#using-higher-level-query-api
あった
例外はExceptionなのか
セッションは?
http://kemalcr.com/docs/sessions/
instantiating method_name
このエラーの対処法がわからない。多分型違反なんだけど、どうな押したら良いかわからない
引数受け渡しの型違反だけでない。引数は合ってても、そのメソッドの中でエラーが起きてたら、メソッド名も
in ./src/isucon5q-crystal.cr:77: undefined method 'to_i' for Nil (compile-time type is (Array(Array(Bool | Float64 | Int32 | Int64 | MySQL::Types::Date | Slice(UInt8) | String | Time | Nil)) | Nil)) (did you mean 'to_s'?)
@@user = result.to_i
長いなーw
こういうエラーに導かれているんだろうか。
なんか、if is_a?() は条件を複雑にしてはいけない
型推論がうまくいかない
あと、hashとtupleが似ているので、hashの際は => で指定したほうが良い
あー、実行前にいろいろ気付けるのはありがたいな
保存がちょっと遅い
実際のコードが通る場所じゃないと、buildの時にエラーチェックされない
まぁ、だって、通らないからエラーにはならないよね
クラス変数を使うと、かなりややこしくなる
うーむ
Helperクラス化して、インスタンス変数にする?
まー普通はそうだよね。
別パッケージにするよね。
getとかはブロックだから、普通に中で色々使えるし。
go言語を参考にする作戦
自分定義の型って書けるんだっけ?
いままでのrubyっぽく書いていると、かなーりコンパイルエラーになる
そうすると、結構遠回りしている感じがする
goを真似よう
型を定義したい
おーテストコード書いたらわかった
クラスを渡すことができるっぽいな
型制約というか、クラス制約を書ける
Userクラスを作成すれば良さそう
mysqlくん!なんだ、ちゃんとINT32とかで返してるじゃん!
rows.scanとかにしたほうがよい?
うーん、わからん、ユニオン型のままでいくと、意図したメソッドが返らないんだよなー
どこかで明示的に、ユニオン型を型定義したいが、どうしたもんか
goの場合ってどうしていたんだっけ?
postgreSQLってなんだ、やってみよう
なんかいろんなことに挑戦する気になるな
うーん、ライブラリが無いからって、postgreに行く理由はないよなー
mysqlで頑張ろう
とりあえず、実装していくことにした
時間取られてもしゃーない
ひたすらコンパイラを通すコーディング
もっとcrystalらしさってのがあるんじゃないかなー
kemalとかのコードを見てみるか
Kemal::Sessionsについて調べる
sessionを消したい
ドキュメントを読む癖がついた
セッション消すのどうしようかなー
https://github.com/sdogruyol/kemal/blob/master/src/kemal/session.cr#L96
これで、現在時刻を入れればよさそう
Kemal::Sessions.prune!(Time.now.epoch_ms)
クラス変数だったね
コンパイラとエラー文がやさしいから、乗り越えていける
cssが返らん
sinatraは public_folder というキーワードを使っていた
crystalは?
http://kemalcr.com/docs/static_files/
うおおおお、返せた
バイナリと同じ階層にpublicディレクトリを置いておく感じだ
バイナリに静的ファイルが組み込まれるわけではない
できた
postのパラメータってどう取るんだろ
https://github.com/sdogruyol/kemal/blob/master/src/kemal/param_parser.cr
postは body だった
コード読んだらわかった
よっしゃ!rootのさくせいや!
やっていってる
ecrのなかでエラーが出るのは結構きついなー
ifでのコンパイル解析がうまくいかないらしい
Webappをクラス化して、ルーターからはメソッドを呼ぶだけにした
クラス変数使わなくて済む
インスタンス変数でいいからわかりやすい
コンパイルは通ったが、ブラウザでエラーが出る
なんかmysqlからの返り値がまったくわからんぞ
一応型が入って返ってきているっぽい
うーむ
しかし、Stringがほしいところ、Slice[UInt8]が返ってくる
Sliceってなんだ
/testエンドポイントで確かめてみる
ふむー
String.new(slice : Slice(UInt8)) でstringに変換できそうだけど、
ユニオン型なのでコンパイルエラーになる
unlessするかなー
できた
String.new(bytes : Slice(UInt8), encoding : String, invalid : Symbol | ::Nil = nil)
これでいける?
https://github.com/waterlink/crystal-mysql/blob/94e0bf2719288365c4bdecfd4124f6dbabb3789d/src/mysql/types.cr#L158
ここでmysqlのtypeとcrystalのtypeのdispatchをしているけど、textがない
varcharもみあたらないな
textをvarcharに変えてみる?
mysqlのvarcharとtextの違い
http://lxyuma.hatenablog.com/entry/2015/08/15/131309
varcharにalterしてみるかな
dumpして、alter
む、その前に、mysqlにcast関数があるのか
あああ
textが問題ではなさそうだ
日本語文字列が??になってしまうっぽい
varcharでもそうなった
crystal自身で書いた日本語は普通に出力される
mysqlからcrystalに飛んできたものが怪しい
textとかvarcharとか関係なく
mysqlの中では日本語なので、やっぱりライブラリなのかなー
「????????????」ってなってるけど、一応通った
ひたすら型を制限していった
commentをCASTでcharにしているのに、なぜかsliceになってしまう
joinするとsliceになる
String.new(comment, “utf8")
too many mysql connection が出たので、dbのコネクションオブジェクトを再利用できるようにする
||=にした
indexいけた
あとはcss
sinatraはレイアウトコマンドがあったけど、crはどうしたらいいかな
layout.erbの内容が自動で読み込まれていたっぽい
crystalの場合は全部に書くかー
セッションキーがないと言われた
なんだ、ログインしてからやったらできたじゃん
profile.ecrの25行目の[0..60]の部分が怪しい
いや、怪しくなかったわ。60文字に制限しているだけだった
新しいecrを作ったら
hashの取得をメンバの取得に切り替える
layout.ecrをコピペ
privateキーワードの置換
文字列を””に変える
dbアクセス部分をガッツリ書き換え
セッションが切れちゃうのはなんでだろうなー
一応全部いったかな
よっしゃ
リファクタリング?
crystal-mysql を forkした
ローカルで編集して、shardでinstallする
insert時に
Unexpected byte 0x27 at position 36, malformed UTF-8 (InvalidByteSequenceError)
というのが出た
関係ありそう
mysqlライブラリ側のエラーではないのか。
crystal側?
https://github.com/crystal-lang/crystal/search?utf8=%E2%9C%93&q=InvalidByteSequenceError
あるなぁ
https://github.com/crystal-lang/crystal/blob/1f3e8b0e742b55c1feb5584dc932e87034365f48/src/char/reader.cr#L208
ここだ
メソッドが invalid_byte_sequence
private だから、同じクラス内からしか呼べない
Charクラスだ
このfirstからexceptionに飛んでいるが、各 0xxxというのが何かわからん
調べてみるかー
struct Char::Reader のエラーの説明文をしっかり読もう
[5693068] *MySQL::Query#replace:String +92
[5692594] *MySQL::Query#to_mysql:String +274
[5692298] *MySQL::Query#run:(Array(Array(Bool | Float64 | Int32 | Int64 | MySQL::Types::Date | Slice(UInt8) | String | Time | Nil)) | Nil) +10
[5654042] *Isucon5q::Webapp#post_profile_account_name:Int32 +1162
このあたり怪しいよねぇ
MySQL::Query#replace ってなにしてるんだろ
*String#index:(Int32 | Nil) が呼ばれ、*Char::Reader#decode_current_char が呼ばれ、??? でraiseされているのか
fm
MySQL::Query#replaceに渡ってきた第一引数のs、これのindexを取っているから、このsにおかしなものが紛れ込んでいそう
このsってなんだっけ?
上にあった。#to_mysql
このvalueという項目がsに渡ってきている
valueってなんだっけ?
reduceってなんだっけ? injectと同じか
to_mysqlはmysqlのsqlに変換してるっぽいな
:hoge を valueにしているのか
あれ?
valueにmultibyteの文字が入ってきている?
そうしたら確かにおかしくなりそうかも
自分の書いたコードを見てみる
post_profile_account_name
名字に全角を入れてもエラーにならない(1文字しか入らないけど)
名前に全角を入れるとエラーになる
first_nameのほうだ
やっぱりそうだ
first_nameに決め打ちで入れてみる
それでもだめっぽい
あー、last_nameにもあっちゃだめだな
あれ?コメントとか日記ってできたんじゃないの?
いけるじゃん!!!!!
update文がだめなのか?
やはりupdate文だ
ライブラリの中にログを仕込む
fmfm
last_nameに変な値が入っている
なんでだろう
どこで変な値になっているのか
1文字だったらいけるか
replaceメソッドを徹底検証
- s = クエリ文字列
- name = :hoge の文字列
- value = 置き換える値
- resultを空にする
- lenに「:name」の長さを入れる。これだったら5
- p0=0にする
- whileで回る
- p1 = s.index(name, p0)を代入し、その値でwhileチェックする
- s.index(name, p0)
- https://crystal-lang.org/api/0.19.4/String.html#index%28search%3AString%2Coffset%3D0%29-instance-method
- これっぽい
- p0動かしたところからstartして、nameが見つかった文字列のindexの数値を返す
- そのindexをp1に入れる
- result += s[p0...p1]
- 0~見つかったindexの数値までの文字列を入れる
- result += value
- valueに置き換える
- 最後に、残りの文字列をくっつける
- これってgsubでいいのでは?
- この部分が書かれたのっていつだっけ?
- 2015/03/24 古いな
- crystalにgsubって有る?
- あー、replaceは2016/03/02に作られている
- これはPR出すかー
- fm
- 書き換えてみる
- gsub良さそう
- valueに一文字しか入らない
- representation(@params[name]) の結果、1文字しか入らない
- Types::Value.new(value).lift_down.to_mysql
- なおした
- あとは、mysql側のinsert/updateにて、文字コードがおかしいらしい
- mysqlに入っているデータがそもそも文字化けしている
- なんでだろう
- crystal側で読み込めない文字列が、mysqlに入っている
- で、crystalから入れた文字列は、crystalは読み込めるが、mysqlが読み込めていない
- コード上に文字列を入れて、やってみるか
- だめだ
- コード上の文字列でも、入れるとあかんくなる
- つまり、vim上での文字列はmysqlの認識できるものではない
- 逆に、mysql上で認識できる文字列は、crystalページで文字化けしている
- やっぱり、mysqlライブラリを間に挟んでいるから、そこからcrystal側でおかしくなっているのでは
- runメソッドを見てみる
- String#to_unsafe ???
- https://crystal-lang.org/api/0.19.4/String.html#to_unsafe-instance-method
- pointerof(@c) ってのを返している
- @cってなんだ
- LibMySQLクラスがよくわからんなー
- ext.crにあるっぽい
- あーc言語bindかー
- 勉強するかー
- to_unsafeってCバインでィングの話か
- libの名前はLibから始める
- @[Link("pcre")] は -lpcre をリンカに渡します。
- バインディングしたら、クラスメソッドのように扱える
- funcで宣言すれば、crystalの中でCの関数を使えるのか
- Cのmysqlclientを読まないといけないっぽい
- Cに期待される型を正確に渡さなければならない
- うーん、これ以上深く追ってもアレだな
- crystalの文字コードについて学ぶ
- mysqlの画面でも日本語が打てない
- もしかして、環境変数?
- コマンドラインでは打てるな
- mysql> status
- Server characterset: latin1
- Db characterset: latin1
- Client characterset: utf8
- Conn. characterset: utf8
- これを全部utf8に変えるのかなぁ
- いやー、こうすると、rubyとかgoもおかしくなっちゃうよなー
- crystal側に問題がありそうな気がする
- 仕切り直し
- もどした
- DBは正しい
- crystal側がおかしい
- うーん。なんかutf8に対応するようなコードが、libmysqlclientにあると思うんだよなー
- rubyのコードを見てみるか
- それっぽいメソッドをcrystalのコードから見つける
- それはCのメソッドのはずで、rubyのリポジトリにも書いてあるはず
- mysql_queryで探してみる
- mysqlという単語すらない
- rubyのmysqlのライブラリをあたるか...
- https://github.com/brianmario/mysql2
- これかなー
- mysql_enc_to_ruby.rb
- ruby_enc_to_mysql.rb
- の二つがある
- まじかーw
- 見てみよう
- https://github.com/brianmario/mysql2/blob/67d1b16ba3382322a0042fd9ff5bf23cc27cd9e8/ext/mysql2/result.c
- これかーー??
- https://github.com/brianmario/mysql2/blob/67d1b16ba3382322a0042fd9ff5bf23cc27cd9e8/ext/mysql2/mysql_enc_name_to_ruby.h
- これもかーーーー??
- String#encodeってのがcrystalにもあるらしい
- 使えるんだろうか
- だめそう
- mysql2がどうやってencodeしているかを調べたい
- 仮説
- クエリ結果を取得する
- その結果と共に or mysqlの設定を何処かで、取得して、取得結果の文字コードを得ているはず
- 取得結果と、得た文字コード情報を用いて、適切にmysql->rubyのところで変換しているはず
- https://github.com/brianmario/mysql2/blob/67d1b16ba3382322a0042fd9ff5bf23cc27cd9e8/lib/mysql2/client.rb#L46
- self.charset_name というものがあった
- https://github.com/brianmario/mysql2/blob/67d1b16ba3382322a0042fd9ff5bf23cc27cd9e8/lib/mysql2/client.rb#L46
- ここにも、queryがあるじゃん
- https://github.com/brianmario/mysql2/blob/67d1b16ba3382322a0042fd9ff5bf23cc27cd9e8/lib/mysql2/client.rb#L105
- これっぽいなー
- https://github.com/brianmario/mysql2/blob/67d1b16ba3382322a0042fd9ff5bf23cc27cd9e8/ext/mysql2/client.c#L1342
- cのmysqlクエリをそのまま呼んでいる
- https://github.com/brianmario/mysql2/blob/67d1b16ba3382322a0042fd9ff5bf23cc27cd9e8/ext/mysql2/client.c#L672
- ここか
- じっくり見直そう
- _queryというCのコードとbindしている
- ext/mysql2/client.c:672
- static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
- selfはclient構造体
- sqlは文字列
- currentは???
- struct nogvl_send_query_args args;
- 69:struct nogvl_send_query_args {
- /*
- * used to pass all arguments to mysql_send_query while inside
- * rb_thread_call_without_gvl
- */
- struct nogvl_send_query_args {
- MYSQL *mysql;
- VALUE sql;
- const char *sql_ptr;
- long sql_len;
- mysql_client_wrapper *wrapper;
- };
- mysql_send_queryへの全ての変数をあつかう?
- argsがこの構造体
- GET_CLIENT(self);
- ext/mysql2/client.h
- 62:#define GET_CLIENT(self) \
- #define GET_CLIENT(self) \
- mysql_client_wrapper *wrapper; \
- Data_Get_Struct(self, mysql_client_wrapper, wrapper);
- https://docs.ruby-lang.org/ja/latest/function/Data_Get_Struct.html
- ここ?
- ruby側のCレイヤーのメソッドだった
- Ruby のオブジェクト obj から type 型へのポインタを とりだし svar に代入します。
- Ruby のオブジェクト self から mysql_client_wrapper 型へのポインタを とりだし wrapper に代入します。
- mysql_client_wrapperのポインタをwrapperに保存している
- 総じて、mysql_client_wrapperをポインタにセットしている
- どこに?
- wrapper変数に保存
- mysql_client_wrapperとは?
- client.hの52行目
- typedef struct {
- VALUE encoding;
- VALUE active_thread; /* rb_thread_current() or Qnil */
- long server_version;
- int reconnect_enabled;
- unsigned int connect_timeout;
- int active;
- int automatic_close;
- int connected;
- int initialized;
- int refcount;
- int freed;
- MYSQL *client;
- } mysql_client_wrapper;
- おおう、、、encoding持ってる。。。
- wrapperだから、MYSQL *clientを持っている
- これって、どこでセットされているんだろうね
- 先かな
- REQUIRE_CONNECTED(wrapper);
- ext/mysql2/client.h :54
- #define REQUIRE_CONNECTED(wrapper) \
- REQUIRE_INITIALIZED(wrapper) \
- if (!wrapper->connected && !wrapper->reconnect_enabled) { \
- rb_raise(cMysql2Error, "closed MySQL connection"); \
- }
- 初期化か。
- コネクション張ってるのか
- REQUIRE_INITIALIZED(wrapper)
- ext/mysql2/client.c :28
- #define REQUIRE_INITIALIZED(wrapper) \
- if (!wrapper->initialized) { \
- rb_raise(cMysql2Error, "MySQL client is not initialized"); \
- }
- wrapperのinitializedを見ている
- intじゃん
- ただ、コネクションしているかどうかを見ているのかな
- 結構いろいろwrapperに仕事させてるな
- args.mysql = wrapper->client;
- mysql_send_queryへの全ての変数を扱うargsのmysqlメンバにwrapperのclientをセット
- (void)RB_GC_GUARD(current);
- 多分GC
- Check_Type(current, T_HASH);
- currentってなんなんだー
- optionに相当するから、Hashかどうかをチェックしているんだと思う
- 一応見るかー
- mysql2には定義なし
- やはりrubyか
- https://docs.ruby-lang.org/ja/latest/function/Check_Type.html
- TYPEが異なれば、例外
- オプションがHashかどうかを見ている
- rb_iv_set(self, "@current_query_options", current);
- 現在のオブジェクトのinstance_variableの、@current_query_optionsにcurrentをセット
- Check_Type(sql, T_STRING);
- クエリが文字列かどうかを判定
- #ifdef HAVE_RUBY_ENCODING_H
- クエリ文字列の中にマルチバイト文字が入っていたらこっちなのかな
- mysql2側にはないな
- ruby側だ
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/include/ruby.h#L16
- ずっと1っぽいぞ
- まー、両方考えるか
- /* ensure the string is in the encoding the connection is expecting */
- args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding));
- 「文字列は、接続が期待されているエンコーディングであることを確認」
- by google翻訳
- rb_str_export_to_enc
- mysql2に無いから、rubyかな
- https://github.com/ruby/ruby/blob/468301b98487d3b2b0d9e4a60c912803f4ba39f0/string.c#L1060
- これか
- string.cか
- rb_str_export_to_enc(VALUE str, rb_encoding *enc)
- {
- return rb_str_conv_enc(str, STR_ENC_GET(str), enc);
- }
- 指定したエンコーディングで文字列を出力する
- rb_str_conv_enc
- 見るからに、from-toでconvertしているよね
- sqlの文字列を、エンコーディングで出力している
- なんのエンコーディングに指定するのか
- rb_to_encoding(wrapper->encoding)
- rb_to_encoding
- mysql2に無い
- rubyだ
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/encoding.c#L245
- rb_to_encoding(VALUE enc)
- {
- if (enc_check_encoding(enc) >= 0) return RDATA(enc)->data;
- return str_to_encoding(enc);
- }
- enc_check_encoding:::::::
- static int
- enc_check_encoding(VALUE obj)
- {
- if (!is_obj_encoding(obj)) {
- return -1;
- }
- return check_encoding(RDATA(obj)->data);
- }
- オブジェクトがエンコーディングじゃ無かったら-1
- エンコーディングだったら、check_encoding
- static int
- check_encoding(rb_encoding *enc)
- {
- int index = rb_enc_to_index(enc);
- if (rb_enc_from_index(index) != enc)
- return -1;
- if (enc_autoload_p(enc)) {
- index = enc_autoload(enc);
- }
- return index;
- }
- rb_enc_to_index:::
- int
- rb_enc_to_index(rb_encoding *enc)
- {
- return enc ? ENC_TO_ENCINDEX(enc) : 0;
- }
- ENC_TO_ENCINDEX::::
- #define ENC_TO_ENCINDEX(enc) (int)((enc)->ruby_encoding_index & ENC_INDEX_MASK)
- ruby_encoding_index::::
- みつからん
- ENC_INDEX_MASK::::
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/encoding.c#L62
- #define ENC_INDEX_MASK (~(~0U<<24))
- まぁ、なんかのMASKだろう...
- エンコードオブジェクトをエンコードインデックスに変換するマクロだな
- encがあればエンコードインデックスを返すし、なければゼロ
- rb_enc_from_index(index)
- indexからencオブジェクトを生成している
- え、これ、絶対一致するんじゃないの?w
- しないこともあるのかなぁ
- なかったら-1
- enc_autoload_p(enc)
- なんだこれ
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/encoding.c#L76
- これだ
- #define enc_autoload_p(enc) (!rb_enc_mbmaxlen(enc))
- rb_enc_mbmaxlen::::
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/include/ruby/encoding.h#L174
- これだ
- #define rb_enc_mbmaxlen(enc) (enc)->max_enc_len
- 最大のエンコード長さを返している
- なんだ最大のエンコード長さって
- わからんが、「!」がついているので、0だったらtrueが返るってことか
- (enc)->max_enc_len が 0 ならif文に入る
- index = enc_autoload(enc);
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/encoding.c#L137
- static int enc_autoload(rb_encoding *);
- 関数のプロトタイプ宣言?
- 実態がどこかわからん
- まーindexを返すものとしておくか
- indexを返す
- indexを返す
- enc_check_encoding(enc)
- 0以上だったら RDATA(enc)->data; を返す
- RDATAは、encをなんかいい感じのデータにするはず
- そのdataってなんだ
- dataそのものかな
- つまり、encodingオブジェクトかどうかをチェックして、そのオブジェクトを返しているんだな
- encodingのオブジェクト化するものは wrapper->encoding
- 何が入っている?
- たしかに、wrapperの構造体の中にencodingはある
- エンコーディング情報なのかな?
- どこで設定されている?
- argsを設定しているとき?
- GET_CLIENT(self)かな
- 結局、selfのポインタをwrapperに入れている
- selfってなんだ
- rb_queryの第一引数
- え、これって、client.query(sql, options = {}) にないんだけど
- ああああああああああああああああ
- レシーバか!!!
- clientか!
- nrhd
- clientってなんだっけ
- Mysql2::Clientだ
- initializeしてあるはず
- initializeからCの構造体をどうやって作っているのか謎
- ここだなー
- rubyの変数とCの構造体のメンバが、どうやってbindingされているのか
- client.cとclient.hを見てみるかーーー
- わからん
- ext/mysql2/result.cで、wrapper->encodingに入れている
- :984
- うおおおおおお
- 結果を一気にwrapperに入れているじゃないか!!!!!!
- result.cでwrapperが埋まるのか
- クエリから始まり、最終的にここに来たい
- 一旦離脱する
- 一応これで、args.sqlにwrapperのencodingになった文字列が入った
- もう片方
- args.sql = sql;
- 普通にsqlを代入している
- args.sql_ptr = RSTRING_PTR(args.sql);
- args.sql_len = RSTRING_LEN(args.sql);
- args.wrapper = wrapper;
- ポインタや、length, wrapperをargsに入れる
- rb_mysql_client_set_active_thread(self);
- スレッド関連だろう
- とりあえず、WIN_32は無視する
- do_send_query(&args);
- きたぞ
- ここは何をしているんだ
- client.c :436
- struct nogvl_send_query_args *query_args = args;
- argsをquery_argsに入れる
- mysql_client_wrapper *wrapper = query_args->wrapper;
- wrapperをargsから取ってくる
- これは、db_send_queryをする前に入れていたのでOK
- if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
- ここがfalseならnilを返す
- ここがtrueなら、以下を実行する
- エラーになる
- rb_thread_call_without_gvl
- client.h :8
- /* emulate rb_thread_call_without_gvl with rb_thread_blocking_region */
- #define rb_thread_call_without_gvl(func, data1, ubf, data2) \
- rb_thread_blocking_region((rb_blocking_function_t *)func, data1, ubf, data2)
- rb_thread_blocking_region
- mysql2になさそう
- rubyか
- あれ?ない?
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/doc/NEWS-2.2.0#L293
- コレを見ると、2.2.で「Deprecated APIs removed. [Feature #9502]」らしい
- 消された?
- あー、一つ上のifdefがあるじゃん
- #ifndef HAVE_RB_THREAD_CALL_WITHOUT_GVL
- #ifdef HAVE_RB_THREAD_BLOCKING_REGION
- 「rb_thread_call_without_gvlがなくて、rb_thread_blocking_regionがあれば定義」的な感じだから、rb_thread_call_without_gvlがあるんならそれを見よう
- 2.2.0を境目にその挙動が変わる
- 見つけた、rb_thread_call_without_gvl
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/thread.c#L1314
- 長いなーw
- gvlとは
- http://d.hatena.ne.jp/mirichi/20141231/p1
- Global VM Lock らしい
- まー並列関連だな
- 今回はパス
- 結局、do_send_query はスレッドの有無とか並列のhogeをチェックしている
- 次いこ
- /* this will just block until the result is ready */
- return rb_ensure(rb_mysql_client_async_result, self, finish_and_mark_inactive, self);
- client.c :715
- 「結果の準備ができるまで、これは単にブロックされます」
- ほー
- ensureってなんだっけ
- ensure=「確保」
- rb_ensure
- mysql2にない
- rubyだ
- https://github.com/ruby/ruby/blob/f0137ba8cdd805249a55d371aa2309f5622b7f70/doc/extension.ja.rdoc#L1509
- ドキュメントにあった
- VALUE rb_ensure(VALUE (*func1)(), VALUE arg1, VALUE (*func2)(), VALUE arg2)
- 関数func1をarg1を引数として実行し, 実行終了後(たとえ例外が 発生しても) func2をarg2を引数として実行する.戻り値はfunc1 の戻り値である(例外が発生した時は戻らない).
- ほーー
- つまり、この行は、
- 関数をrb_mysql_client_async_resultをselfを引数として実行し, 実行終了後(たとえ例外が 発生しても) finish_and_mark_inactiveをselfを引数として実行する.戻り値はrb_mysql_client_async_result の戻り値である(例外が発生した時は戻らない).
- ということか
- rb_mysql_client_async_result(self)とは
- client.c :490
- 確かにselfが引数だ
- これはレシーバ = client構造体
- Returns the result for the last async issued query.
- 結果を返すっぽいーーーーー
- client.async_result を実行したときに呼ばれるらしい
- ってことは、これを呼んでいることと等しいってことか
- なにするんだろ
- いや、結果を返すってことか
- そうだな
- どこかでエンコードをしているはず!!!!!!!!!!!!!!
- みていくかー
- 491 MYSQL_RES * result;
- 492 VALUE resultObj;
- 493 VALUE current, is_streaming;
- resultオブジェクトとか用意
- その他、ローカル変数を用意
- 494 GET_CLIENT(self);
- あーなんだっけな
- mysql_client_wrapperをポインタにセットしている
- 496 /* if we're not waiting on a result, do nothing */
- 497 if (NIL_P(wrapper->active_thread))
- 498 return Qnil;
- 結果を待っている間は何もしない…?
- 500 REQUIRE_CONNECTED(wrapper);
- ただ、コネクションしているかどうかをみてる
- 501 if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
- 502 /* an error occurred, mark this connection inactive */
- 503 wrapper->active_thread = Qnil;
- 504 rb_raise_mysql2_error(wrapper);
- 505 }
- エラーが発生したら、コネクションを切る
- まー、なんかスレッド関連だろう
- 507 is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream);
- 508 if (is_streaming == Qtrue) {
- 509 result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_use_result, wrapper, RUBY_UBF_IO, 0);
- 510 } else {
- 511 result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
- 512 }
- rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream);
- https://github.com/ruby/ruby/blob/bbd58fa2b956a33c035dbddc83e9efbe07ee7276/hash.c#L820
- hashとkeyを引数にとる
- 今回引数
- hash -> rb_iv_get(self, "@current_query_options”) -> clientオブジェクトの@current_query_options
- key -> sym_stream
- なんだこれ
- 1351 sym_stream = ID2SYM(rb_intern("stream"));
- らしい
- つまり、clientの@current_query_optionsのhashに、:stream が存在するかどうかか
- ここにstreamingするならtrueとか入っているのでは?
- https://github.com/brianmario/mysql2/blob/079860b464d350a46117551fae1eb4a743d11a4a/README.md#streaming
- あるやーーーーん!
- でかいテーブルとかをストリーミング処理できるらしい、いいね
- これがあるかどうかで処理が変わるのか
- まー考えずにいくか
- ストリーミングじゃない場合
- result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
- え、rb_thread_call_without_gvlかよ
- なんか返したっけ
- とりあえず今は無視
- 514 if (result == NULL) {
- 515 if (mysql_errno(wrapper->client) != 0) {
- 516 wrapper->active_thread = Qnil;
- 517 rb_raise_mysql2_error(wrapper);
- 518 }
- 519 /* no data and no error, so query was not a SELECT */
- 520 return Qnil;
- 521 }
- 結果が無かったら、コネクションを切ってnilを返す
- 523 current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
- 524 (void)RB_GC_GUARD(current);
- 525 Check_Type(current, T_HASH);
- clientオブジェクトの@current_query_optionsをdup
- GCを制御
- Hashかどうかをチェック
- 526 resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
- ここやああああああああああああああ
- rb_mysql_result_to_obj
- 結果をオブジェクト化している
- encodingが設定されている
- wrapper->encodingってどうやって設定されているんだっけ?
- ちょっとこれはTODO
- とりあえず見ていく
- ext/mysql2/result.c :972
- みてたやん
- 973 VALUE obj;
- 974 mysql2_result_wrapper * wrapper;
- objとwrapperのローカル変数を定義
- 976 obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper);
- この結果をobjに入れているのか
- なんかここキモっぽいな
- https://github.com/ruby/ruby/blob/f0137ba8cdd805249a55d371aa2309f5622b7f70/doc/extension.ja.rdoc#L1509
- ここにある
- Data_Make_Struct(klass, type, mark, free, sval)
- type型のメモリをmallocし,変数svalに代入した後,それをカプセル化したデータを返すマクロ.
- つまり
- cMysql2Result型のメモリをmallocし,変数wrapperに代入した後,それをカプセル化したデータを返すマクロ.
- typeは構造体っぽいな
- markは?
- rb_mysql_result_mark
- ext/mysql2/result.c :77
- 関数だ
- 76 /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */
- 77 static void rb_mysql_result_mark(void * wrapper) {
- 78 mysql2_result_wrapper * w = wrapper;
- 79 if (w) {
- 80 rb_gc_mark(w->fields);
- 81 rb_gc_mark(w->rows);
- 82 rb_gc_mark(w->encoding);
- 83 rb_gc_mark(w->client);
- 84 rb_gc_mark(w->statement);
- 85 }
- 86 }
- wrapperがあれば、gcでmarkしている
- freeは?
- rb_mysql_result_free
- ext/mysql2/result.c :133
- 関数だ
- 133 /* this is called during GC */
- 134 static void rb_mysql_result_free(void *ptr) {
- 135 mysql2_result_wrapper *wrapper = ptr;
- 136 rb_mysql_result_free_result(wrapper);
- 137
- 138 // If the GC gets to client first it will be nil
- 139 if (wrapper->client != Qnil) {
- 140 decr_mysql2_client(wrapper->client_wrapper);
- 141 }
- 142
- 143 xfree(wrapper);
- 144 }
- あー、オブジェクトを開放する関数ぽい
- svalは?
- この変数にポインタをセット
- なるほど、Data_Make_Structは、オブジェクトを生成するが、そのオブジェクトのgc_markをmarkの関数で行い、freeの関数も渡し、いい感じに行うようにしている
- 他の変数はどうなるんだっけ
- コードを見てみるか
- https://github.com/ruby/ruby/blob/787e878863d32d297373d7a4796271923246ae4d/include/ruby/ruby.h#L1149
- ここっぽい
- ifdefあるな
- Data_Make_Struct0はすぐ上にある
- __GNUC__がある場合
- Data_Make_Struct0(data_struct_obj, klass, type, sizeof(type), mark, free, sval); \
- data_struct_obj; \
- Data_Make_Struct0
- #define Data_Make_Struct0(result, klass, type, size, mark, free, sval) \
- VALUE result = rb_data_object_zalloc((klass), (size), \
- (RUBY_DATA_FUNC)(mark), \
- (RUBY_DATA_FUNC)(free)); \
- (void)((sval) = (type *)DATA_PTR(result));
- まぁいいや
- なるほど、Data_Make_Structは、オブジェクトを生成するが、そのオブジェクトのgc_markをmarkの関数で行い、freeの関数も渡し、いい感じに行うようにしている
- こんな感じだな
- wrapperのメンバ変数にめっちゃ入れるところ
- 初期化っぽいな
- ext/mysql2/result.c :977
- 977 wrapper->numberOfFields = 0;
- フィールド数0
- 978 wrapper->numberOfRows = 0;
- 行数0
- 979 wrapper->lastRowProcessed = 0;
- 最終行の処理0
- 980 wrapper->resultFreed = 0;
- ???
- 981 wrapper->result = r;
- rは引数で持ち込んだもの
- client.cの526行目で引数resultが渡されている
- とりあえず、508行目で生成している
- MYSQL_RES型
- 982 wrapper->fields = Qnil;
- フィールドnil
- 983 wrapper->rows = Qnil;
- 行nil
- 984 wrapper->encoding = encoding;
- encodingを設定
- これって、wrapperのencodingだよな?
- 第二引数で渡されている
- client.cの526行目でwrapper->encodingが渡されているんだよなぁ
- なんだろこれ
- 985 wrapper->streamingComplete = 0;
- わからん
- 986 wrapper->client = client;
- clientは第一引数で渡されている
- Mysql::Client型のやつ
- self
- 987 wrapper->client_wrapper = DATA_PTR(client);
- https://docs.ruby-lang.org/ja/latest/function/DATA_PTR.html
- 実際は struct RData* 型である dta から、 それがラップしているポインタを取り出します。
- まぁ、clientのポインタかな
- わからん
- 988 wrapper->client_wrapper->refcount++;
- referenceカウント?参照カウント?
- 989 wrapper->result_buffers = NULL;
- ???
- 990 wrapper->is_null = NULL;
- まぁ、nullではないよね
- 991 wrapper->error = NULL;
- エラーなし
- 992 wrapper->length = NULL;
- 長さなし
- wrapperのメンバの初期化ってどこでやってるんだろう
- encodingとか、入ってる前提よね?
- 994 /* Keep a handle to the Statement to ensure it doesn't get garbage collected first */
- 995 wrapper->statement = statement;
- statementってなんだ
- 引数の5番目ラストで渡ってきている
- Qnilじゃん
- Qnilなので、
- 1000 wrapper->stmt_wrapper = NULL;
- が実行される
- Qnilじゃなかったら、
- ポインタをstmt_wrapperにセットしているな
- 1003 rb_obj_call_init(obj, 0, NULL);
- https://docs.ruby-lang.org/ja/latest/function/rb_obj_call_init.html
- オブジェクト obj に対して initialize を呼び出します。 引数は長さ argc の配列 argv で表され、 ブロックが積んである場合はそれも自動的に渡されます。
- ほー
- なるほど
- 呼ばれるのはわかるけど、返り値ないの?
- とりあえず、objになんか入ってる、でいいのかな
- 1004 rb_iv_set(obj, "@query_options", options);
- オブジェクトのinstance_variableにoptionsを設定
- 引数の3番目にわたってきてるやつ
- client.cではcurrentだった
- 523 current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
- @current_query_optionsお値をコピーしている
- 1006 /* Options that cannot be changed in results.each(...) { |row| }
- 1007 * should be processed here. */
- 1008 wrapper->is_streaming = (rb_hash_aref(options, sym_stream) == Qtrue ? 1 : 0);
- ストリーミングが設定されていたら1
- されていなかったら0
- 1010 return obj;
- あー、これで返しちゃうのか
- この時点で結果が入っていると思っていたが、そうではないのか
- resultのメソッドが呼び出されたら、やっとmysqlにつなげるのか
- なるほど
- resultのメソッドを見る必要があるな
- wrapperのencodingが初期化されているところが気になる
- 何が入っているの?
- wrapperってなんだっけ
- ext/mysql2/client.h :39
- 39 typedef struct {
- 40 VALUE encoding;
- 41 VALUE active_thread; /* rb_thread_current() or Qnil */
- 42 long server_version;
- 43 int reconnect_enabled;
- 44 unsigned int connect_timeout;
- 45 int active;
- 46 int automatic_close;
- 47 int connected;
- 48 int initialized;
- 49 int refcount;
- 50 int freed;
- 51 MYSQL *client;
- 52 } mysql_client_wrapper;
- ここは定義だけなんだよなー
- いままで追ってきた処理で、一番最初にwrapperが出てきたところってどこだっけ?
- うーん
- ちょっとアプローチを変えて ag 'wrapper->encoding’ で検索
- ext/mysql2/client.c :1205
- wrapper->encoding = rb_enc;
- なんだこれ
- 1186 static VALUE set_charset_name(VALUE self, VALUE value) {
- この中だ
- 何してる?
- ざっと見るに、mysqlとrubyのcharsetのmappingっぽい
- どこから呼ばれている?
- 1334: rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1);
- rubyのメソッドに定義されている
- client.charset_name = hoge って感じか
- ってことは、ruby側のコードを見ないといけないか?
- lib/mysql2/client.rb :46
- self.charset_name = opts[:encoding] || 'utf8'
- clientのinitializeだ
- オプションで :encoding が指定されていなかったら utf8がデフォルトだ
- よし、set_charset_name を見ていこう
- 1186 static VALUE set_charset_name(VALUE self, VALUE value) {
- 引数のvalueは、文字コードを表す文字列
- 1187 char *charset_name;
- char型の変数を定義
- 1188 #ifdef HAVE_RUBY_ENCODING_H
- 1189 const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb;
- 1190 rb_encoding *enc;
- 1191 VALUE rb_enc;
- 1192 #endif
- HAVE_RUBY_ENCODING_Hは、ruby側でencodingを持っていたらtrueなはず
- mysql2_mysql_enc_name_to_rb_map型の構造体のポインタを生成 mysql2rb
- mysql2_mysql_enc_name_to_rb_mapとは?
- 二つのヘッダファイルにあるけど、client.cは何をincludeしているんだっけ?
- 15 #include "mysql_enc_name_to_ruby.h"
- こっちだった
- ext/mysql2/mysql_enc_name_to_ruby.h :32
- 32 struct mysql2_mysql_enc_name_to_rb_map { const char *name; const char *rb_name; };
- これは、nameのmapをつくるもので、mysql側のnameとruby側のrb_nameがある
- この段階では、まだmappingは生成していない
- 入れ物だけっぽい
- rb_encoding型のポインタを生成 *enc
- VALUE型の rb_enc を生成
- 1193 GET_CLIENT(self);
- clientオブジェクトを生成
- 1195 charset_name = RSTRING_PTR(value);
- charset_nameに、引数の値をString化してセット
- 1197 #ifdef HAVE_RUBY_ENCODING_H
- rubyにencodingがセットされていたら入る
- 1198 mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value));
- あーここでこの関数呼び出しているのか
- mappingテーブルを取得しているっぽい
- 中を見てみよう
- mysql_enc_name_to_ruby.h :86
- 引数はstrとlen
- str: rubyコードで client.charset_name = hoge としたときの hoge
- valueをString型にしたもの
- len: valueのlength
- constとかやってるけど、何を返しているのか
- 99 static const struct mysql2_mysql_enc_name_to_rb_map wordlist[] =
- mysql2_mysql_enc_name_to_rb_map型のwordlistを生成
- 一つ目にmysqlの文字コード、二つ目にrubyの文字コードが書いてある
- 155 if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
- 指定した文字数以内かどうかを判定
- 今回の場合、enumで登録してあるもので 3-8文字
- 文字数内だったら以下に入る
- register int key = mysql2_mysql_enc_name_to_rb_hash (str, len);
- Cのregisterってなんだ
- http://dixq.net/forum/viewtopic.php?f=3&t=535
- 「registerは変数へのアクセスを最大限速くしなさいという意味の予約語です。」
- まぁいいや
- mysql2_mysql_enc_name_to_rb_hash
- :43
- なんじゃこりゃ
- mysqlの名前をrubyのhashにする?
- なんかのマッピングか
- 配列のindexを返す?
- やっぱり。返り値をwordlistのindexにつかっている
- return len + asso_values[(unsigned char)str[2]] + asso_values[(unsigned char)str[0]] + asso_values[(unsigned char)str[len - 1]];
- ‘utf8’でやってみる
- str = utf8
- http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1337632318
- アスキー文字コード
- http://e-words.jp/p/r-ascii.html
- (unsigned char)str[0] = (unsigned char)u = 117
- (unsigned char)str[1] = (unsigned char)t = 116
- (unsigned char)str[2] = (unsigned char)f = 102
- (unsigned char)str[3] = (unsigned char)8 = 56
- len = 4
- return 4 + a[102] + a[117] + a[str[4-1]]
- return 4 + a[102] + a[117] + a[str[3]]
- return 4 + a[102] + a[117] + a[56]
- おー、asso_valuesは0-255までの要素数だ
- return 4 + 20 + 0 + 10
- return 34
- wordlist[34] てなんだ
- "34 : {\"utf8\"=>\"UTF-8\"}"
- うわあああああああああああああああああ
- utf8になった!!!!!
- なんだこれ!!!!!
- なんだこれ...
- ちゃんと変換してんだなー
- 159 if (key <= MAX_HASH_VALUE && key >= 0)
- 160 {
- 161 register const char *s = wordlist[key].name;
- 162
- 163 if (*str == *s && !strcmp (str + 1, s + 1))
- 164 return &wordlist[key];
- 165 }
- MAX_HASH_VALUEを超えていなければ入る
- wordlist[key].nameなので、mysql側の文字コードを取る = *s
- strとsのポインタは同じだが、ポインタを一つ進めるとことなる
- 返り値は、wordlistのポインタだ
- この場合は、{\"utf8\"=>\"UTF-8\”} が返る
- これで、1198 mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value)); の返り値は、今回使用するエンコードの {\"utf8\"=>\"UTF-8\”} となる
- 1199 if (mysql2rb == NULL || mysql2rb->rb_name == NULL) {
- 1200 VALUE inspect = rb_inspect(value);
- 1201 rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
- mappingが取れなければraise
- 1202 } else {
- 1203 enc = rb_enc_find(mysql2rb->rb_name);
- 1204 rb_enc = rb_enc_from_encoding(enc);
- 1205 wrapper->encoding = rb_enc;
- 1206 }
- mappingがあれば、
- 1203 enc = rb_enc_find(mysql2rb->rb_name);
- ruby側のencodeを探す
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/encoding.c#L728
- name -> inx -> enc としている
- 1204 rb_enc = rb_enc_from_encoding(enc);
- rubyのエンコーディングオブジェクトを生成
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/encoding.c#L116
- enc -> idx -> encoding としている(?)
- 1205 wrapper->encoding = rb_enc;
- wrapperのencodingにrubyのエンコーディングオブジェクトをセット
- ここかぁぁぁぁぁぁ
- 1209 if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
- 1210 /* TODO: warning - unable to set charset */
- 1211 rb_warn("%s\n", mysql_error(wrapper->client));
- 1212 }
- まーようわからんがいいや
- 1214 return value;
- 最後にvalueを返すのか
- 入力した文字列そのものだった
- 結論
- wrapper->encoding は、clientをinitializeした時に設定されている
- デフォルトはutf8
- :encodingオプションで指定可能
- つぎは、wrapper->encoding が設定されていることがわかったので、このencodingがいつ使用されるのか
- おそらくresult.cの中の関数だな
- resultオブジェクトに対して do とかすると、結果が返ってくるので
- ほとんどの関数に、encodingを用いているところがあるはず
- result.c
- 170 static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbolize_keys) {
- このあたり、すげーあやしいな
- wrapper->encodingも使われている
- 170 static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbolize_keys) {
- 333 static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, const result_each_args *args)
- 520 static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const result_each_args *args)
- これらで wrapper->encoding がつかわれているぞ!
- この3つだ
- conn_encに入っている?
- rb_enc_associate(rb_field, conn_enc);
- 何しているんだろ
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/encoding.c#L854
- VALUE
- rb_enc_associate(VALUE obj, rb_encoding *enc)
- {
- return rb_enc_associate_index(obj, rb_enc_to_index(enc));
- }
- rb_enc_associate_index:::
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/encoding.c#L826
- objをうけとるのか
- returnもobj
- この間で何をしているか
- rb_encoding *enc;
- int oldidx, oldtermlen, termlen;
- rbのencodingクラス
- 各種変数
- rb_check_frozen(obj);
- https://github.com/ruby/ruby/blob/f0137ba8cdd805249a55d371aa2309f5622b7f70/include/ruby/intern.h#L268
- rb_check_frozen_internal
- https://github.com/ruby/ruby/blob/f0137ba8cdd805249a55d371aa2309f5622b7f70/include/ruby/intern.h#L260
- まぁ、frozenしているかどうかのチェックかな
- oldidx = rb_enc_get_index(obj);
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/encoding.c#L773
- objがSPECIAL_CONST_Pだったら、入る
- シンボルじゃなかったら return -1
- シンボルだったらStringに変換する
- objの型によって、caseの行き先が変わる
- T_STRING or T_REGEXP
- i = enc_get_index_str(obj);
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/encoding.c#L760
- Stringからidを得ているっぽい
- i = ENCODING_GET_INLINED(str);
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/include/ruby/encoding.h#L56
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/include/ruby/encoding.h#L45
- (int)((RBASIC(obj)->flags & RUBY_ENCODING_MASK)>>RUBY_ENCODING_SHIFT)
- なんかflagとMaskとSHIFTでなんかしている
- まー、idだな
- T_FILE
- internal_encodingやexternal_encodingをみる
- 下へ
- T_DATA
- if (is_data_encoding(obj)) {
- i = enc_check_encoding(obj);
- }
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/encoding.c#L152
- エンコーディングだったらidを返すって感じか
- とにかくidを返す
- if (oldidx == idx)
- return obj;
- 引数のidxとoldidxが一致したらobjを返す
- if (SPECIAL_CONST_P(obj)) {
- rb_raise(rb_eArgError, "cannot set encoding");
- }
- SPECIAL_CONST_Pだったらencodingは設定できない
- enc = must_encindex(idx);
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/encoding.c#L179
- indexをenc化し、indexが正しいかをチェックしている
- なんかもろもろあるけど、一旦無視
- enc_set_index(obj, idx);
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/encoding.c#L807
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/include/ruby/encoding.h#L54
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/include/ruby/encoding.h#L39
- 結局、RBASIC(obj)->flags |= (VALUE)(i) << RUBY_ENCODING_SHIFT;\ にいれている
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/include/ruby/encoding.h#L35
- RUBY_ENCODING_INLINE_MAX = 127
- https://github.com/ruby/ruby/blob/202bbda2bf5f25343e286099140fb9282880ecba/include/ruby/encoding.h#L28
- rb_ivar_set(obj, rb_id_encoding(), INT2NUM(idx));
- rb_id_encoding()
- CONST_ID(id_encoding, "encoding");
- https://github.com/ruby/ruby/blob/787e878863d32d297373d7a4796271923246ae4d/include/ruby/ruby.h#L1717
- https://github.com/ruby/ruby/blob/787e878863d32d297373d7a4796271923246ae4d/include/ruby/ruby.h#L1714
- https://github.com/ruby/ruby/blob/787e878863d32d297373d7a4796271923246ae4d/include/ruby/ruby.h#L1707
- encodingという文字列の変数のconst_idを返す
- encodingという変数に、idxのnumberをセット
- encodeのidがobjにセットされて、返される
- 結局、rb_fieldオブジェクトに、wrapper->encodingの値がセットされている
- まだ変換までいってない
- result.c
- 201 if (default_internal_enc) {
- 202 rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
- 203 }
- default_internal_encがあるなら、rb_fieldにはそれをセットする
- なんかもうざっとみたんだけど、結局rubyはobjectの中にencodingを持っていて、mysql2はそこに適切にencをセットしているっぽい
- crystalってencの扱いはどうなっているんだっけ?という問題
一旦crystalに戻る
- mysqlから取ってきたものは、そもそも何の文字列なんだっけ?
- なんもないのかな
- なんか文字コードあるのかな
- あー、mysqlに持っていく中で、もうだめだったんだった
- mysqlにどう持っていくかだった
- ふむー
http://tmtms.hatenablog.com/entry/2016/09/06/mysql-utf8
これ怪しいな
やっぱiconvがutf8mb4に対応していないのでは
あー、utf8のテーブル作って、crystalで取ってきて、変になるかをやってみるか
やってみよう
これでだめだったら、utf8mb4は関係なくて、単にutf8対応でよさそう
- utf8のテーブルを作って試してみる
- まずは、utf8mb4でmysqlに接続して、手動でinsertできるか確認
- myconfでutf8mb4にした
- mysql> show variables like "chara%";
- +--------------------------+----------------------------+
- | Variable_name | Value |
- +--------------------------+----------------------------+
- | character_set_client | utf8mb4 |
- | character_set_connection | utf8mb4 |
- | character_set_database | latin1 |
- | character_set_filesystem | binary |
- | character_set_results | utf8mb4 |
- | character_set_server | utf8mb4 |
- | character_set_system | utf8 |
- | character_sets_dir | /usr/share/mysql/charsets/ |
- +--------------------------+----------------------------+
- 8 rows in set (0.00 sec)
- 接続し直したので、内容を見てみる
- うーん、日本語入力ができない
- コマンドプロンプトからは入力できる
- その状態で、utf8のテーブルを作る
- 作ってみるか
- 作った
- mysql> CREATE TABLE `test` (
- -> `user_id` int(11) NOT NULL,
- -> `name` varchar(64) NOT NULL,
- -> PRIMARY KEY (`user_id`)
- -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED;
- Query OK, 0 rows affected (0.01 sec)
- mysql> select * from test;
- +---------+-----------+
- | user_id | name |
- +---------+-----------+
- | 1 | あああ |
- +---------+-----------+
- 1 row in set (0.00 sec)
- これをcrystalから取ってみる
- お!utf8のテーブルでも、「???」になる!
- 「あああ」 = 「???」ってことは、utf8,utf8mb4に関わらず、マルチバイト文字がだめっぽいな
- その対応だなー
- crystalから入れてみる
- insert into test (user_id, name) values (2, 'あああ')
- mysql> select * from test;
- +---------+-----------------------+
- | user_id | name |
- +---------+-----------------------+
- | 1 | あああ |
- | 2 | ã‚ã‚ã‚ |
- +---------+-----------------------+
- 2 rows in set (0.00 sec)
- やはり文字化けした
- encodingがおかしいのかな
- query = String.new("insert into test (user_id, name) values (4, 'あああ')".encode("SHIFT-JIS"))
- ってやってみた
- mysql> select * from test;
- +---------+-----------------------+
- | user_id | name |
- +---------+-----------------------+
- | 1 | あああ |
- | 2 | ã‚ã‚ã‚ |
- | 3 | ã‚ã‚ã‚ |
- | 4 | ‚ ‚ ‚ |
- +---------+-----------------------+
- 4 rows in set (0.00 sec)
- なんか違う感じにはなったから、変換はできているっぽい
- マルチバイト文字対応
- utf8, utf8mb4の扱いに違いはなさそう
- crystal自身、utf8は扱えている
- crystalから入れたマルチバイト文字がmysql上で文字化けしていて、それを取得すると、ブラウザにうまく表示できている
- crystal-mysqlを通ると、おかしくなり、そのままmysqlにinsertされ、取ってくるとうまく表示できる
- crystal-mysqlがまずそう
- utf8で保存できていないのかもしれない
- crystal-mysqlの中を追うかー
- ファイルに出力して、どこで文字化けしているか見てみたい
- connection.cr :95
- 95 code = LibMySQL.query(@handle, query_string.to_unsafe)
- @handle
- 11 @handle = LibMySQL.init(Pointer(LibMySQL::MySQL).null)
- query_string.to_unsafe
- to_unsafe
- https://crystal-lang.org/docs/syntax_and_semantics/c_bindings/to_unsafe.html
- https://ja.crystal-lang.org/docs/syntax_and_semantics/c_bindings/to_unsafe.html
- もし、ある型に to_unsafe メソッドが定義されていた場合、C に渡されるのはそのメソッドからの戻り値となります。例をあげます。
- Stringのto_unsafe
- https://github.com/crystal-lang/crystal/blob/7f82f79bd1e3a7a1a73607e35c0662906736f1f8/src/string.cr#L3162
- @cのポインタを返している
- Cでのポインタって、文字列の先頭を表すからな
- @c
- なにかわからんが
- LibMySQL.query(@handle, query_string.to_unsafe)
- ext.cr :175
- 175 fun query = mysql_query(mysql : MySQL*, stmt_str : UInt8*) : Int16
- @handleはMySQL*だし、query_string.to_unsafeはポインタだからUInt8*なのか
- いや、charがUInt8で、to_unsafeはそのポインタを返しているからこうなるのか
- mysql_query関数
- これはmysqlclientライブラリにある?
- やっぱり、C関数だ
- これってさ、rubyのmysql2でも使ってるんじゃないの?
- resultを出力したら Pointer(Void)@0x1a285a0 だった
- rowを出力してみよう
- [1, "???"]
- ???になっている
- ふむ
- なーんかこの間で起きているんだなー
- CのAPIで文字コードをhogehogeするのってできないの?
- うおおおおおおおお
- 247 fun set_character_set = mysql_set_character_set(mysql : MySQL*, stmt_str : UInt8*) : Int16
- これをext.crに付け加えて、
- LibMySQL.set_character_set(@handle, "utf8”)を書いたら、返ってきたぞ、日本語で。
- /testでは帰ってきたが、rootではだめだった
- bodyがSliceで返って来ている
- charsetをなくすとどうなる?
- なるほど。でる
- なんだあああああああああああ??????????
- あぁ、以前いれたエンコードのおかしい文字列が邪魔していた
- /initilizeを実行したら普通に表示できた
- 一応動くところまではきたかな
- TODO
- mysqlライブラリの修正
- ベンチ叩いてみる
- エラーでた
- つぶしていくかーーー
- /loginからのリダイレクトでrootにいってるけど500か
- どこで落ちているのか
- 画面確認
- ログイン失敗すると落ちるな
- Index out of bounds
- ん?文字列がおかしい?
- プリントデバッグ
- ああん?post_loginで500なのか
- あれ?resultがnullだったら、raiseじゃないの?
- ruby版で確かめてみる
- 「ログインに失敗しました」ってでるぞ
- どこでエラーハンドリングしているんだろう
- あーなるほど。errorメソッドで、Exception毎に飛ばす先を決めているのか
- ふむー
- kemalにそれあるか?
- あるーーーー
- http://kemalcr.com/docs/error_handling/
- やろう
- returnしたらだめだな
- Exceptionを用いて、errorに飛ばさないとだめだ
- kemalにいろんなエラーがあるか?
- あんまなかったな
- あー、別のエラーだった
- 「未ログインでトップページへのアクセスが正しいリダイレクトになっていません」
- このとおりだよ
- でもまー別のエラーを解除したから良しとするか
- Kemal::Sessionsのkeyがあるかを見たいが、Kemal::Sessions#[]:(Bool | Float64 | Int32 | String) だからhas_keyがないんだよな
- でも中身ではHashを見ているから、なんかできないかしら
- Kemal::Sessions の中を見てみる
- []では、index out of bounds になるが、
- []?ではnilが返る
- いけた
- リダイレクトしないだと…?
- ああああああああああ
- @envにリダイレクトをセットして、そのままreturnしたら、そっちに飛ぶってことか
- はぁ?
- まぁいろいろやりますか
- /css/bootstrap.min.css
- これがとれない
- etagのとこ通ってない
- 何回もアクセスすれば、そうなるわな
- ブラウザだとlengthなくて、curlだとある
- なんで?
- gzip側にいって、content-lengthがないのか
- 足してみる
- いった!!!!!!!!
- コメントに書いておこう
- static_file_handler.crの :62 と :72
- 次のエラーがでた
- javaだ
- Exception: java.io.EOFException: HttpConnectionOverHTTP@411c95fe(l:/127.0.0.1:56088 <-> r:/127.0.0.1:80,closed=false)[HttpChannelOverHTTP@69fe4e12(exchange=HttpExchange@99329bf req=TERMINATED/null@null res=PENDING/null@null)[send=HttpSenderOverHTTP@783e5462(req=QUEUED,snd=COMPLETED,failure=null)[HttpGenerator{s=START}],recv=HttpReceiverOverHTTP@5d289dfe(rsp=CONTENT,failure=null)[HttpParser{s=CLOSED,19879 of 122540}]]]
- java.io.EOFException: HttpConnectionOverHTTP@411c95fe(l:/127.0.0.1:56088 <-> r:/127.0.0.1:80,closed=false)[HttpChannelOverHTTP@69fe4e12(exchange=HttpExchange@99329bf req=TERMINATED/null@null res=PENDING/null@null)[send=HttpSenderOverHTTP@783e5462(req=QUEUED,snd=COMPLETED,failure=null)[HttpGenerator{s=START}],recv=HttpReceiverOverHTTP@5d289dfe(rsp=CONTENT,failure=null)[HttpParser{s=CLOSED,19879 of 122540}]]]
- at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.earlyEOF(HttpReceiverOverHTTP.java:274)
- at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:1263)
- at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.shutdown(HttpReceiverOverHTTP.java:182)
- at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.process(HttpReceiverOverHTTP.java:129)
- at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.receive(HttpReceiverOverHTTP.java:69)
- at org.eclipse.jetty.client.http.HttpChannelOverHTTP.receive(HttpChannelOverHTTP.java:90)
- at org.eclipse.jetty.client.http.HttpConnectionOverHTTP.onFillable(HttpConnectionOverHTTP.java:112)
- at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:245)
- at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:95)
- at org.eclipse.jetty.io.SelectChannelEndPoint$2.run(SelectChannelEndPoint.java:75)
- at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceAndRun(ExecuteProduceConsume.java:213)
- at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:147)
- at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:654)
- at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:572)
- at java.lang.Thread.run(Thread.java:745)
- なにこれ
- HttpConnectionOver
- コネクションしすぎ?
- 127.0.0.1:56088
- なんだろ
- そんなポートない
- ベンチマークって公開されてるっけ?
- ベンチがどうなっているか見るか
- んで、ベンチが公開されているかを見るか
- わからんなーーー
- ちょっと別のエラーを考える
- アクセスログの通りに動いてみる
- post_loginで302ってなんだ
- いや当たり前だ
- rootで302ってなんだ
- あー、current_userが無い場合にリダイレクトしたからか
- rubyだったらどうなるんだっけ?
- nginxのログを比較した
- http://www.mk-mode.com/octopress/2013/01/16/nginx-access-log/
- 見方
- デフォルトが書かれている
- log_format combined '$remote_addr - $remote_user [$time_local] '
- '"$request" $status $body_bytes_sent '
- '"$http_referer" "$http_user_agent"';
- crystalは $body_bytes_sent が3, rubyは0になっている
- 他もcrystalの方がちょっと多いな
- あー、rootで、crystalは302, rubyは200になっている
- rubyはなんでそうなるんだ?
- rubyのrootを見てみる
- authenticated!だなー
- rubyはどうしているか
- currentユーザーが居なかったら、reditrectしている
- ふむー、やはりcurrent_userの挙動がおかしいっぽい
- あ、cookieに焼いてない????
- use Rack::Session::Cookie ってなんだ
- セッション情報を全てcookieに入れている
- kemalでもやってみるか
- Sessionがない...
- セッションが動作することは、/testで分かった
- どっかでセッションを切っていないとか?
- 切ってるのかな
- リダイレクトがなんか関係ある?
- あああああああああ
- クラス変数にしないとだめか!!!
- ちがう、envは渡すものだ
- なんでkemal.sessionの@idが違うときがあるのか
- @idはどうやって生成しているのか
- Kemal::Sessions.newをした時に決まる
- あー @env.session を呼んだ時に生成されるのか
- ふむー
- HTTP::Server::Contextのそれぞれのオブジェクトで、session_idが決まる
- HTTP::Server::Context はどこで生成されているのか
- 何をもってして、そのアクセスはこのsession、だとしているのか
- 64 def initialize(ctx : HTTP::Server::Context)
- 65 id = ctx.request.cookies[Kemal.config.session["name"].as(String)]?.try &.value
- 66 if id && id.size == 32
- 67 # valid
- 68 else
- 69 # new or invalid
- 70 id = SecureRandom.hex
- 71 end
- 72
- 73 ctx.response.cookies << HTTP::Cookie.new(name: Kemal.config.session["name"].as(String), value: id, http_only: true)
- 74 @id = id
- 75 end
- idを生成している
- Kemal.config.session["name"].as(String)
- tryとは
- メソッドがなかったり、nilに対して呼び出しても大丈夫って感じ
- cookieに焼かれていないんだ!!!!!!!!
- kemal_sessionというcookie
- なんで焼かれないんだろう
- 新しいHTTP::Server::Contextになっていないか?
- initializeってどこで呼ばれているんだろう
- context.cr の :29
- @sessionが nil or false なら行われる
- cookieにsessionidだけを設定しているんだよね
- rubyだとどういう扱いになるんだっけ
- あと、ブラウザのcookieを確認してみる
- うーん、rubyだとうまくいくのはなんでだろう
- ベンチ側にcookieが焼けないのだなぁ
- 逆に、cookieを焼いているところはどこか
- ほおおおおおおおおおおおお
- 先にsessionにセットした、その時は参照できた、で、しばらくしてから同じユーザーでアクセスすると、そのときは取れていない
- prune!がまずい?
- しばらく保存しておかないとだめだよね
- pruneしているところを洗い出し
- あーログイン時にpruneしているな。ここか
- ここで消しているんだ
- deleteにした
- 進んだ!!!!!!
- エラーが出る
- 友だちリストに友だちになったばかりのユーザが表示されていません
- 表示されているんだけどなー
- ベンチだけかな
- またCookieか?
- でもDBっぽいしなー
- 友だちになったユーザのコメントがトップページに正しく表示されていません
- あああああああ
- 「comment-created-at not match」って書いてあるじゃん!
- なんか時刻に「UTC」ってついている
- created_at.to_s("%Y-%m-%d %H:%I:%S")
- この to_s をつけないといかん
- エラー減った!!!!
- 次のエラー
- NEW-FRIEND'S ENTRY LIST MUST HAVE RECENT PRIVATE ONE
- private周り?
- あ、これは成功した感じか
- ん?ruby版とcrystal版で、friendsの時刻が異なるぞ
- crystal版、なんか1桁しかない
- https://crystal-lang.org/api/master/Time/Format.html
- ああああああああああ
- created_at.to_s("%Y-%m-%d %H:%M:%S")
- 「%M」だった!!!!
- ruby版といろいろ比較だなー
- ちょいちょい違うな
- htmlの比較をするか
- indexのfootprintsの時刻が出ていない
- 246: comments_of_friendsなんか人物違う
- private系?
- あー、そうっぽいな
- private=1のエントリーのコメントは出してはいけない
- あー、private_flag, boolじゃん
- 突破した
- 友達のエントリーが出ていないものがある
- private系?
- rubyにはprivateが出ているが、crystalには出ていない
- ベンチ前は差分なし
- crystalベンチ後は?
- 違いなし!
- ちがったのは、rubyでベンチをallで走らせたから?
- いや、ruby_benchのあともcrystalとも確認すべきでは
- ない!
- 他のページもやってみるか
- うーん、友達リスト、合ってるなぁ
- 地道にやるしかないかー
- 友達になりたての友達リストに載ってないらしい
- 個数は確かめたけど、あってるっぽいなー
- URLがなんか/friendsじゃないのが微妙
- rubyとcrを比べたけど、一致しているなー
- ブラウザでぽちぽちやったけど、一緒だなー
- ベンチは何か違うことをしている?
- それか、エラー文が違う?
- /diary/entries/cortez118 これかな
- NEW FRIEND CAN SEE PRIVATE ENTRY って、次にはrubyが出ている
- 友人のエントリーが本文すくなくなってるー
- 直してみよう
- body.split('\n')[1]
- これが、タイトル抜かした1行目だけになってるのかーーーーー
- rubyではどうしてるんだっけ
- </br>が入っている?
- ベンチ側がおかしいんじゃないかなー
- javaのエラー見てみるか
- LOGIN POST
- INDEX AFTER LOGIN
- STYLE SHEET CHECK
- Exception: java.io.EOFException: HttpConnectionOverHTTP@255b8a9d(l:/127.0.0.1:34229 <-> r:/127.0.0.1:80,closed=false)[HttpChannelOverHTTP@31d993be(exchange=HttpExchange@53e47a4e req=PENDING/null@null res=PENDING/null@null)[send=HttpSenderOverHTTP@6d9a1744(req=HEADERS,snd=SENDING,failure=null)[HttpGenerator{s=START}],recv=HttpReceiverOverHTTP@14ec6708(rsp=CONTENT,failure=null)[HttpParser{s=CLOSED,19879 of 122540}]]]
- java.io.EOFException: HttpConnectionOverHTTP@255b8a9d(l:/127.0.0.1:34229 <-> r:/127.0.0.1:80,closed=false)[HttpChannelOverHTTP@31d993be(exchange=HttpExchange@53e47a4e req=PENDING/null@null res=PENDING/null@null)[send=HttpSenderOverHTTP@6d9a1744(req=HEADERS,snd=SENDING,failure=null)[HttpGenerator{s=START}],recv=HttpReceiverOverHTTP@14ec6708(rsp=CONTENT,failure=null)[HttpParser{s=CLOSED,19879 of 122540}]]]
- at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.earlyEOF(HttpReceiverOverHTTP.java:274)
- at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:1263)
- at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.shutdown(HttpReceiverOverHTTP.java:182)
- at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.process(HttpReceiverOverHTTP.java:129)
- at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.receive(HttpReceiverOverHTTP.java:69)
- at org.eclipse.jetty.client.http.HttpChannelOverHTTP.receive(HttpChannelOverHTTP.java:90)
- at org.eclipse.jetty.client.http.HttpConnectionOverHTTP.onFillable(HttpConnectionOverHTTP.java:112)
- at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:245)
- at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:95)
- at org.eclipse.jetty.io.SelectChannelEndPoint$2.run(SelectChannelEndPoint.java:75)
- at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceAndRun(ExecuteProduceConsume.java:213)
- at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:147)
- at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:654)
- at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:572)
- at java.lang.Thread.run(Thread.java:745)
- INDEX AFTER LOGIN 2ND USER
- INDEX AFTER LOGIN 3RD USER
- POST COMMENT ON MY ENTRY
- ------------------
- java.io.EOFException
- https://docs.oracle.com/javase/jp/6/api/java/io/EOFException.html
- 入力の途中で、予想外のファイルの終了、または予想外のストリームの終了があったことを表すシグナルです。
- なんか返す文字列がおかしい?
- 何の項目だっけ?
- STYLE SHEET CHECKの次?
- STYLE SHEET CHECK
- style sheet check
- INDEX AFTER LOGIN 2ND USER
- ------
- style sheet checkだ
- cssがおかしい?
- cr: "GET /css/bootstrap.min.css HTTP/1.1" 200 19879 "-" "Isucon5q bench"
- rb: "GET /css/bootstrap.min.css HTTP/1.1" 200 122540 "-" "Isucon5q bench"
- ぜんぜんちげーじゃねーか!
- ヘッダが何かおかしいのかな
- ヘッダのパース?
- 微妙にちがう
- 合わせてみるか
- content_typeに charset=utf-8 を足してみる
- 足したけど違った
- header操作は、crystal公式apiにありそう
- https://crystal-lang.org/api/master/HTTP/Headers.html
- X-Content-Type-Options: nosniff を付けてみる
- つけた
- だめだった
- etagを避けて、Last-Modifiedにしてみる
- etagを消してみる
- あかんなー
- HttpParser{s=CLOSED,19879 of 122540}
- これって、19879文字目で閉じているってこと?
- なんかおかしいの返しているんじゃないかなー
- cssの文字列は大丈夫だ
- やっぱりheaderだろう
- at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.earlyEOF(HttpReceiverOverHTTP.java:274)
- earlyEOFって出てるけど
- gzip??
- うわあああああああああああああああああああああああ
- headerのgzipを切ったらいけたああああああああああ
- curlじゃわからんかった
- 相変わらず、友達になったばかりの友人の出力がおかしい
- 実際にコードのどこを動いているか見てみるか
- うーん、ブラウザとベンチの動きが同じっぽい
- ecr?
- ふむー
- なんか動かしてみたら、footprintsがおかしいような
- あしあとついていないっぽさがある
- insertしていない
- うさあああああああああああああああああああ
- mark_footprintのif文が間違っている
- なおした
- 一応入ったが、友達のエラーはなおらず
- うーむ
- headerがちがうのかーーーーー?
- header合わせてみるかー
- いやー、headerじゃないだろー
- このuriだけおかしいってあるかな
- リスト表示がおかしい?
- おかしくないなー
- ベンチを疑うか?
- エラー文ってなんだっけ
- NEW-FRIEND'S ENTRY LIST MUST HAVE RECENT PRIVATE ONE
- うひーーーーーーーーーーーー
- https://github.com/isucon/isucon5-qualify/blob/master/bench/src/main/java/net/isucon/isucon5q/bench/scenario/BootstrapChecker.java
- ここ、エラー文が間違っているっぽいな
- /footprintsが怪しいっぽい
- 足跡リストだ
- そういうことかあああああああああああああああああああああああああ
- 進んだああああああああああああああああああああ
- 次のエラーが出た
- パーミッションエラー
- 解消した
- ついにベンチが回った!!!!!!!!!!!!!!!!!!!
- 初期スコア 0
- まー、あとはエラーの解消かな
- current_userがnilだった場合はautherrorにする
- あー、nginxのログで、どこが500かを見たほうがいいな
- いきますかー
- /frinedsの500
- current_userの500っぽいな
- うへーーーーーー
- journalctl -f
- Exception: Too many connections (MySQL::Errors::Connection)
- あらー
- mysql側か
- アプリ側って、めっちゃ接続しているんだっけ
- あー@dbがinstance_variableだ
- class_variableにしたらどうだろう
- もしくはシングルトン
- class_variableでいけたああああああああああああああああああああああああああああああああああああああああああああ
- やったあああああああああああああああああああああああああああああああああああああああ
- リファクタリングするかー
- なんかinitializeが500だぞ
- crystalをもう一回buildしたらいけた
- buildしないとだめって感じなのかな
- そうだった
- エラー文でぐぐる
- mysqlの状態を見てみる
- コネクション状態とか
- 多分、なんか接続を切るhogehogeがあるはず
- そういうオプションないんだっけ
- http://qiita.com/RyutaKojima/items/3772d695db5e2342ab47
- この3っぽさがある
- うーん、普通にmysqlコマンドだと繋げられるな
- max_allowed_packet=1G
- 変わらん
- rubyでやったらどうなるか
- いけた
- rubyで実行したあとに、crystalにしたら、crystalが動く
- しかし、普通にもう一回やると、mysqlのgoneになる
- crystal側の問題っぽいな
- なんだろう
- mysqlのライブラリ?
- closeしたら、ずっとcloseなのかな
- あー。@@dbが、前回のベンチと同じ値だ
- うーむ、なるほど
- 新しいユーザー毎に、@@dbを作成したいな
- 最初のベンチで多分閉じられたのだろう
- クラス変数はずっと持つのかー
- 一つのアプリケーションだしなー
- rubyはthreadを使用している
- goは?
- defer db.close() をしているから、ユーザーが終わったら切っているのかな
- セッション切るのと同時に、、、でもだめか、ユーザーことなったら意味ないし
- crystal-mysqlに、conn.closeがある
- https://github.com/waterlink/crystal-mysql#closing-connection
- コネクションを適切に切らないとなー
- http://blog.yuuk.io/entry/architecture-of-database-connection
- このあたりが参考になるかな
- やっぱり、コネクションを都度切るといけるな
- リファクタリングはあとにするか
- ちょっと、crystal-pgのtyped queriesを読もう
- どうやって動的に型制約しているのか
- execの部分
- src/pg/connection.cr :70
- ここが起点となっているっぽい
- 71 @pq_conn.synchronize do
- 72 Result.new(types, extended_query(query, params))
- 73 end
- まー、@pq_conn.synchronizeはほっとこう
- Result.new(types, extended_query(query, params))
- typesとbindされたクエリが渡るんだろう
- src/pg/result.cr :11
- 11 def initialize(@types : T, @res : PQ::ExtendedQuery, cache_data = true)
- 12 @decoders = @res.fields.map { |f| PG::Decoders.from_oid(f.type_oid) }
- 13 if cache_data
- 14 @raw_data = Array(Array(Slice(UInt8)?)).new
- 15 @res.get_data { |r| @raw_data << r }
- 16 else
- 17 @raw_data = Array(Array(Slice(UInt8)?)).new
- 18 end
- 19 end
- @resはクエリ文字列ではないな
- PQ::ExtendedQuery クラスだ
- src/pq/query.cr
- fields
- まぁ、fieldsだろう
- PG::Decoders.from_oid(f.type_oid)
- fieldごとのデコーダーが配列で入っているっぽいな -> @decoders
- cacheの有無
- @raw_data = Array(Array(Slice(UInt8)?)).new
- Slice(UInt8)?のArrayなのか
- 二重構造
- @res.get_data { |r| @raw_data << r }
- @resは PQ::ExtendedQuery
- get_data
- conn.read_all_data_rows { |row| yield row } ってやってる
- yieldだから、渡したブロックの引数に row が入るのか
- つまり、@raw_dataにconnのread_all_data_rowsのrowが入っていく
- @raw_dataに結果を入れて終了
- で、この結果のオブジェクトがresultに返されて、結果を取得するのは、eachとかfirstとかだよね
- rowsメソッドに絞ろう
- src/pg/result.cr :21
- 21 def rows
- 22 a = [] of typeof(first)
- 23 each(@types) { |r| a << r }
- 24 a
- 25 end
- firstってなんだ
- src/pg/result.cr :27
- eachメソッドにブロックを渡している
- そのブロックの最初のrowを返している
- self.eachだから、privateのほうかな
- eachいっぱいあるけど、引数によって呼び出し元が変わる
- first内のeachは、typesとブロックが引数だ
- なので、src/pg/result.cr :48 だ
- なぜなら、typesはTupleだからだ
- Array(PGValue) ではない
- あああああああああああああああああ
- 最初のrowのtypeofをしているのか
- なるほど
- え、そんなんでいいの?
- やるよ?
- macro文の中のyieldの最初のrowがfirstで返るっぽい
- macroの解読
- macro generate_private_each(from, to)
- {% for n in (from..to) %}
- private def each(types : Tuple({% for i in (1...n) %}Class, {% end %} Class))
- @raw_data.each do |row|
- yield ({
- {% for j in (0...n) %}
- types[{{j}}].cast(
- decode( row[{{j}}], {{j}}) ),
- {% end %}
- })
- end
- end
- {% end %}
- end
- どんなコードになるのか
- generate_private_each(1, 32) ってやってることを踏まえて
- private def each(types : Tuple(Class, Class, .. Class))
- @raw_data.each do |row|
- yield({ types[0].cast(decode(row[0], 0)), types[1].cast(decode(row[1], 1)), })
- end
- end
- うわあああああああ
- これらの1-32の全てのオーバーロードメソッドができている!
- 32個のメソッド定義じゃん!これ!
- どのパターンで来ても、大丈夫というやつか...
- 引数に Type(Class, Class)ってのは大丈夫なの?
- あああああああ!そういうことか!!
- {Int32, String, ..} とかだから、そりゃClassだわ
- 中身のyieldの処理に移ろう
- types[0]で一つ目のInt32とかを持ってきている
- castメソッドってなんだ
- https://ja.crystal-lang.org/docs/syntax_and_semantics/as.html
- asと同じだとさ
- コンパイラに、「こいつはこの型だ」と読ませる
- 他の型が来たら例外が発生する
- obj as Int32
- castはasとは逆なのかな
- Int32.cast(obj) って感じかな
- decodeってなんだっけ
- @[AlwaysInline]
- protected def decode(data, col)
- if data
- decoders[col].decode(data)
- else
- nil
- end
- end
- 第二引数はcolか!
- で、decoders[col]で、カラム番号に対応したdecoderを取得している
- で、dataをそのdecoderでデコードするのか
- なるほど
- これで、1行のTupleが返るな、firstに
- firstに1行返ってきて、その型でArrayを宣言した
- で、次は each(@types) { |r| a << r } だ
- typeとブロック
- もう一回、macroのeachが呼ばれて、今度はrecordをaに入れているだけだ
- なるほどー
- なんとなくわかった
- 32個のメソッドを作っているとか
- あとは、エラーハンドリング周りをなんかうまいことしたいなー
- できた
- あとはあれか、mysqlの返り値で、型が制限されていてほしい
- できた
- あとはどうするか
- crystal-mysqlのマルチバイト対応のPR
- crystal-mysqlのtyped_queryの対応PR
- isucon5q-crystalの整理
- エンドポイント毎にインスタンスを生成しないようにする
- envはtopレベルにしか置かない
- いや、@userとかあるから、ちょっとむりっぽい
- gitからcloneしてきて、make setupしたら動くようにする
- 準備するもの
- crystalのinstall
- systemctlの設定ファイル
- webappの下に必要なディレクトリと静的ファイルの配置
- make buildターゲット作成
- チューニング
- なんか、いい感じのスピードアップライブラリ、無い?
何回もベンチを叩くと、mysqlに接続できなくなる
接続って切ってなかったっけ?
あーprocesslist見るとやばいな
どんどん増えていく
接続切れてないな
ensureをつけたらいけた
ブログ書くかな
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment