読者です 読者をやめる 読者になる 読者になる

clojureでのref実装について(今後の課題編)

と、ensureを忘れてたので積み残しとして読んでいきます。core.clj読む感じだと多分こいつが重要ぽい奴らの中では最後のやつのはず。あと、今後の課題を整理します。

では、ensureから。

(defn ensure
  "Must be called in a transaction. Protects the ref from modification
  by other transactions.  Returns the in-transaction-value of
  ref. Allows for more concurrency than (ref-set ref @ref)"
  {:added "1.0"}
  [^clojure.lang.Ref ref]
    (. ref (touch))
    (. ref (deref)))

ensureしたrefのオブジェクトについては、他のトランザクションで値を更新できなくします。で、in-transaction-valueを返します。(ref-set ref @ref)とかするより平行性高いよ、と。

touchしてderefしているので、それをみてみます。雰囲気では、doSetしてないので平行性高そう。derefはみたので、Ref#touchですね。

public void touch(){
	LockingTransaction.getEx().doEnsure(this);
}

LockingTransaction#doEnsureに委譲しています。

void doEnsure(Ref ref){
	if(!info.running())
		throw retryex;
	if(ensures.contains(ref))
		return;
	ref.lock.readLock().lock();

	//someone completed a write after our snapshot
	if(ref.tvals != null && ref.tvals.point > readPoint) {
        ref.lock.readLock().unlock();
        throw retryex;
    }

	Info refinfo = ref.tinfo;

	//writer exists
	if(refinfo != null && refinfo.running())
		{
		ref.lock.readLock().unlock();

		if(refinfo != info) //not us, ensure is doomed
			{
			blockAndBail(refinfo); 
			}
		}
	else
		ensures.add(ref);
}

1. 別のトランザクションがコミットに成功していたら、リトライ
2. refに対して書き込みしようとしているトランザクションがあれば、対象のref.infoを取得しblockAndBail
3. refに対して書き込みしようとしているトランザクションがなければ、素直にensuresに追加してensureの管理対象にします


LockingTransaction#blockAndBailはこんな感じで、ref.info.latchで待機させます。Bailは、ロックの所有権を移転してます的な意味かな、たぶん。

private Object blockAndBail(Info refinfo){
//stop prior to blocking
	stop(RETRY);
	try
		{
		refinfo.latch.await(LOCK_WAIT_MSECS, TimeUnit.MILLISECONDS);
		}
	catch(InterruptedException e)
		{
		//ignore
		}
	throw retryex;
}

ブロックというか、ensureするrefのinfoのラッチを待機させる実装です。「他のトランザクションから更新させなくする」というよりは、内部的な表現では「100msだけそのrefに対する他のトランザクションの更新をさせないようにする」という感じですかね。



では今後の課題。中編あたりでリストした課題を見ながら考えます。

・ARef/Fnなどの、clojure全体のオブジェクトモデルを理解しておく必要もありそう。making氏が良さげな資料をもってたので、今度教えてもらう。
making氏は今日くるかと思ったらきてない。「clojure全体のオブジェクトモデル」はおいおいですな。

・TValのhistoryを循環リストで表現している理由は?
さあ?なんかたどりやすいから?いちおう、行ったり来たりする可能性のある実装ではある。haskellだとTVarとかいってもってるので、多分そっちみればわかる。

・ポイントにはいろんな種類があるけど、どう使っているのか?read pointなんかはread write lockで代替できるような。
わかりません。異常系を考えながらオブジェクト図を書いてみるという壮絶な作業が。。

・上記の考え方の根拠はなにか?論文なんかはどこかに転がってないか?
haskellのSTMをあたってみるのがよさそう。

・commuteがによって、isolation levelが大幅に下がることになる様に思えるがが、その理解は正しいか?
そうでもないかも。まあそういう前提で使えって書いてあるし。

・commuteは参照の切り替えではなく関数適用を行うもの。遅延評価のセマンティクスが役に立っているように思うが、そこのところはどうなのか?
なんかそんな雰囲気だ。commuteはhaskellにはないっぽいので、どこか別なアイディア元があるのかも。あと、haskellにはretryがあったり。というか純粋関数型で実行順序とかないはずなのにretryってどうよ?という説が。というか、状態もたないはずなのにSTMってどういうことなのか説も。

・commuteを使用することによってパフォーマンスが向上することは考えられるか?事実上、トランザクションが競合しないのであれば無効なだけか?関数適用の競合が頻発するケースでもcommuteのアプローチではなくserializableな分離レベルを採用すれば再実行が多発することはないのではないか?
いちおう、commuteはref-set/alter以降に先送りされているので、再実行時もトランザクション全体のパフォーマンスはよさそう。serializableにしても、ref-set/alterはスキップできないので、実行順序が重要でない関数適用があるのであればつかってもいいと思う。

・というか、STMのトランザクションに分離レベルという概念はあるのか?
あるっぽい。oracleでいうところのseliarizable相当。snapshot isolationで実装されている。

・そもそも、STMのアプローチが有効な領域はあるのか?ユーザーインターフェイスくらい?
わかんね。他のSTMがどういう発想かはわからないけど、clojureのSTMはACIでDは保証してないので(多分STMってだいたいみんなそんな感じなのだと思う)、STMとDBのtxを連携させたりするのは難しそう。1ノードだけで動作するメモリキャッシュとかだったらこれで実装できるとか思ったけどしょぼすぎて泣けてくる。

haskellでもSTMが実装されているはずだが、その実装との違いは?
さっきの論文を読んでみよう。あとhaskellのコードも読んでみよう。

clojureのMLとかで聞いてみればいいということにこの時点で気づくなど。
MLあさってメモっておきました。commuteの話は聞きたかったけど、なんかレスがついてなかったりしたので、とりあえず先送りする感じで。


今後やっていくこと。
clojure全体のオブジェクトモデルを理解する
・異常系を考えながらオブジェクト図を書いてみるという壮絶な作業をしてみる
haskellのSTMの論文を読む



ということで、がんばってhaskellを勉強します!