Feature #8823

Run trap handler in an independent thread called "Signal thread"

Added by Koichi Sasada 8 months ago. Updated 6 months ago.

[ruby-core:56824]
Status:Rejected
Priority:Normal
Assignee:Koichi Sasada
Category:core
Target version:next minor

Description

= Abstract

How about to make an "Signal thread" to run trap handler?

= Problem

Now, all of thread synchronization methods are not permitted because there is a possibility of deadlock between trap handler and main thraed.

For example:

m = Mutex.new
trap(:INT){
m.synchronization{...}
}
m.synchronization{
...
... # recv SIGINT, and invoke trap handler
...
}

In this case, trap handler (a block passed to trap method) is run on the main thread.

= Proposal

Make a signal handler independent from main thread. If main thread and trap handler run in different threads, there are no such problem.

= Implementation

Don't create signal handler at first. But the first time we need a signal handler, signal handler is created by main thread.

See timing chart (PDF) I attached.

= Discussion

== Advantage:
* Signal thread is independent on main thread, this means that you can use thread synchronization between trap handler and main thread. In other words, you can run any program in trap handler.
* Simplify a path from sighandler to trap invocation thread (after creation of a signal thread)
* Doesn’t need a difficult implementation (modify is limited).

== Disadvantage:
* There is a small compatibility issue because “Thread.current” on a trap handler is not a main thread.

== Other Discussion:
* Create signal thread at first like timer thread is high cost. Without `trap’, we don’t need a signal thread any more.
* In signal handler and timer thread, we can’t make a signal thread because creating “Ruby thread” (== signal thread) needs GVL. So the process path from timer thread to main thread is remained.

== Other thought

I know a philosophy that `trap' should run only a tiny program, without synchronization and so on. I agree with this philosophy. Current behaveour which prohibits synchronization features helps this philosophy. But I'm not sure which is good way for Ruby.

data-signal.pdf (179 KB) Koichi Sasada, 08/27/2013 08:13 PM

data-signal-2.pdf (186 KB) Koichi Sasada, 08/27/2013 08:52 PM

History

#1 Updated by Koichi Sasada 8 months ago

I add an additonal implementation plan (called plan2) in attached PDF.

#2 Updated by Akira Tanaka 8 months ago

2013/8/27 ko1 (Koichi Sasada) redmine@ruby-lang.org:

Feature #8823: Run trap handler in an independent thread called "Signal thread"
https://bugs.ruby-lang.org/issues/8823

How about to make an "Signal thread" to run trap handler?

I think it is a right direction.

However, I know trap is used to interrupt a thread.
For example, irb raises an exception in a trap handler.

lib/irb.rb:

 trap("SIGINT") do
   irb.signal_handle
 end
 ...
 def signal_handle
   ...
   case @signal_status
   when :IN_INPUT

...
raise RubyLex::TerminateLineInput
when :INEVAL
IRB.irb
abort(self)
...
end
end

I suspect "raise RubyLex::TerminateLineInput" doesn't work as expected after
your proposal is implemented because the current thread will be the
signal thread.
It is described as the disadvantage:

== Disadvantage:
* There is a small compatibility issue because “Thread.current” on a trap handler is not a main thread.

Maybe, you should update irb (and possibly other libraries) as well as
your signal thread implementation.
--
Tanaka Akira

#3 Updated by Koichi Sasada 8 months ago

(2013/08/27 20:45), Tanaka Akira wrote:

I suspect "raise RubyLex::TerminateLineInput" doesn't work as expected after
your proposal is implemented because the current thread will be the
signal thread.

I wrote a coutnermeasure for this issue only in PDF.

All of exceptions raised in trap handler, will be sent to the main
threads (like: mainthread#kill(raisedexception)).

I think this spec reduce incompatibility problems.
What do you think about it?

--
// SASADA Koichi at atdot dot net

#4 Updated by Akira Tanaka 8 months ago

2013/8/27 SASADA Koichi ko1@atdot.net:

I think this spec reduce incompatibility problems.
What do you think about it?

I suspect that it causes race conditions.

The exception may be reached to the main thread with unexpected delay.
--
Tanaka Akira

#5 Updated by Eric Wong 8 months ago

"ko1 (Koichi Sasada)" redmine@ruby-lang.org wrote:

== Disadvantage:
* There is a small compatibility issue because “Thread.current” on a
trap handler is not a main thread.

This adds race conditions in existing code.

There is no easy way for existing code to be safe with both your
proposed implementation and the current trap implementation.
I can't safely add mutex.synchronize{} in existing trap handler.

Perhaps this version should be something like:

trap_with_thread(:INT) { ... }

== Other thought

I know a philosophy that `trap' should run only a tiny program,
without synchronization and so on. I agree with this philosophy.
Current behaveour which prohibits synchronization features helps this
philosophy. But I'm not sure which is good way for Ruby.

Alternatively, queue trap handler execution in VM and make them
uninterruptible (at Ruby-level) when running. This is less
likely incompatible, but can slow down execution of (crazy) apps
which disagree with this philosophy and do too much in trap.

#6 Updated by Motohiro KOSAKI 8 months ago

Alternatively, queue trap handler execution in VM and make them
uninterruptible (at Ruby-level) when running. This is less
likely incompatible, but can slow down execution of (crazy) apps
which disagree with this philosophy and do too much in trap.

Could you elaborate a bit more?
If you mean synchronize{} automatically disable interrupt, it doesn't work when using
cond-wait.

mutex.lock
cond.wait <- thread is here. and interrupt never be happen.
mutex.unlock

If you mean, trap handler automatically disable nested trap, we already do since 1.9.3.

#7 Updated by Motohiro KOSAKI 8 months ago

If you mean, trap handler automatically disable nested trap, we
already do since 1.9.3.

That's what I meant, oops :x

Yay, I was cool! :)

#8 Updated by Koichi Sasada 8 months ago

  • Status changed from Open to Feedback

After discussion, I decide to reject this feature.

How about to permit Queue operation in trap handler with [ruby-trunk - Feature #3620], and use Queue#push to synchronize/communicate between other threads. This is Ruby-level alternative of pipe hack.

Comments are welcome.

#9 Updated by Akira Tanaka 8 months ago

2013/8/29 ko1 (Koichi Sasada) redmine@ruby-lang.org:

How about to permit Queue operation in trap handler with [ruby-trunk - Feature #3620], and use Queue#push to synchronize/communicate between other threads. This is Ruby-level alternative of pipe hack.

I'm not sure how to use it.

The pipe hack is usable because we can use select/poll system call
to multiplex file descriptors.

How can an application wait multiple queues or a queue and other
blocking operations?
polling, threads, or other?

I think you should write toy example application.
--
Tanaka Akira

#10 Updated by Koichi Sasada 8 months ago

(2013/08/30 10:45), Tanaka Akira wrote:

I think you should write toy example application.

For example:

def trapinthread(sig, &b)
q = Queue.new
Thread.new{
q.pop
b.call
}
trap(sig){
q.push true
}
end

--
// SASADA Koichi at atdot dot net

#11 Updated by Koichi Sasada 7 months ago

  • Target version changed from 2.1.0 to next minor

Now, Queue is trap-safe.

#12 Updated by Koichi Sasada 6 months ago

  • Status changed from Feedback to Rejected

Also available in: Atom PDF