Feature #10344
closed[PATCH] Implement Fiber#raise
Added by nome (Knut Franke) about 10 years ago. Updated about 5 years ago.
Description
While it is possible to implement this in pure Ruby (by wrapping Fiber.yield and Fiber#resume), this feels like a low-level feature that ought to be provided out of the box. Also, the C implementation is more straight-forward, and more efficient. Unfortunately, it is not quite possible to implement this as a C extension module (without resorting to wrappers again); cf. the change to make_passing_arg().
Example usage:
fib = Fiber.new do
counter = 0
loop { counter += Fiber.yield }
counter
end
fib.resume
fib.resume 10
fib.resume 100
fib.raise StopIteration # => 110
Files
0001-Implement-Fiber-raise.patch (4.12 KB) 0001-Implement-Fiber-raise.patch | nome (Knut Franke), 10/09/2014 12:19 AM | ||
0001-Implement-Fiber-raise.patch (3.51 KB) 0001-Implement-Fiber-raise.patch | updated patch with some doc corrections | nome (Knut Franke), 10/18/2014 01:16 PM | |
0001-Implement-Fiber-raise-in-ext-fiber.patch (3.6 KB) 0001-Implement-Fiber-raise-in-ext-fiber.patch | variant adding Fiber#raise only in ext/fiber | nome (Knut Franke), 10/18/2014 01:16 PM |
Updated by ko1 (Koichi Sasada) about 10 years ago
Do you have good example to use it?
Updated by funny_falcon (Yura Sokolov) about 10 years ago
Yes: it is missing piece to make "greenlet" port, in other words - green threads that mimics real threads.
To accomplish that there is a need to raise exception in a fiber waiting on event if event fails.
As Knut said it could be implemented in a Ruby, but it feels to be dirty way. If implemented in C it will lead to cleaner implementation.
Updated by nome (Knut Franke) about 10 years ago
For some more sophisticated examples, see https://github.com/nome/coroutines. The library does work with vanilla Ruby, but the patch improves performance.
Also, similar code can be simplified by using Fiber#raise. Compare e.g. the two implementation of Consumer::Yielder#await at
https://github.com/nome/coroutines/blob/master/lib/coroutines/base.rb
Updated by ko1 (Koichi Sasada) about 10 years ago
On 2014/10/12 1:28, Knut.Franke@gmx.de wrote:
For some more sophisticated examples, see https://github.com/nome/coroutines. The library does work with vanilla Ruby, but the patch improves performance.
Also, similar code can be simplified by using Fiber#raise. Compare e.g. the two implementation of Consumer::Yielder#await at
https://github.com/nome/coroutines/blob/master/lib/coroutines/base.rb
I understand this feature helps some libraries. But I can't understand
why it is important.
I'm afraid that introducing such feature increases complexity of Fiber.
Basically, I want to recommend strongly that using Fiber as
semi-croutine, ristricted feature.
At least, such feature should be located at ext/fiber.
--
// SASADA Koichi at atdot dot net
Updated by funny_falcon (Yura Sokolov) about 10 years ago
At least, such feature should be located at ext/fiber.
even if ext/fiber it is still should be implemented in C .
I'm afraid that introducing such feature increases complexity of Fiber.
Not too much. Look at the patch: it uses same hook as rb_fiber_terminate
.
By the way, why Thread has this feature?
http://www.ruby-doc.org/core-2.1.3/Thread.html#method-i-raise
And why Fiber hasn't? In fact, it is strange that this feature still not
presented, cause it is expected to exists.
Updated by nome (Knut Franke) about 10 years ago
- File 0001-Implement-Fiber-raise.patch 0001-Implement-Fiber-raise.patch added
- File 0001-Implement-Fiber-raise-in-ext-fiber.patch 0001-Implement-Fiber-raise-in-ext-fiber.patch added
I understand this feature helps some libraries. But I can't understand why it is important.
Without Fiber#raise, libraries need to use some rather inelegant and inefficient workarounds (wrapping or tagging exceptions, wrapping Fiber.yield etc). Also, having Thread#raise arguably leads one to expect the same functionality in Fiber. I'm not sure whether the feature qualifies as "important", but I still think it is worth including.
Basically, I want to recommend strongly that using Fiber as semi-croutine, ristricted feature.
I think Consumer is an example of a semi-coroutine (in the sense that it uses Fiber.yield, not Fiber#transfer) that benefits from having Fiber#raise.
At least, such feature should be located at ext/fiber.
I disagree, because the feature is applicable to the restricted (semi-coroutine) fibers available without requiring 'fiber', and indeed implementable on top of them. Nevertheless, I've attached a variant of the patch which adds the method only in ext/fiber (as a compromise solution).
Updated by ko1 (Koichi Sasada) about 10 years ago
Yura Sokolov wrote:
At least, such feature should be located at ext/fiber.
even if ext/fiber it is still should be implemented in C .
I agree.
I'm afraid that introducing such feature increases complexity of Fiber.
Not too much. Look at the patch: it uses same hook as
rb_fiber_terminate
.
I don't mention about implementation, but specification.
By the way, why Thread has this feature?
http://www.ruby-doc.org/core-2.1.3/Thread.html#method-i-raise
And why Fiber hasn't? In fact, it is strange that this feature still not
presented, cause it is expected to exists.
Thread#raise is one of inter communication method (but asynchronouse, difficult, unrecommended way).
Fibers can be under control without such communication.
Updated by ko1 (Koichi Sasada) about 10 years ago
Knut Franke wrote:
I understand this feature helps some libraries. But I can't understand why it is important.
Without Fiber#raise, libraries need to use some rather inelegant and inefficient workarounds (wrapping or tagging exceptions, wrapping Fiber.yield etc).
I understand.
Also, having Thread#raise arguably leads one to expect the same functionality in Fiber. I'm not sure whether the feature qualifies as "important", but I still think it is worth including.
I don't expect there is Fiber#raise. Fiber and Thread are different.
Basically, I want to recommend strongly that using Fiber as semi-croutine, ristricted feature.
I think Consumer is an example of a semi-coroutine (in the sense that it uses Fiber.yield, not Fiber#transfer) that benefits from having Fiber#raise.
Interesting. I don't know details of this library. Could you explain why it is important in this library?
Updated by nome (Knut Franke) about 10 years ago
Koichi Sasada wrote:
I think Consumer is an example of a semi-coroutine (in the sense that it uses Fiber.yield, not Fiber#transfer) that benefits from having Fiber#raise.
Interesting. I don't know details of this library. Could you explain why it is important in this library?
Consumer provides an abstraction for (semi-)coroutines that accept (consume) values; analogous to an Enumerator, which produces (enumerates) values. Since a consumer may need to do resource cleanup and/or produce a final result, we need some way to signal end of values. I think the most natural way to do this is to raise StopIteration in the consumer: The situation is analogous to calling Enumerator#next when no more values are available, and StopIteration terminates Kernel#loop, which often allows writing consumers very concisely.
In general, I think Fiber is a powerful primitive that can be used by libraries to build abstractions like Enumerator, Consumer and others. While Enumerator does not require the #raise feature, other abstractions can very well benefit from it.
Updated by yugui (Yuki Sonoda) about 7 years ago
- Assignee set to ko1 (Koichi Sasada)
- Target version set to 2.5
Let me add another use case which I discussed with naruse and akr in RubyKaigi.
Fiber#raise
is helpful to improve compatibility of methods with blocks (internal iterators) and Enumerator
(external iterators).
This happened for me when I tried to write an adapter between two independently-developed APIs.
Here's a simplified example below. You can also find the real example in https://github.com/supership-jp/activerecord-spanner-adapter/blob/master/lib/active_record/connection_adapters/spanner/client.rb.
Example¶
Suppose that we have a third-paty API, which we cannot control.
def with_transaction
tx = Transaction.new
begin
yield tx
rescue
tx.rollback
else
tx.commit
end
end
And now I need to begin a transaction in a method and then need to commit or rollback the transaction in other methods.
class TransactionManager
def begin_transaction
@iter = Transaction.enum_for(:begin_transaction)
@tx = @iter.next # skip error handlings for simplicity
end
def commit_transaction
loop { @iter.next }
ensure
@tx = nil
end
def abort_transaction(reason = RuntimeError.new)
# ??? How can I let the iterator know the error raised
@iter.raise(reason)
end
attr_reader :tx
end
In other words, internal iterators in Ruby can manage resources in it but external iterators can't even if the user tried to take responsibility of calling cleanup mechanism.
In the real usecase, with_transaction
was an API given by google-cloud-spanner
gem and the interface of the TransactionManager
was the one I had to implement for ActiveRecord.
Proposal¶
Although I could certainly implement the adapter without the native Fiber#raise
, it would be still helpful if internal iterators and external iterators in Ruby were more consistent.
Can we have Fiber#raise
and Enumerator#raise
on top of it to improve the consistency? ko1?
FYI: I've confirmed that the patch by nome still works fine for ruby-trunk. https://github.com/yugui/ruby/commit/5a04a8b49b5086686fd75c6635c95c12ccc6caa8
Updated by ioquatix (Samuel Williams) almost 6 years ago
I think this is a good idea. I already ended up implementing it by hand, but it's messy.
Updated by ioquatix (Samuel Williams) almost 6 years ago
@ko1 (Koichi Sasada) do you think we can aim for 2.6? I don't mind reviewing this patch and merging it if you are happy with it.
Updated by ioquatix (Samuel Williams) almost 6 years ago
- Status changed from Open to Assigned
- Assignee changed from ko1 (Koichi Sasada) to ioquatix (Samuel Williams)
- Target version set to 2.7
We will experiment and aim to merge this into 2.7
Updated by ioquatix (Samuel Williams) almost 6 years ago
- Related to Bug #595: Fiber ignores ensure clause added
Updated by Anonymous almost 6 years ago
- Status changed from Assigned to Closed
Applied in changeset trunk|r66610.
Implement Fiber#raise. Fixes #10344.
This allows raising exceptions in another fiber, similarly to
Thread#raise.
Updated by ioquatix (Samuel Williams) almost 6 years ago
@nome (Knut Franke) can you review the relevant commits and give me feedback?
Updated by naruse (Yui NARUSE) almost 6 years ago
@ioquatix (Samuel Williams) Is this feature accepted by matz?
A change which is visible from Ruby and non trivial bugs requires matz's approval.
And also you need to add description to NEWS if you add new feature.
Updated by ko1 (Koichi Sasada) almost 6 years ago
naruse (Yui NARUSE) wrote:
@ioquatix (Samuel Williams) Is this feature accepted by matz?
A change which is visible from Ruby and non trivial bugs requires matz's approval.
this is a support comment.
Matz agreed this feature several years ago (maybe I'm only person against, and I also agree to introduce this feature now, except naming issue. but nobody except me has no objection :p)
I'm not sure about naming issue, but he doesn't say anything last time.
Anyway, it is safe to discuss at next dev meeting (I will speak for Samuel).
Thanks,
Koichi
Updated by ioquatix (Samuel Williams) almost 6 years ago
Thanks I didn’t know approval procedure.
I will update NEWS one I’m sure the feature has stabilised.
Updated by naruse (Yui NARUSE) almost 6 years ago
ko1 (Koichi Sasada) wrote:
naruse (Yui NARUSE) wrote:
@ioquatix (Samuel Williams) Is this feature accepted by matz?
A change which is visible from Ruby and non trivial bugs requires matz's approval.this is a support comment.
Matz agreed this feature several years ago (maybe I'm only person against, and I also agree to introduce this feature now, except naming issue. but nobody except me has no objection :p)
I'm not sure about naming issue, but he doesn't say anything last time.Anyway, it is safe to discuss at next dev meeting (I will speak for Samuel).
Sure, thank you for comment.
ioquatix (Samuel Williams) wrote:
Thanks I didn’t know approval procedure.
I will update NEWS one I’m sure the feature has stabilised.
Anyway please update NEWS now to avoid forgetting adding the description.
Updated by decuplet (Nikita Shilnikov) over 5 years ago
Shortly after I started to work on a library implementing algebraic effects (https://github.com/dry-rb/dry-effects) I stumbled upon lack of Fiber#raise
. From the user POV it is important to signal improper use of effects with a meaningful stacktrace so they can quickly detect what's wrong. It's possible to emulate #raise
with checks but it's quite inefficient and seems redundant. Thank you all folks for discussing and working on this, I hope it'll land in 2.7. If you're interested I can show a clear example but the task is almost identical to @ioquatix's one.
Updated by ioquatix (Samuel Williams) about 5 years ago
NEWS is updated.