Feature #3251

allow to unlock mutex locked by another thread

Added by Yusuke Endoh about 5 years ago. Updated over 2 years ago.

[ruby-dev:41173]
Status:Rejected
Priority:Normal
Assignee:Koichi Sasada

Description

=begin
遠藤です。

現状では mutex は lock したスレッドからしか unlock できませんが、
これを許可するようにしませんか。動機は 2 つあります。

1) Python の condition variable が mutex を別スレッドから unlock
することで実装されている、のが真似できる
2) Thread#raise を race condition なしに使えるようになる (気がする)

1 について、Python の condition variable は以下のような感じに実装
されています。

def wait(m1)
m2 = Mutex.new
m2.lock
@waiters << m2
m1.unlock
begin
m2.lock
ensure
m1.lock
end
end

def signal
@waiters.shift.unlock
end

つまり、wait は二重に mutex を lock しようとすることでブロックし、
signal は mutex を別スレッドから unlock することでブロックしている
スレッドを再開します。
今の ConditionVariable の実装には大量の問題点が指摘されているので、
Python の真似をするとよいのではと思います。

# 権威主義

2 について、現状は Thread#raise には以下のような race が存在します。

t1: begin 節を実行している
t2: t1.raise する
t1: rescue/ensure 節の実行を開始する
t3: t1.raise する
t1: rescue/ensure 節が実行されないまま再度例外が発生する

# ちなみにこの race はシグナルにも存在します

これを、Thread#raise の前に Mutex#lock するというルールにすれば、
race を避けて使うことができるようになります。と思います。

t1: begin 節を実行している
t2: m.lock; t1.raise する
t1: rescue/ensure 節の実行を開始する
t3: m.lock が止まらないので t1.raise できない
t1: 次の例外が投げ込まれる準備ができたら m.unlock する
t3: m.lock が終わって t1.raise する

--
Yusuke Endoh mame@tsg.ne.jp
=end


Related issues

Related to Ruby trunk - Bug #4285: Ruby don't have asynchrounous exception safe syntax and It should have. Closed 01/17/2011

History

#1 Updated by Koichi Sasada about 5 years ago

=begin
 ささだです.

(2010/05/06 1:24), Yusuke Endoh wrote::

Feature #3251: allow to unlock mutex locked by another thread
http://redmine.ruby-lang.org/issues/show/3251

起票者: Yusuke Endoh
ステータス: Open, 優先度: Normal
担当者: Koichi Sasada, カテゴリ: core, Target version: 1.9.x

遠藤です。

現状では mutex は lock したスレッドからしか unlock できませんが、
これを許可するようにしませんか。動機は 2 つあります。

1) Python の condition variable が mutex を別スレッドから unlock
することで実装されている、のが真似できる
2) Thread#raise を race condition なしに使えるようになる (気がする)

 Python はなぜ Mutex を他から解放できるのか,よくわからないのですが,

http://ja.wikipedia.org/wiki/%E3%83%9F%E3%83%A5%E3%83%BC%E3%83%86%E3%83%83%E3%82%AF%E3%82%B9

狭義には、ミューテックスの場合にそれをロック(P操作)したタスクのみがア
ンロック(V操作)できるのに対して、セマフォではその様な制約はない。

ということで,Mutex という名前だと,他から解放出来ない,というニュアンス
が含まれてるんじゃないかなぁ,という感覚があります.

 他のシステムだと,セマフォがあるからいいじゃん,ということなのかもしれ
ず,じゃぁ Ruby にもセマフォを入れないといけない,ということかもしれません.

 「感覚」なので,根拠のある話ではないんですが,用語の混乱を避けるために
も,「便利そうだから入れちゃえ」というのはまずいんではないかと愚考する次
第です.Fiber とかてきとーに入れちゃった人間が言うのもなんですが.

 そもそも,Mutex は critical section を作るためのものなの
で,lock/unlock は使わないで欲しいなぁ,という気がします.な
ら,CriticalSection クラスでも作るべきなのかな.

--
// SASADA Koichi at atdot dot net

=end

#2 Updated by Motohiro KOSAKI about 5 years ago

=begin
kosakiです

ということで,Mutex という名前だと,他から解放出来ない,というニュアンス
が含まれてるんじゃないかなぁ,という感覚があります.

他のシステムだと,セマフォがあるからいいじゃん,ということなのかもしれ
ず,じゃぁ Ruby にもセマフォを入れないといけない,ということかもしれません.

「感覚」なので,根拠のある話ではないんですが,用語の混乱を避けるために
も,「便利そうだから入れちゃえ」というのはまずいんではないかと愚考する次
第です.Fiber とかてきとーに入れちゃった人間が言うのもなんですが.

思いつく限り1つだけデメリットがあります。Linuxだとmutexとsemaphoeの両方が
カーネル内にあるわけなんですが、semaphoeはそのセマンティクス上
lockdep(カーネル内ロックバリデータ)非サポートであり、近年では強く非推奨に
近いステータスとなっています。

semaphoeセマンティクスだとABBAデッドロックを検知しても第三者が、
unlockしてくれることで実はちゃんと動くかも知れないので、実用的な
lock validatorの実装がむつかしいと。

このへんは遠藤さんのほうが専門なので、ご意見いただけるとありがたいです。

=end

#3 Updated by Yusuke Endoh about 5 years ago

=begin
遠藤です。

2010年5月6日1:55 SASADA Koichi ko1@atdot.net:

 Python はなぜ Mutex を他から解放できるのか,よくわからないのですが,

snip

ということで,Mutex という名前だと,他から解放出来ない,というニュアンス
が含まれてるんじゃないかなぁ,という感覚があります.

そういえば Python では Lock という名前です。
よくわかりませんが、マニュアルを見る感じ mutex とは明確に区別されて
いるようです。さすが Python 先生。

他のシステムだと,セマフォがあるからいいじゃん,ということなのかもしれ
ず,じゃぁ Ruby にもセマフォを入れないといけない,ということかもしれません.

それなら Semaphore を作りましょうかねえ。
まあ、順序付き Map を Hash と呼ぶ Ruby なら些細な話ではないかという
気もしますが。

2010年5月6日17:09 KOSAKI Motohiro kosaki.motohiro@gmail.com:

semaphoeセマンティクスだとABBAデッドロックを検知しても第三者が、
unlockしてくれることで実はちゃんと動くかも知れないので、実用的な
lock validatorの実装がむつかしいと。

第三者は unlock は呼べませんが、Thread#raise や Thread#kill を呼ぶ
ことができるので、ABBA deadlock から抜け出せる可能性があります。
恐ろしいことに。

なので Ruby の deadlock 検出は、「生きている全スレッドがロック解放
待ち、または無期限 sleep で休止中」という、非常に保守的な判定で実装
されてます。1 つでも実行中のスレッドがあるなら deadlock を報告する
ことはありません。

--
Yusuke Endoh mame@tsg.ne.jp
=end

#4 Updated by Yusuke Endoh about 5 years ago

=begin
遠藤です。

2010年5月6日20:02 Tanaka Akira akr@fsij.org:

2010年5月6日1:24 Yusuke Endoh redmine@ruby-lang.org:

2 について、現状は Thread#raise には以下のような race が存在します。

t1: begin 節を実行している
t2: t1.raise する
t1: rescue/ensure 節の実行を開始する
t3: t1.raise する
t1: rescue/ensure 節が実行されないまま再度例外が発生する

これを、Thread#raise の前に Mutex#lock するというルールにすれば、
race を避けて使うことができるようになります。と思います。

なにか後始末が必要な処理があるとします。
たとえば、open したものは close しないといけないとして、
以下のコードを考えます。

begin
f = open(filename)
ensure
f.close
end

ここで、open が終了した後、f に代入する前にコンテキストスイッチが起こると
どうでしょうか。
コンテキストスイッチの結果、他のスレッドが動いて、上記のコードを動かしている
スレッドを raise したとします。

そうすると、ensure 節が実行されるわけですが、close はできません。
なんでかというと、open で生成した IO オブジェクトがどこにも
記録されていないからです。

これは上記の Mutex#lock を使っても防げません。
一回しか Thread#raise していないからです。

Thread.raise を受け付けたらまずい期間は mutex をロックしておけ、という
ことで解決できないでしょうか。

# Thread 1
begin
m.synchronize do
f = open(filename)
end
# ...
ensure
f.close if f
m.unlock
end

# Thread 2
m.lock
th.raise

ちなみにこの race はシグナルにも存在します

POSIX シグナルでは、受け付けたシグナルは signal handler 内でマスクされるので、
handler 内で再度そのシグナルで割り込まれる、という心配はありません。

いえ、そういう心配ではないです。
Ctrl+C を連打した場合、ensure の実行開始直後でもう一回 Interrupt が
発生してしまうと、ensure 節の中身が実行されない可能性があるのでは
ないかという心配です。

# ちゃんとは確認してないのですが、ひょっとしたら何か対策されている?

SIGINT が Interrupt 例外に自動変換されるのが問題なので、trap(:INT) など
で自力で対策すれば問題なくなると思います。

ところで、Ruby レベルの trap が実行中にシグナルを受け取ったら、別の
trap を実行してしまうことが (ささださんによると) あるらしいのですが、
まずいですかね。

--
Yusuke Endoh mame@tsg.ne.jp

=end

#5 Updated by Shyouhei Urabe over 4 years ago

  • Status changed from Open to Assigned

=begin

=end

#6 Updated by Koichi Sasada almost 3 years ago

  • Description updated (diff)

これ,どういう話で止まってるんでしたっけ....
なんか,いろんな話が混ざってる気がします.

#7 Updated by Motohiro KOSAKI almost 3 years ago

1)まず元々の提案は非同期例外を救うというモチベーションから来ているので、専用構文のほうがベターであろう。よってたぶんmutexの変更は不要
2)やるなら、semaphoeをつくるほうがいいなあ

#8 Updated by Koichi Sasada over 2 years ago

  • Status changed from Assigned to Rejected

小崎さん,ありがとうございます.
とりあえず,Mutex になんかするのは reject ということで,
すみませんが,整理して別 ticket としてください>遠藤さん

Also available in: Atom PDF