kanejaku.org

OkHttp の HTTP/2 並列性 - 2021/05/02

2 May 2021

Message Passing #12 で OkHttp がどうやって HTTP/2 の上にブロッキング API を作っているのか、という疑問が書いてあった。興味が出たので Android も Kotlin も分からないけれど少し調べてみた。

最初はコードを読んでいたのだけど途中でしっかりした説明があることに気づいた。ここに知りたいことは書いてあった。

基本はもりたさんが想像した通りのようだ。ソケットからの読み込みには専用のスレッドを使う。この Read 用のスレッドはアプリケーションのコードを実行しない。アプリケーションのコードはどこでブロックするか分からないから。フレームを Read した後は、アプリケーションレイヤのコードを走らせたり、Ping に対する応答などをしなきゃいけない。Read 用の専用スレッドではこれらの処理を扱えないので Runnable をキューに突っ込んで executor threads に実行してもらうようにする。

ブロッキング API は Http2Stream と Http2Writer によって実装されている。Http2Stream は wait/notify を使ってソケットから対応する HTTP/2 stream のデータが届くまでブロックする。 Http2Writer は単純にソケットに書けるまでブロックする。

凝ったことはしていなくて素直な実装であった。


最近の趣味プロジェクトとして HTTP/2 サーバを自作している。

自作 HTTP/2 サーバも OkHttp と似たアプローチを取っている: ソケットからの読み込みは専用のタスク (tokio を使っているのでスレッドではなくタスク)を使う。このタスクは HTTP/2 フレーム単位での読み込み処理のみを行う。読み込んだフレームは他のタスクへ channel 経由で渡され、ファイル IO やソケットへの書き込みは別のタスクが行う。

この自作 HTTP/2 サーバ、単純なリクエスト (navigation + subresources) をさばけるぐらいまでは動いているのだけど、Flow Control にバグがあるらしく、たまに Stall してしまう。この週末はそのバグを直そうとしていた。いくつか問題を見つけて修正したがまだ Stall するケースが残っている。

OkHttp のドキュメントには「Write が Read をブロックしてはいけない、さもなくばデッドロックが生じるかもしれない」との注意書きがある。自作 HTTP/2 サーバがデッドロックするのはこの状況に陥っているからかもしれない、と思い始めた。今の実装は TCP バッファがあふれてて Write がブロックする状況を考慮していない。

こんな風に息抜きのつもりのちょっとした寄り道がヒントをくれたりするのは楽しい。

2021/04/13

13 Apr 2021

I’ve been working hard in the past four weeks: I interviewed twice a week and wrote feedback for every interview. I became a member of a committee which makes decisions about who we hire as interns. I made progress on my own project.

I worked hard because these were fun. I enjoyed working. Really.

I feel I should try not to work so much. I was suffering from burnout last December. It’s the time to apply what I learnt from the experience.

Message Passing 11 感想 - 2021/03/26

26 Mar 2021

Design Docs の話。自分が思ったことは言い尽くされていた。

この一か月くらい合意形成のために一連の文章を書いており、個人的に関心のある話題だった。公かつ現在進行形だし、ということで思いついたことをつらつらと書いてみる。

前提として、ブラウザに Web 標準にかかわる機能を追加するには公で議論する必要がある。Explaner や Design Docs といった体裁の差はあれ、意図を文章に書き下すのは必須になる。この時点でこの手の文章を書くべきかどうか、を議論する選択肢はなくなった。悲しい。個人的には書かなくて済むならそうしたいところ。

Chromium のテンプレートには意図的に従っていない。これは強制されなければ好きなように書けばいいのでは、と思っていたから。当初は Standardization やら Considerations やらのセクションは無く、専門家から突っ込みを受けて追加した。ここは、はまじさんがいっていた、デザインの根幹にかかわる指摘を専門家から受けた事例かな、と思う。実際有難かったし、テンプレートには意味があるなあと思うところでもあった。現時点では Metrics のセクションもない。LCP を改善できるかも、とは書いているが具体的な比較手段を言及する必要がある。現状が許されているのは、件の文章は Intent to Prototype として書かれたもので、とりあえずやってみよう、という段階だから。

なぜそれをやるのか、は意識して書こうとしたのだけど、いろいろと修正していったら焦点がぼやけてしまった。なにをやらないのか、が無いのもその帰着。合意をとるのを重視したのが出ている部分だと思う。

Design のセクションの有用性は半々かなという印象を持っている。このセクションは Proof of Concept なコードをまず書いて、ある程度動く見通しを立ててから文章に落とし込んでいる部分が多い。ただ、alternatives を列挙しているトピックについてはパッチを書く前に合意をとるのに役立ったし、事前にフィードバックをもらうことで解決した問題もあった。

1 つ具体例を挙げる。ネットワークからの信頼できない情報を処理するのに、当初は権限の強いプロセスを使うことを想定していた。これはセキュリティ的に懸念があり頭を悩ませていた部分だった。お茶を濁すような文章を書いていたら、緩和策として分離された別のプロセスで処理する既存の機能を流用できることを教えてもらった。意図を文章で書くことで得られた次善案だった。

Design セクションの後半は TODO: あとで考える が多い。これはもりたさんのいう、決まってないところは無理に書かず、インクリメンタルにアップデートする、という考え方を実践してるのかもしれない。前段で問題のスコープに関して同意をとれていれば詳細について TODO を多用してもだいたい許されるように思う。裏を返せば前半に TODO が少ないのは実装が既に終わっているから。実装し終わった後に TODO を消して実装に合うように記述を修正している。

最後にこのエントリを書いている時点での進捗と進め方について。今はあるトピックについて Work in Progress なパッチを育てている。並行して、今後の大まかな方針について意識を合わせるためのドキュメントを書いている。ここでもコードが先行し、文章が後追い、という感じ。数か月後に振り返ってみて、どう帰着したか見てみると面白いかもしれない。


自分の書いたドキュメントは我ながら稚拙だなあと思う一方、一定の役割を果たしているのならそれでいいか、と割り切って書いている。

Write Code Every Day for Relief - 2021/03/23

23 Mar 2021

コードを書く話

しばらくやっていた仕事に節目がついたので次の仕事を始めたのが二月の初めのころ。

方々の合意をとるに必要なのは過不足のない文章であり、それを書くには時間がかかる。仕事で文章を書く機会が増え、相対的に余暇に割く時間をプログラミングに振っていた。

自分がプログラムを書く主な動機は自己顕示欲の発散であって、今もそれは変わっていない。でも不思議かな、公開することの無いプログラムを毎日書くのは、心の安寧を保つのに役立っている。

ベジ弁当がおいしい - 2021/02/25

25 Feb 2021

このところ Guruatsu さんのランチボックスを気に入って週に一度くらいの頻度で食べている。どれもおいしい。ちょっとお高いのだけど、ボリュームもあって食べ応え満点、というかだいたい食べきれない。全体的に炭水化物多めな印象なので実際のところヘルシーかどうかは分からない。

ランチボックスを買うついでに翌日の朝食用にマフィンやスコーンも買っている。こちらもうまい。

2021/02/22

22 Feb 2021

I don’t remember how many times I’ve done Tokio’s tutorial (probably more than five times), but I gave it another try over the weekend. Async/await APIs don’t stick in my head because I rarely have a chance to use them.

The current version of the tutorial is really good. I like the tutorial describes not only how but why.

Specifically, I found a blog post linked from the tutorial helpful.

I’m bad at understanding generic concepts. It took me a whole day to read the article even though it says “30 minute read” and shows plenty of code. It was worth spending time. I learnt a lot from the article.

I found the misconception 2 the most useful and I liked the misconception 10 the most.

Every language has gotchas.

Using idioms - 2021/02/15

15 Feb 2021

Learning a language ever lasts. Here are two phrases I learnt over the last weekend.

  • X is the new black → X is the current trend.
  • It feels like preaching to the choir → Needless to say.

These phrases come from an article which describes io_uring.

I enjoyed reading the article. The article helped me to get an overview of what io_uring is trying to solve. But on the other hand I felt that using these phrases makes the article difficult to understand unnecessary for people who use English as a second language. Is it a reasonable expectation for people who want to understand technical things also need to understand these phrases?

I’d say no.

ダークモードを試す

2 Feb 2021

このところ左目の飛蚊症に悩まされている。ディスプレイで文章を読むときに背景が白地だと気になる。

そういえば少し前にダークモードが流行っていたと思い出し、おもむろに OS やらスマホやらをダークモードに切り替えてみた。良い感じだ。気が散らない。

自分のブログもダークモードに対応させておこう。

Reducing Audio Glitches on Chrome for Android for my SPC player

1 Feb 2021

My SNES SPC player is built on top of WebAssembly + AudioWorklet. It works well with Chrome/Firefox on my desktop/laptop but produces audio glitches with Chrome on my phone (Pixel 4a). An interesting thing is that the player doesn’t produce audio glitches when it runs with Firefox on my phone.

I had a guess. I didn’t think the cause of glitches was the player was too slow to generate samples. The player’s CPU usage was quite low on desktop/laptop (<1% according to Windows Task Manager). I thought that there might be a task scheduling problem in Chrome for Android.

I used chrome://inspect/#devices to see if my guess was right.

First, let’s take a look at how much time it took to generate audio samples.

It took <1ms for each AudioWorkletProcessor#process() call. AudioWorkletProcessor#process() asks 128 samples per call (As of Feb 2021). Since the S-SMP generates a sample at 32kHz, the AudioContext’s sampleRate was set up 32000 for the player. Generating 128 samples within 1ms seems to be fast enough (128 / 32000 = 0.04 = 4ms is the hard deadline).

Zoom out to get an overview of what’s going on.

“Idle” is dominant. I couldn’t think of my player’s bad behavior to be blocked, as it doesn’t allocate any memory and doesn’t perform any IO. These findings support my guess – there seems to be a problem in the audio scheduler of Chrome for Android.

The next step is to find a solution. I searched on crbug.com to see if anyone else had the same issue. I found https://crbug.com/1014614. It looks like a similar issue but it’s the opposite.

My interpretation of the discussion is: On a low-latency path, the audio task scheduler posts tasks to generate samples more frequently and each task is asked to generate less samples. For my player the behavior doesn’t seem to work well as intended.

Then how do I get my problem fixed? The crbug.com issue mentioned latencyHint. My player doesn’t require interaction so increasing latency seems a good compromise. Setting a large latencyHint like 0.05 solves the problem. I don’t hear audio glitches anymore, yay!

I hope that I can get rid of the workaround in the future.

Rust: An easy way to count memory allocations

27 Jan 2021

Real time waits for nothing. That’s why avoiding memory allocations while generating audio samples is important. Today I implemented a quick hack to make sure my SNES SPC player does not allocate memory after the initial setup is done.

use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};

struct CheckAlloc;

static ALLOCATED: AtomicUsize = AtomicUsize::new(0);

unsafe impl GlobalAlloc for CheckAlloc {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        ALLOCATED.fetch_add(1, SeqCst);
        System.alloc(layout)
    }

    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        System.dealloc(ptr, layout);
    }
}

#[global_allocator]
static A: CheckAlloc = CheckAlloc;

#[test]
fn test_no_allocation_while_running() {
    let mut synth = Synth::new();
    synth.init();
    let before_count = ALLOCATED.load(SeqCst);
    synth.generate_samples(NUM_SAMPLES_TO_GENERATE);
    let after_count = ALLOCATED.load(SeqCst);
    assert_eq!(before_count, after_count);
}

The idea is simple. Create a custom allocator which wraps the System allocator and counts the number of allocations. After the initial setup, the number of allocations should be the same. I borrowed the idea from a blog post from redislabs.