TCPListenerのファイルディスクリプタを調べる

ListenしているSocketのfdを知りたいが、net.Listenerのインターフェースをみても取得する方法がないのでググったら、

http://naoina.plog.la/2013/11/12/235753683181

こんな記事をみつけた。

Socketからfdが取得できないのは、POSIX縛りになるからだと思われるが、ここで方法へどうやって辿りついたかよくわからなかったので調べてみた。

TCPListenerの定義を調べると、

// src/pkg/net/tcpsock_posix.go
type TCPListener struct {
  fd *netFD
}

となっているので、reflectパッケージを使えば、

 reflect.Indirect(reflect.Indirect(reflect.ValueOf(listener)).FieldByName("fd"))

で、fdの構造体が取得できる。

また、netFDの定義は、

// src/pkg/net/fd_unix.go
// Network file descriptor.
type netFD struct {
    // locking/lifetime of sysfd + serialize access to Read and Write methods
    fdmu fdMutex

    // immutable until Close
    sysfd       int
    family      int
    sotype      int
    isConnected bool
    net         string
    laddr       Addr
    raddr       Addr

    // wait server
    pd pollDesc
}

となっているから、

 uintptr(fdValue.FieldByName("sysfd").Int())

で、ファイルディスクリプタの値が取得できる、ということのようだ。

動いているVMにNatのポートフォワードの設定をCLIから変更する

Dockerをローカルで実行できるようになっても、ポート転送を手動で設定しないといけないのがつらいので、コマンドでやれるようにするのを調べた。

VBoxManage controlvm boot2docker-vm natpf1 "node,tcp,127.0.0.1,49160,,49160"
VBoxManage controlvm boot2docker-vm natpf1 delete node

というようにすると、ポートフォワードの設定が変更できるので、ブラウザから立ちあげたコンテナへアクセスすることができます。

boot2docker-vmのマシンが起動していないときは、

VBoxManage modifyvm -boot2docker-vm natpf1 "node,tcp,127.0.0.1,49160,,49160"
VBoxManage modifyvm -boot2docker-vm natpf1 delete node

boot2dockerへwrapperを書いてみたのだけど、既に色々このあたりを楽にしよう、というのは議論されているようなので、そのうち入るでしょう。

OSXでDockerを試す

雪がひどすぎて外出る気がしない。あー、そういえば、DockerがOSX対応したっていうし、ぼちぼち試してみるかー、と思って http://docs.docker.io/en/latest/installation/vagrant/ みてみたけど、あんまりかわってない気がする、というみなさんこんにちは。

そのページは前からあまり変わっていなくて、

https://github.com/dotcloud/docker/blob/master/docs/sources/installation/mac.rst

こっちが新しい手順らしい。

手順はそのままな気がするけど、homebrewでやればもうちょい簡単なようです。

まずは事前にVirtualBoxをInstallしておきます。

brew tap homebrew/binary
brew install docker
brew install boot2docker

とやればcurlでごにょごにょしなくてもPATHへ入れてくれます。

すると、boot2dockerとdockerというコマンドができるので、

boot2docker init
boot2docker up

とやると、VirtualBox上でDockerが動作するLinuxマシンが起動する。

ローカルのOSXのポート転送してこのVM内のDocker daemonへ飛ばすように設定されているので、
OSX側のTerminalから、

DOCKER_HOST=localhost docker run ubuntu /bin/echo "hello docker"

とかやると、あたかもOSXからそのままDockerできるようになる、という感じらしい。

DOCKER_HOST=localhostをしないと、

2014/02/08 10:57:50 dial unix /var/run/docker.sock: no such file or directory

というエラーが出るのでexportしておくとよい。

あとは、ふつうにチュートリアルをひととおりこなせばよい。

最初と同じく更新が追いついてないっぽいので直接リポジトリ側をみた方がよさげ。
https://github.com/dotcloud/docker/tree/master/docs/sources/examples


Vagrantを入れてなくてもできるようになった。Vagrant sshしなくてもよい、というぐらいではありますが、確かにお手軽に試せるようになったようです。

追記
チュートリアルとかやるときにポートにバインドする例とかあるけど、残念ながら、OSX側にはバインドされないので、boot2docker sshしてログインしたあとにバインドしたポートを叩かないといけないもよう。このあたりvirtualboxのイメージに対して名前でアクセスできて、直接ポートアクセスできる、とかであればよいんだけどな。。。

VirtualBoxのマシンの設定をいじって、Host Onlyアダプタを追加しておけば、いちいちポートフォワードしなくてよいのでよさげ。

Server-Sent Events覚え書き

なかなか書く機会はなかったのだけど、ふと使えそう!、という場面があったので、Server-Sent Eventsを書いてみたのでメモ。

レスポンスのContent-Typeはtext/event-stream

dataを組み立てる際は、改行(\r, \n, \r\n)をエスケープする。 http://www.w3.org/TR/eventsource/#parsing-an-event-stream

http://dev.ariel-networks.com/wp/archives/4168

任意に改行を入れられると、任意のイベントを定義されてしまうので、何らかの入力を元に出力するときには、改行コードのエスケープが必要。

UTF-8であること、という制約もあり、とりあえずはJSONにしておくのが扱いやすくてよさげ。
JSONにするときに改行はエンコードしてくれないことがあるっぽいので、そこを処理するか、改行で分割してからJSONにするのが良いっぽい。

idを指定しておくと、retry時にLast-Event-Idヘッダにどこまで受信したか教えつつリクエストしてくれる

reconnect/retryが仕様化されているのが非常にすばらですね。

bufferingされると少し遅延がある

frontにnginxがいて、proxy_bufferingがonのとき(デフォルトなのでだいたいのとき)は、X-Accel-Bufferingをoffもつけると遅延されなくなる。

IEはまだ使えないっぽいらしい

Android端末とかも対応する範囲は限られそう。

http://caniuse.com/eventsource

訂正: OperaOpera Mobileの勘違いでした。

HTML Media Captureの記述の仕方

webでHTML Media Captureについてググると、上の方にでてくるコンテンツはわりと、

<input type="file" accept="image/*;capture=camera" />

とか、

<input type="file" accept="image/*" capture="camera" />

とか書いてあったりするのだけど、http://www.w3.org/TR/html-media-capture/ を見たら、

partial interface HTMLInputElement {
                attribute boolean capture;
};

のようになっていて、capture属性はbooleanになっているなー、ということで、

 <input type="file" name="image" accept="image/*" capture>

のように書くらしい。

なんでだろーと思って調べてみたら、

http://www.w3.org/TR/2012/WD-html-media-capture-20120712/

のときの仕様だと、上の方の書き方で、最新のものだと、captureがbooleanになるらしい。

20120712以降で改訂ということは、それ以前のブラウザだと、上の書き方じゃないと動かなかったりすることあるのかな。。。

mod_proxy_balancerで振り先が//にならないようにする。

<Proxy balancer://app>
  BalancerMember http://192.168.0.1/
</Proxy> 

<VirtualHost *:80>
   ProxyPass / balancer://app/
</VirtualHost>

のように、BalancerMemberの定義の末尾に"/"をつけると、振り先で、//に対して振るようになってしまうっぽいので、つけないように気をつける。

<Proxy balancer://app>
  BalancerMember http://192.168.0.1
</Proxy> 

<VirtualHost *:80>
   ProxyPass / balancer://app/
</VirtualHost>

dalliはfork safeか?

ふと、dalliがunicornなどのmulti processで動作するアプリケーションで動かしたときにコネクションを張りなおす処理をしないといけないか気になったので、調べてみた。


gist8284358

これを実行してみると、

true
1
I, [2014-01-07T00:27:41.396287 #9290]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.396622 #9291]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.396905 #9292]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.397327 #9293]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.397726 #9294]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.398108 #9295]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.398495 #9296]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.399108 #9297]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.399440 #9298]  INFO -- : localhost:11211 failed (count: 0)
I, [2014-01-07T00:27:41.399961 #9299]  INFO -- : localhost:11211 failed (count: 0)
[9290, 0, 1]   
[9292, 0, 1]   
[9291, 0, 1]   
[9290, 1, 1]   
[9293, 0, 1] 


こんな感じになり、forkした直後のログで失敗した、というエラーメッセージが出るが、その後、再接続し、通信できているようだ。

dalliのソースコード(acb1ff3afd4d)をpidでgrepすると、

# lib/dalli/server.rb
    def verify_state
      failure!(RuntimeError.new('Already writing to socket')) if @inprogress
      failure!(RuntimeError.new('Cannot share client between multiple processes')) if @pid && @pid != Process.pid
    end


という処理が出てきて、newしたときと、pidが変わっているかをチェックしていることがわかる。

このverify_stateはクライアントがリクエストしようとしたときには必ず通過するようになっているので、これでpidの変更を検出しているようだ。

failureの中では、

    def failure!(exception)
      message = "#{hostname}:#{port} failed (count: #{@fail_count}) #{exception.class}: #{exception.message}"
      Dalli.logger.info { message }

      @fail_count += 1
      if @fail_count >= options[:socket_max_failures]
        down!
      else
        close
        sleep(options[:socket_failure_delay]) if options[:socket_failure_delay]
        raise Dalli::NetworkError, "Socket operation failed, retrying..."
      end
    end

のようになっていて、失敗回数を保持するカウンタを1あげつつ、例外を吐いている。

ここで発生させた例外は、

# lib/dalli/client.rb
    # Chokepoint method for instrumentation
    def perform(*all_args, &blk)
      return blk.call if blk
      op, key, *args = *all_args

      key = key.to_s
      key = validate_key(key)
      begin
        server = ring.server_for_key(key)
        ret = server.request(op, key, *args)
        ret
      rescue NetworkError => e
        Dalli.logger.debug { e.inspect }
        Dalli.logger.debug { "retrying request with new server" }
        retry
      end
    end

serverを呼びだしている元の、performメソッドで、rescueされて、retryするようだ。