In a language with side-effects, I've become convinced that the only safe
way to handle asynchronous exceptions is for code to be uninterruptible by
default, and only explicitly allow interrupts at specific points (not broad
regions of code).
no doubt you've read it, but for posterity i'll post it here:
I don't think we'll get anywhere solving the problem by saying "let's just remove threads" or "don't use threads".
Threads are hard, yes. Threads are too hard for most programmers, yes. But since they're in Ruby, they're going to get used...and providing provably unsafe operations on threads is obviously not making matters better.
Besides, even if *you* avoid using threads, the libraries you call might not. So we need a global answer.
I think the specific problem mentionned was that asynchronous exceptions can abort the cleanup within an ensure section. So what about a simple solution to that specific problem, like: if an asynchronous exception is raised inside an ensure section, the exception is queued and raised at the end of the ensure section. Is that too naive?
I think this is reasonable too, especially given that we can turn
signals off using trap.
Paul
···
On Mon, Mar 24, 2008 at 09:41:29PM +0900, Tanaka Akira wrote:
So the default should be depended by kind of asynchronous
events. I think it is reasonable that signals are
interruptible by default but others are uninterruptible by
default.
This seems sensible to me, though it would be nice to also have
an alternate way to handle POSIX signals that didn't involve
interruption.
-mental
···
On Mon, 24 Mar 2008 21:41:29 +0900, Tanaka Akira <akr@fsij.org> wrote:
So the default should be depended by kind of asynchronous
events. I think it is reasonable that signals are
interruptible by default but others are uninterruptible by
default.
I think I agree. However, that still leaves us with timeouts, which
are sometimes done for operations that will be retried in the future,
so data loss is not acceptable there.
Won't we also need to augment the existing stdlib APIs to allow the
specification of timeouts for any blocking operations that can't
already be done with non-blocking and select?
-mental
···
On Mon, 24 Mar 2008 21:22:35 +0900, Tanaka Akira <akr@fsij.org> wrote:
So I think asynchronous events should be used only for
termination. This is why I think exception on blocking
operation is tolerable.
Yes. For my purposes, partial success is still success as long
as the caller can tell how far it got. Even in the absence of
signals, a successful write() isn't guaranteed to write everything
that was requested. The important thing is that the operation
doesn't report total failure when it really succeeded, or vice-versa.
Historical example: under SVR4, a write() interrupted by a signal
would always fail with EINTR, even if some bytes had already
been written. POSIX later remedied this, so that write() would
always report success if any bytes were written, even if it was
interrupted by a signal. In that instance, the API was modified
to conform to alternative #1.
Now, the question with IO and asynchronous exceptions in Ruby is
whether there is a good way to report a partial result?
-mental
···
On Thu, 20 Mar 2008 05:57:02 +0900, Paul Brannan <pbrannan@atdesk.com> wrote:
On Thu, Mar 20, 2008 at 03:50:13AM +0900, MenTaLguY wrote:
There are really two alternatives:
1. fix the operation so that it succeeds or fails atomically
2. otherwise, make the operation always non-interruptable
For I/O operations, it is typically desirable for the operation to be
interruptible, but to get the result of the partial operation after it
is interrupted. At least, that's how it works at the C level with
respect to signals.
indeed. the only sane approach, i think, is the middle one whereby threads are there, exceptions are there, but cross thread exceptions operated via channels and events.
Maybe slightly. I don't think it would necessarily be an issue for the
current implementation of 1.9, but you do also need to do the
registration/unregistration of ensure and catch clauses in an
uninterruptible fashion.
Basically you need to have the equivalent of this:
Thread.uninterruptable do
begin
Thread.interruptable do
# ...
end
rescue
# ...
ensure
# ...
end
end
(in cases where the thread is currently interruptable)
See also section 4.2 of "Asynchronous Exceptions in Haskell":
On Mon, 24 Mar 2008 22:17:34 +0900, Daniel DeLorme <dan-ml@dan42.com> wrote:
I think the specific problem mentionned was that asynchronous exceptions
can abort the cleanup within an ensure section. So what about a simple
solution to that specific problem, like: if an asynchronous exception is
raised inside an ensure section, the exception is queued and raised at
the end of the ensure section. Is that too naive?
In article <5ec9772607938ba6d0665ce1905904eb@localhost>,
MenTaLguY <mental@rydia.net> writes:
I think I agree. However, that still leaves us with timeouts, which
are sometimes done for operations that will be retried in the future,
so data loss is not acceptable there.
For net/http, data loss is not big problem because
timeouted request is not resumed. The timeout terminates
the http request.
I'm not sure your assumption. If an operation will be
resumed later, why the operation needs timeout? What a task
should be done between the timeout and the resuming? Why
the task should not be done in another thread?
Won't we also need to augment the existing stdlib APIs to allow the
specification of timeouts for any blocking operations that can't
already be done with non-blocking and select?
(For the sake of those following along who might not have a lot of
C or Unix experience, I forgot to mention that a successful write()
always returns the number of bytes actually written. The issue with
SVR4 Unix was that interrupted writes were always treated like
failures, so no count was returned in those cases and you couldn't
tell how much you had left over in case you needed to try again.)
-mental
···
On Thu, 20 Mar 2008 07:21:06 +0900, MenTaLguY <mental@rydia.net> wrote:
Yes. For my purposes, partial success is still success as long
as the caller can tell how far it got.
The existing API already returns the number of bytes successfully
written for both IO#write and IO#syswrite. But how do we find out what
caused the operation to be interrupted? Currently IO#write gives an
exception:
irb(main):001:0> r, w = IO.pipe
=> [#<IO:0x4039f734>, #<IO:0x4039f70c>]
irb(main):002:0> t = Thread.new { w.write("HELLO" * 20000) }
=> #<Thread:0x4039a0a4 run>
irb(main):003:0> r.close
=> nil
irb(main):004:0> t.join
Errno::EPIPE: Broken pipe
from (irb):2:in `write'
from (irb):4:in `join'
from (irb):4
and IO#syswrite gives the number of bytes written:
I think the behavior of IO#syswrite is correct (because there is only
one write(2) system call), but IO#write actually got *both* partial
success *and* an exceptional condition, so what should its behavior be?
Paul
···
On Thu, Mar 20, 2008 at 07:21:06AM +0900, MenTaLguY wrote:
Now, the question with IO and asynchronous exceptions in Ruby is
whether there is a good way to report a partial result?
In article <b52213a47751467b7fcc5c5e245fdba4@localhost>,
MenTaLguY <mental@rydia.net> writes:
Now, the question with IO and asynchronous exceptions in Ruby is
whether there is a good way to report a partial result?
I think defining the partial results causes, ultimately,
event driven architecture.
The partial result is similar to a continuation.
For syswrite, it is number of bytes wrote. It can be used
to resume syswrite.
If IO#gets returns a partial result on an interrupt, it
should be usable to resume IO#gets. It may contain the
bytes read. If the IO converts character code, it may
contain characters converted, a state of character code
conversion engine and the bytes not converted yet.
If http request reading procedure returns partial result on
an interrupt, it should be usable to resume the procedure.
It contains a state of http request parser. If the parser
is recursive decent, it contains the stack. It is the
continuation.
So defining the partial result of a complex I/O operation
such as http request reading is translating continuation to
some data structure. It is done by an event driven
architecture.
If a program is not event driven, I think that defining the
partial results for complex I/O operations is too tedious.
Basically you need to have the equivalent of this:
Thread.uninterruptable do
begin
Thread.interruptable do
# ...
end
rescue
# ...
ensure
# ...
end
end
Yeah that would be work. That or, as one poster put it, only use
exceptions to kill, not to raise (to snuff out the non-determinism and
force users to use queues). Both of those would work.
Good luck.
-R
That's a good point; if another thread is used most of the cases I was
thinking of are unnecessary. So I think I'm satisfied.
-mental
···
On Tue, 2008-03-25 at 19:33 +0900, Tanaka Akira wrote:
I'm not sure your assumption. If an operation will be
resumed later, why the operation needs timeout? What a task
should be done between the timeout and the resuming? Why
the task should not be done in another thread?