summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Daloze <eregontp@gmail.com>2022-12-20 22:10:37 +0100
committerBenoit Daloze <eregontp@gmail.com>2022-12-20 23:05:56 +0100
commit4495dea153a097c59d56819bc827bebfbef5be3f (patch)
tree4d3ecaf69b58325d7a058d3d7e369a18b21ae850
parent39e70eef724d1c4b50a6ee894c35a3e60773671c (diff)
downloadruby-4495dea153a097c59d56819bc827bebfbef5be3f.tar.gz
Improve documentation for fiber-scoped variables
* Especially around Enumerator.
-rw-r--r--NEWS.md4
-rw-r--r--cont.c4
-rw-r--r--enumerator.c45
3 files changed, 42 insertions, 11 deletions
diff --git a/NEWS.md b/NEWS.md
index 6d4b1221ff..744cf6a580 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -103,8 +103,8 @@ Note: We're only listing outstanding class updates.
fiber. [[Feature #19078]]
Existing Thread and Fiber local variables can be tricky to use.
- Thread local variables are shared between all fibers, making it
- hard to isolate, while Fiber local variables can be hard to
+ Thread-local variables are shared between all fibers, making it
+ hard to isolate, while Fiber-local variables can be hard to
share. It is often desirable to define unit of execution
("execution context") such that some state is shared between all
fibers and threads created in that context. This is what Fiber
diff --git a/cont.c b/cont.c
index 89dcd49545..71d93cebd0 100644
--- a/cont.c
+++ b/cont.c
@@ -2153,7 +2153,7 @@ rb_fiber_storage_set(VALUE self, VALUE value)
/**
* call-seq: Fiber[key] -> value
*
- * Returns the value of the fiber-local variable identified by +key+.
+ * Returns the value of the fiber-scoped variable identified by +key+.
*
* The +key+ must be a symbol, and the value is set by Fiber#[]= or
* Fiber#store.
@@ -2176,7 +2176,7 @@ rb_fiber_storage_aref(VALUE class, VALUE key)
/**
* call-seq: Fiber[key] = value
*
- * Assign +value+ to the fiber-local variable identified by +key+.
+ * Assign +value+ to the fiber-scoped variable identified by +key+.
* The variable is created if it doesn't exist.
*
* +key+ must be a Symbol, otherwise a TypeError is raised.
diff --git a/enumerator.c b/enumerator.c
index a4e9d9a5c8..cba4369606 100644
--- a/enumerator.c
+++ b/enumerator.c
@@ -73,6 +73,8 @@
* puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" }
* # => ["0:foo", "1:bar", "2:baz"]
*
+ * == External Iteration
+ *
* An Enumerator can also be used as an external iterator.
* For example, Enumerator#next returns the next value of the iterator
* or raises StopIteration if the Enumerator is at the end.
@@ -83,15 +85,44 @@
* puts e.next # => 3
* puts e.next # raises StopIteration
*
- * Note that enumeration sequence by +next+, +next_values+, +peek+ and
- * +peek_values+ do not affect other non-external
- * enumeration methods, unless the underlying iteration method itself has
- * side-effect, e.g. IO#each_line.
+ * +next+, +next_values+, +peek+ and +peek_values+ are the only methods
+ * which use external iteration (and Array#zip(Enumerable-not-Array) which uses +next+).
+ *
+ * These methods do not affect other internal enumeration methods,
+ * unless the underlying iteration method itself has side-effect, e.g. IO#each_line.
+ *
+ * External iteration differs *significantly* from internal iteration
+ * due to using a Fiber:
+ * - The Fiber adds some overhead compared to internal enumeration.
+ * - The stacktrace will only include the stack from the Enumerator, not above.
+ * - Fiber-local variables are *not* inherited inside the Enumerator Fiber,
+ * which instead starts with no Fiber-local variables.
+ * - Fiber-scoped variables *are* inherited and are designed
+ * to handle Enumerator Fibers. Assigning to a Fiber-scope variable
+ * only affects the current Fiber, so if you want to change state
+ * in the caller Fiber of the Enumerator Fiber, you need to use an
+ * extra indirection (e.g., use some object in the Fiber-scoped
+ * variable and mutate some ivar of it).
+ *
+ * Concretely:
+ * Thread.current[:fiber_local] = 1
+ * Fiber[:scoped_var] = 1
+ * e = Enumerator.new do |y|
+ * p Thread.current[:fiber_local] # for external iteration: nil, for internal iteration: 1
+ * p Fiber[:scoped_var] # => 1, inherited
+ * Fiber[:scoped_var] += 1
+ * y << 42
+ * end
+ *
+ * p e.next # => 42
+ * p Fiber[:scoped_var] # => 1 (it ran in a different Fiber)
+ *
+ * e.each { p _1 }
+ * p Fiber[:scoped_var] # => 2 (it ran in the same Fiber/"stack" as the current Fiber)
*
- * Moreover, implementation typically uses fibers so performance could be
- * slower and exception stacktraces different than expected.
+ * == Convert External Iteration to Internal Iteration
*
- * You can use this to implement an internal iterator as follows:
+ * You can use an external iterator to implement an internal iterator as follows:
*
* def ext_each(e)
* while true