2015/08/01

Capybara で同時に複数のリクエストを受け取った時のテストをする

Capybara を使って Rails Application のテストを書いていた時に同時にリクエストを受け取った時のテストを書きたくなったのでそのログ。

環境


  • OSX Yosemite 10.10.4
  • rbenv 0.4.0
  • ruby-build 20150719
  • Ruby 2.2.2
  • Rails 4.2.2
  • Rspec 3.3.1
  • Capybara 2.4.4
  • Postgresql 9.2.13


状況と解説と解決策


想定としては

  • ユーザが resource を new する
  • resource に対して Rails 側が unique な値を追加する
  • unique な値は DB 内では重複してはいけない
といった感じです。

とりあえずアバウトなサンプルとして
  • 本の名前と価格をユーザが入れる
  • ISBNをRails側が順番に振っていく
  • もし本の登録が無くなったらそのISBNは再利用
みたいなサンプルを書いてみました


まず、リクエストを並列に投げるには spec 内で fork しちゃうと良いみたいです
validates_uniqueness_of をすり抜ける程度には近いタイミングで実行されるようなので、同時に受け取ったテストと考えて良いかな、と思っています。
実際、 SQLite3 + validates_uniqueness_of のみを書いてある commit では、2つのリクエストを同時に投げると unique に振らないといけない ISBN に対して同じ値でレコードが2つ追加されてしまいます。


DB内で重複を排除するには unique な index を貼ることで回避してます。
そうすることで SQLite3 でも重複された値は格納されないようになるのですが
  • /Users/atton/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/sqlite3-1.3.10/lib/sqlite3/statement.rb:108:in `step':
  •  SQLite3::BusyException: database is locked: INSERT INTO "books" ("name", "price", "created_at", "updated_at") VALUES (?, ?, ?, ?)
  •  (ActiveRecord::StatementInvalid)
と言われてしまってテストがコケちゃいます。
ActiveRecord::StatementInvalid を rescue で拾ってから再格納しようとしても BusyException が出るのでうまくいかず。


ということで DB を PostgreSQL に変えてみます
そうすると、 validates_uniqueness_of をすり抜けて重複した値が入ってきた時には ActiveRecord::RecordNotUnique が返ってきます。
こいつを rescue してきちんとした値にしてやればどうにか目的は達成できました。
ちなみにちゃんと rescue するロジックを書いた場合だとテストで同時に80リクエストくらい捌けました。fork しまくったせいでメモリを数GBとか使うので80くらいが限界。


コマンドとか

Github に上げたサンプルをとりあえず動かすには
  • git clone https://github.com/atton-/sample_of_rspec_to_concurrent_requests_using_capybara.git
  • cd sample_of_rspec_to_concurrent_requests_using_capybara
  • git checkout sqlite3_and_validates_uniqueness_of
  • RAILS_ENV=test bundle exec  rake db:drop db:create db:migrate spec
    • sqlite3 と validates_uniqueness_of のみ。
    • unique であって欲しい column に同じ値が入ってしまう
  • git checkout use_postgresql
  • RAILS_ENV=test bundle exec  rake db:drop db:create db:migrate spec
    • PostgreSQL + Unique Index
    • unique であって欲しい column に同じ値は入らない
    • 同じ値を入れようとすると ActiveRecord::RecordNotUnique
  • git checkout handle_record_not_unique
  • RAILS_ENV=test bundle exec  rake db:drop db:create db:migrate spec
    • PostgreSQL + Unique Index + ActiveRecord::RecordNotUnique を rescue
    • ActiveRecord::RecordNotUnique をきちんと処理してやれば unique にしてあげられる
とかです。
PostgreSQL の設定は環境変数に設定するか database.yml を変更してください。


まとめ

  • 特定の column の値を Unique にしたかったら unique index を貼っておくと validates_uniqueness_of をすり抜けられてもDBに入るのは止められる
  • SQLite3 だと Unique index がある table に同時に insert とかすると BusyException とか出ちゃう
  • PostgreSQL だとほぼ同時なリクエストでも捌けた
  • PostgreSQL だと返ってくる exception は ActiveRecord::RecordNotUnique に変わる


気になるところ

  • Unique Index を貼る方法で Uniqueness を確保する方法って一般的なのかな
  • 特定の column の値が [1,2,3,4, 6,7,8,9] みたいな状態で 5 を取ってくる SQL ってあったっけ(今は 1 から順に ActiveRecord#exists? で回してるので O(n) なんだよなー)
  • 並列にリクエストを投げるのを SQLite3 でできると楽なんだけれど何か方法無いかな

2015/07/03

ハッカーズチャンプルー2015に行ってきた

2015/06/27(Sat) に ハッカーズチャンプルー2015 があったので参加してきました。

今年のハッカーズチャンプルーは開発合宿もあったようです。
私はカンファレンスの部ビーチパーティーの部に行ってきました。


カンファレンス

カンファレンスの部は、うちはこんな風に仕事しているよ、という紹介が多かった印象。
例えば esa.io さん の仕事の仕方だとか、 Github さんでの仕事の仕方とか。
LT も@Amothic さんのマイ20%ルールとか @Yutaka_Kinjyo さんの一度死んだ話とか。
なんだかゆるーくやっていこうぜ、みたいな話がたくさん出ているの、沖縄っぽくて良いのかなー、と思います。
あとカーゴカルトの話は耳が痛い。何も考えずに良さそうと採用するのは良くない。
その点、esa さんは自分達が考えた結果こうしている、みたいな形だったのできちんと考えているな、と。

働き方とかの話とは違うベクトルと言えば Jupyter の話
確実に再現可能なコードがレポートについて来るのは非常にうれしいことですね。(実際過去のコードが再現できなくて本人に聞いてる風景を見たことがあるので)
あとは IoT。ガジェットが KickStarter でぽんぽん出てるのは良いことだなー、と。
アジャイルに関してはスクラムリーダーどこから連れてくるのー、って質問が結構印象的でした。
最初の一人どこから来るんだ問題はどこでもあるみたいですね。


ビーチパーティー

ビーチでバーベキューでした。
学生の数が少なくて(たぶん全体で3名?)、大学内でビーチパーティ来た人では私が一番若いという状態。
ハッカーズチャンプルーの目的とは違うかもしれませんけれど、学生こないの勿体ないなー、とか思う。
学生どうにかなりませんか、という話を @k_nishijima さんに話したら学生料金にして頂けました。ありがとうございます。
実際、学生料金とかを設定すると人数の把握が難しくて金額調整が大変なんだとか。うーむ。
後から学生に何故来なかったのか聞いたところ「高かったので。学生料金なら行ってました」と答える人が数人。
あと「枠が埋まっていたので参加を諦めました」って人もいました。
そういう意味だと学生側からは学生枠の需要はありそうですね。

バーベキューをずっと手伝ってたのでいろいろ貰ったり。
学生料金の件も含めてなんだかゴネた人みたいな感じになので罪悪感。
なんらかの形でフィードバックしないとなー、とか思う。
とりあえず安直なのは来年やる時のステマで、お金が手にはいる用になったら学生バーベキュー用スポンサーでもやろうかな、とか。
これで学生枠問題が解決すると良いんだけれど。でも今お金無いです。

バーベキューしながらの立ち話では、沖縄の学生にコード書くバイトさせようよ東京の方々、みたいな話をしていました。
沖縄の大学生はコンビニとかでバイトしてる人もいて、コード書ける奴にそんなことさせるのもったいないよ、仕事振ってみたら、と。
コード書くのはリモートでも可能だし、沖縄の学生基準だと時給1000円でも十分うれしいはずなので十分振ることでメリットあるのでは、とか話してました。
あとは人が足りないみたいな話。
スクラムリーダーがいない、って話じゃないですけれど、良い感じのプログラマ/デザイナってどこに潜んでるんだろう、とかとか。
東京でもデザイナ足りていない状態らしくて、東京でも不足しているのなら人材不足って相当なもんなのかなー、と。


まとめとしては、ゆるくやろうぜー、みたいな雰囲気が漂ってるのは良いことだな、と。
私もゆるくやっていきたい所存でございます。

2015/06/09

xv6 ソースコード読み会

2015/06/05 - 2015/06/07 にxv6のソース読み会があったので参加してきました。

読んだソースコードは swetland/xv6 で 64bit 版の xv6 です。
gdb で追えるようにした話はこちら


History of CPU Architectures

OS を読む/書くにあたって CPU の仕様を見ることは避けられないので Intel CPU の歴史から。ソースを辿ったわけでは無いので間違いがあるかもしれません。
  • 4004 (4bit cpu)
  • 8080/8080a/Z80 (8bit cpu)
    • Z80 が AH/AL, BH/BL, CH/CL, DH/DL, SP, IP で register は16bit だったとか
  • 8086 (16bit cpu)
    • 16bit に拡張するためにセグメントレジスタを使った
  • i386(32/64bit cpu)
    • eax register ができた
    • セグメントレジスタの参照先を変えることでプロセス空間を変えたりできる
    • segment register を変えることで virtual memory へもアクセスできる
    • セグメントの対応表は GDT とかに入ってる
16 から 32 への拡張は命令セットはそんなに変わらなかったそうです。
8 から 16 、 32 から 64 がだいぶ変わったとか。


Intel 64 and IA-32 Architectures Software Developer Manuals

xv6 の boot は 16 bit から始まって 32bit へ拡張、最後に64bitになります。
何故16bitからスタートしているかは CPU の仕様だったりします。
ということでマニュアルを確認することに。
読んだ当時の2015年の Intel 64bit CPU の Manual はこちら
Section 2.2 Modes of Operation によると、CPU は3つのモードがあるみたいです。
  • protected mode
    • native state of processor
    • 32/64bit mode
  • real-address mode
    • 8086 processor
    • 16bit mode
    • 起動時はこのモード
  • system management mode
    • Power Management とかの System 周りをやるモード
なので xv6 の boot process は real-address mode から protected mode への移行ももちろん入っているわけです。
モードの変更方法もマニュアルに書かれていて、Section 9.9 Mode Switching の 9.9.1 Switching to Protected Mode にあります。
Interupput を disable にして、 CR0 register の PE (Protection Enable) flag をセットするとか具体的な手順が書かれています。
あと、Register の構成や GDT については Figure 2-2. System-Level Registers and Data Structures in IA-32e Mode に載っています。
仮想記憶のアドレスの切り替えやメモリ空間の拡張はセグメントレジスタの値を切り替えることによって行なっていて、その Segment Descripter の対応は GDT(Global Descripter Table)に入ってるみたいです。
なので GDT を切り替えることでアドレス空間を切り替えるような構成になってるみたいです。


Boot phase on xv6(16bit)

さてブートから読んでいきましょう、ということで kernel/bootasm.S を読みます。
.code16 から始まっているので 16bit mode で始まっていることが分かります。
cli で clear interrupt して ds,es,ss を zero fill 。
seta20.1 でinb/outb で 0xd1 と 0xdf を書き込み。
inb/outb は CPU が持ってる IO らしいです。
0xd1 は今から命令を書くよ、ってもので 0xdf は A20 を有効化する命令みたいです。
そして gdtdesc から lgdt (load gdt) して gdt をセット。
%cr0 に PE をセットして protected modeへ。
最後にljmpして 32bit mode になります。
この手順はマニュアルにそのまま書いてあったりします(Section 9-9)。

Boot phase on xv6(32bit)

protected mode になっても基本的には初期化。
32bit なので .code32 で始まっています。
register を初期化して bootmain って関数を call。
bootmain は C で書かれていて、 kernel/bootmain.c にあります。
mbheader にいろいろとベタで書かれていて、こいつは後から main に移る時に参照します。
bootmain は readseg してその値をメモリの 0x10000 に書き込みます。
readseg する場所は entry64.S が書きこまれているところです。
entry64.S を見ると分かるのですが、マジックナンバー 0x1BADB002 が書かれていて、read segした後にこいつがあるかチェックしています。
マジックナンバーが書かれてる = multi boot の entry が入ってる、ってことで entry の中身を展開して、 entry() を呼びます。
kernel/entry64.S に entry の中身が書かれていて、 page table の初期化とかをしています。
64bit mode (IA32e) にするために CR4.PAE を立てたり、 EFER.LME を立ててから
 ljmp します。
CR4.PAE は Physical Address Extension でメモリを 4GB 以上扱えるようにするもので、 64bit mode になる時に必須です。
EFER.LME の EFER は Extended Feature Enable Register でフラグを設定するレジスタです。
  LME は IE-32e Mode Enable で命令セットを64bit mode に切り替えるフラグです。(たぶん LongModeEnable)
これを有効にして ljmp すると 64bit mode になります。

Boot phase on xv6(64bit)

64bit になったらあとは main へ。
main は kernel/main.c にあります。
main では初期化を行ないます。
  • uartearlyinit: uart (console)の初期化
  • kinit1: カーネル用にメモリ確保。 freelist を作るだけ。
  • kvmalloc: カーネルのページテーブルにメモリ割り当て。
  • acpiinit: acpi を確認してCPU数をチェック。
  • pinit: プロセステーブルの初期化。 lock を作るだけ。
とかを読みました。
main は最後に mpmain を呼びます。
mpmain は CPU ごとに呼ばれます。
mpmain は最終的に scheduler に落ちます。


syscall

次に syscall を読もう、ということに。
syscall の table は kernel/syscall.h に定義されています。
syscall は trap された後に kernel/syscall.c の syscall(void) で実行されます。
trap は alltraps から呼ばれるようになっていて、 alltraps は kernel/trapasm64.S に定義されています。
trap vector は kernel/vectors.S に定義されていて、 tools/vectors64.pl で生成されるみたいです。

sys_wait

具体的なシステムコールを読もうということで wait を読むことに。
sys_wait から wait() を呼んでます。
wait は kernel/proc.c にあります。
process table を lock して、子供を ptable から探してそれが終わるまで sleep してます。
sleep は 2つの spinlock を使って status を SLEEPING にして内部で sched() しています。

sched

sched は process の切り替えみたいです。kernel/proc.c にあります。
ちゃんとロックしてるか、interrupt が enable じゃないかなどをチェックしてから swtch() します。
proc.h に定義されている proc という名前の変数には __thread が付いていて、 thread local storage みたいです。
コメントによれば Per CPU Variable とのこと。
gcc の実装はこうなっていて、 fs や gs に CPU ごとに違う値が入ってるっぽいです。
thread local な値があるので並列にプロセスを実行できるってわけですね。

swtch

kernel/swtch64.S にあります。
レジスタの中身を退避してから stack pointer を切り替えることで process を切り替えてます。
実際 sp を切り替えたあとに gdb で bt すると別の back trace が得られました。
stack pointer が変わるのでこのCPUでこの先に実行される命令が変わるわけです。

scheduler

sched を読んだのて scheduler もきちんと読もう、ということに。
scheduler() は kernel/proc.c にあります。
mpmain から呼ばれています。
sti() を使ってinterrupt を enableに。
ptable を lock して、 RUNNABLE なものを拾ってきて switchuvm。
switchuvm はたぶん switch user virtual memory で gdt を切り替え。
これでプロセスの virutal memory を切り替えているみたいですね。
その後にまた swtch する感じです。
プロセスが終わったら switchkvm で kernel virtual memory に戻ってきます。
scheduler は ptable を無限ループで見続けてます。

sys_fork

あと1つくらい syscall を読もうよ、ということで fork。
fork の本体は kernel/proc.c にあります。
allocproc で新しいプロセスを作ってます。
copyuvm() で親の virtual memory をコピってますね。
子供側の return value は0なので eax を0にして state を RUNNING に。
親側に pid を返して終わり。


感想とか

当然といえば当然なのだけれど、OSはCPUの仕様に依存していて、特にbootとかはCPU側の手順に合わせる必要があって。
Page Table とかも IA32e では CPU 側が専用命令を持っているようで、OSが handle できない部分もあるんだなー、とか。
xv6 のコードはかなり短かくて、読みやすいとか玩具みたいとかのコメントがちらほら。
個人的にはマクロとかが無いので ctags とか gdb だけで追えて割と素直だなー、と。
PC とか SP とか register とか、CPUの機構知ってないと読むの大変な気もしますが、コンピュータアーキテクチャの講義でやってるのでハードウェアとソフトウェアの繋がりが見えて良いですね。
あと gdb が stack pointer の切り替えとか thread local な値も dump できるのがだいぶ便利で助かりました。
迷ったら x/20 とかするの便利。 x/20i とか x/20s とか x/20x とか。
これだけハードウェアに近い機構もサポートしている gdb の内部も気になるかもです。
CPU Architecture に詳しくなった気がする xv6 読み会でした。

2015/05/23

64bit 版 xv6 を gdb で追う

前回 にも xv6 を読もう、ということで Mac に環境を構築したのですが、 swetland さんによる 64bit 版の xv6 があるみたいで、そっちも読めるようにしたログ。


環境
  • Host
    • Mac OSX Yosemite 10.10.3
    • Vagrant 1.7.2
    • VirtualBox 4.3.26
  • Guest
    • Ubuntu 14.04 on vagrant (chef/ubuntu-14.04)
    • qemu 1.7.91
    • gcc 4.8.2
    • gdb-multiarch 7.7


ブートの流れと gdb で素直に追えない理由

64bit 版の boot loader は memory space を 16 -> 32 -> 64 と拡張しながら起動していくみたいです
切り替えの際に命令セットが変わるので、 16bit(i8086) で attach していた gdb で b main して continute しても main で止まらなかったり、アーキテクチャの設定を間違えると gdb が文句を言ったりします。
この問題は swetland さんの xv6 の README.64bit にも書かれていて、
* gdb pukes when qemu switches from 32bit to 64bit mode
  * this made debugging the mode change entertaining
  * for now attach gdb after the switch
とのこと。
要は切り替えると gdb がダメになるので、切り替えた後に attach しなよ、と。
この切り替えポイントと gdb の attach をどうするか格闘したログ


16bit -> 32bit

boot 部分はアセンブラで直書きされているのでその辺りを読む。

out/bootblock.asm にある
ff 53 1c                call   *0x1c(%ebx)
でどうやら 16bit -> 32bit している様子。
なのでこの命令がある 0x7dc1 に break を置く。
ここは起動直後の gdb での continue で止まる。
stepi すると 0x00100020 に飛ぶ。

ちなみに 0x00100020  は mboot_entry ってやつで、ページテーブルの初期化とかしてるみたいです。
これで 32bit になったので 64bit に飛ぶところを探す。


32bit -> 64bit

メモリが32bitになったので 0x1000a4 とかが指定できる。
64bit mode にしてる部分は out/kernel.asm の
# shift to 64bit segment
 ljmp $8,$(entry64low - mboot_header + mboot_load_addr)
のところみたいです。
私の環境のアドレスだと 0x1000a4 。
ここから stepi すると
Remote 'g' packet reply is too long: 000200000000000000000.....
と言われて gdb で操作できなくなる。
直前に set architecture i386:x86-64 とかしてもダメ。
ということで、 16 -> 32 した gdb はそのままで新しい gdb を上げる。
新しい gdb で symbol-file と set arch してから target remote すると gdb を待つ状態になるので、古い gdb で continue してエラーが出てから quit 。
古い gdb は落ちて新しいの 64bit で繋がるので、これで b main とかが効くようになります。


Ubuntu 14.04 に環境を作る

  • sudo apt-get install -y gcc git qemu gdb-multiarch vim zsh
  • git clone https://github.com/swetland/xv6.git xv6-64bit
とか。
Makefile の QEMU を qemu-system-x86_64 にして、64bit build の時は X64 って環境変数を作る。
gdb で debug する時は tool/gdbinit.tmpl の symbol-file kernel を symbol-file/kernel.elf にする。
あとは
  • make 
  • make qemu-nox-gdb
してから gdb-multiarch でいけます。
最初の gdb-multiarch の .gdbinit は
echo + target remote localhost:25900\ntarget remote localhost:25900
echo + symbol-file out/kernel.elf\nsymbol-file out/kernel.elf
break *0x7dc1continuestepibreak *0x1000a4
echo + please attach another gdb.\n
とか。32bit mode での最後の ljmp までは2つめの gdb-multiarch でいけます。
2つ目の gdb-multiarch の .gdbinit は
set architecture i386:x86-64:intel
echo + target remote localhost:25900\ntarget remote localhost:25900
echo + symbol-file out/kernel.elf\nsymbol-file out/kernel.elf

とか。 2つ目を接続してから1つ目を continue すると良い。

xv6 を fork してみる

結局やることは
  • .gdbinit の書き換え
  • 16 -> 32 bit 版 gdb 用 .gdbinit
  • 32 -> 64 bit 版 gdb 用 .gdbinit
  • QEMU の設定
  • X64 を設定
とか沢山あったので、 fork してみました
3つ shell を上げて
  • (1) make
  • (1) make qemu-nox-gdb
  • (2) gdb-multiarch -x .gdbinit64 
    • please attach another gdb と出たら
  • (3) gdb-multiarch -x .gdbinit64-2
  • (2) continue
  • (2) exit
    • すると (3) の gdb が繋がるので
  • (3) b main
  • (3) continue
    • とかができます。
(3) の gdb の attach 時に 'Bogus trace status reply from target: PacketSize=1000' と出る場合もありますが、もう一度  $ gdb-multiarch -x .gdbinit64-2 すると繋がります。

メモリがきちんと64bitになっていればok。
The target architecture is assumed to be i386:x86-64:intel+ target remote localhost:259000x00000000001000e0 in ?? ()+ symbol-file out/kernel.elf(gdb) 
とかですね。
あとは main から読むなりなんなりできます。



コマンドまとめ

vagrant 上の Ubuntu 14.04 にて 3 つ shell を上げます。それぞれが(1), (2), (3) です。

(1) $ sudo apt-get install -y gcc git qemu gdb-multiarch vim zsh
(1) $ git clone https://github.com/atton-/xv6.git xv6-64bit
(1,2,3) $ cd xv6-64bit
(1) $ make
(1) $ make qemu-nox-gdb
(2) $ gdb-multiarch -x .gdbinit64
    please attach と出たら
(3) $ gdb-multiarch -x .gdbinit64-2
(2) $ continue
    して Remote 'g' packet reply is too long: 000200000000000000000.....
    と言われると思うので(2) の gdb を落とす
(3) $ b main
(3) $ continue
    main で止まります。
(3) の attach に失敗したら (3) をもう一度  gdb-multiarch -x .gdbinit64-2  で上げると良いはずです。

2015/05/14

tmux 2.0 と Ricty と East Asian Ambiguous

先日 tmux 2.0 がリリースされましたね。

最近は Terminal.app + tmuxinator を使って作業とかしてるので、 tmux もアップデート。
そうすると U+22BD(▽) とかの East Asian Ambiguous 文字の幅が tmux 上だと single になってしまう問題が発生。
tmux の上で eskk.vim を使って変換すると文字の表示がズレてしまうのでちょっと不便。

East Asian Amniguous の幅を2にする patch が tmux 1.7 の時に作ってくれた方がいるみたいなので、それを借りて自分向けに formula を作ってみました。
homebrew-customs って名前でついでに tap も作成。

$ brew tap atton-/customs
$ brew install atton-/customs/tmux

で patch が当たった版の tmux が入ります。
一応これで eskk.vim を使う分には快適に。

環境

  • Mac OSX Yosemite
  • Terminal.app 2.5.3
  • Ricty 3.2.2 (no build option)
  • tmux 2.0

蛇足

eskk.vim を使うのには快適になりましたが、 Agda の一部の文字はやっぱりズレる。
例えば U+2248(≈)とか U+2080 - U+2089 (₀₁₂₃₄₅₆₇₈₉) とか。

U+2248(≈) はフォント上では single width なのだけれど、 Width が Ambiguous
Terminal 側で Ambiguous は2にする設定をしているので、幅が2つ取られてフォントは1つ分になって無駄な空白が入っちゃう。

U+2080 - U+2089 も同じようにフォントは single width。
でも何故か U+2081 - U+2084 だけが Ambiguous で、U+2085 とかは Neutral 。
これは Unicode 作った時に何を考えてたのか謎……。

そんな感じなので、East Asian Ambiguous の幅を 2 にすると空白が出てくる文字があって、1にすると重なってしまう文字が出てくるので消化不良……。
とりあえず空白入ってても良いや、という趣旨で基本的に幅を2にしてます。

ちなみに Ricty の生成スクリプトに -a というFull Width を切ってくれるオプションがあるのだけれど、U+22BD は Full Width になってしまってる。
fontforge とかで調べてみるとそもそも Inconsolata に U+22BD のフォントが無いので、 Migu 側が使われてちゃって Full と Half がいりまじることに。
うーん。 英語は Inconsolata, 日本語は Migu 1M, Ambiguous は全部 1 or 2 のフォントって無いかな。