# Fiber#join

**URL:** <https://forum.crystal-lang.org/t/fiber-join/1390>\
**Category:** Crystal Contrib\
**Created:** [November 18, 2019, 7:17am UTC](https://forum.crystal-lang.org/t/fiber-join/1390 "2019-11-18T07:17:18Z")\
**Posts on this page:** 19\
**Page:** 1

<div class="post-metadata">

**Author:** ![rogerdpack](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/rogerdpack/32/117_2.png) [@rogerdpack](https://forum.crystal-lang.org/u/rogerdpack)\
**Post date:** [November 18, 2019, 7:17am UTC](https://forum.crystal-lang.org/t/fiber-join/1390/1 "2019-11-18T07:17:18Z")

</div>

Might be nice to have a convenience method Fiber#join  
Just so we don’t have to have a “done channel” every time we want to learn when a fiber exits… 🙂  
-roger-

---

<div class="post-metadata">

**Author:** ![sol.vin](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/sol.vin/32/73_2.png) [@sol.vin](https://forum.crystal-lang.org/u/sol.vin)\
**Post date:** [November 18, 2019, 9:55am UTC](https://forum.crystal-lang.org/t/fiber-join/1390/2 "2019-11-18T09:55:22Z")

</div>

I’d rather see a `Fiber#exit?`

---

<div class="post-metadata">

**Author:** ![rogerdpack](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/rogerdpack/32/117_2.png) [@rogerdpack](https://forum.crystal-lang.org/u/rogerdpack)\
**Post date:** [November 18, 2019, 11:25pm UTC](https://forum.crystal-lang.org/t/fiber-join/1390/3 "2019-11-18T23:25:08Z")

</div>

What would exit? do? Check if it’s still running?

---

<div class="post-metadata">

**Author:** ![sol.vin](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/sol.vin/32/73_2.png) [@sol.vin](https://forum.crystal-lang.org/u/sol.vin)\
**Post date:** [November 19, 2019, 11:26am UTC](https://forum.crystal-lang.org/t/fiber-join/1390/4 "2019-11-19T11:26:52Z")

</div>

Yes

---

<div class="post-metadata">

**Author:** ![straight-shoota](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/straight-shoota/32/36_2.png) [@straight-shoota](https://forum.crystal-lang.org/u/straight-shoota)\
**Post date:** [November 19, 2019, 12:08pm UTC](https://forum.crystal-lang.org/t/fiber-join/1390/5 "2019-11-19T12:08:38Z")

</div>

That’s [`Fiber#dead?`](https://crystal-lang.org/api/0.31.1/Fiber.html#dead?-instance-method).

---

<div class="post-metadata">

**Author:** ![bcardiff](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/bcardiff/32/3_2.png) [@bcardiff](https://forum.crystal-lang.org/u/bcardiff)\
**Post date:** [November 19, 2019, 1:38pm UTC](https://forum.crystal-lang.org/t/fiber-join/1390/6 "2019-11-19T13:38:03Z")

</div>

I think they want something that will pause the current fiber until the awaited one finished. That would need some integration with the scheduler. And would allow hooking to running fibers for which the body is unknown or already set, hence it is not possible to add a `done.send`.

---

<div class="post-metadata">

**Author:** ![lbarasti](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/lbarasti/32/648_2.png) [@lbarasti](https://forum.crystal-lang.org/u/lbarasti)\
**Post date:** [November 22, 2019, 12:41am UTC](https://forum.crystal-lang.org/t/fiber-join/1390/7 "2019-11-22T00:41:00Z")

</div>

Just an experiment, far from elegant:

- define a subclass of Fiber - let’s call it `JoinableFiber` - where we
  - initialise a `done` channel at creation time
  - define a `#join` method that receives on it
  - [the hacky bit] override `#run` with a copy-pasted version from `Fiber`, where we send to `done` just before invoking `Crystal::Scheduler.reschedule`

- redefine spawn to use the new class `JoinableFiber` class.

```auto
class JoinableFiber < Fiber
  def initialize(@name : String? = nil, &@proc : ->)
    super(@name, &@proc)
    @done = Channel(Nil).new(1)
  end
  def join
    @done.receive?
  end
  def run
    GC.unlock_read
    @proc.call
  rescue ex
    if name = @name
      STDERR.print "Unhandled exception in spawn(name: #{name}): "
    else
      STDERR.print "Unhandled exception in spawn: "
    end
    ex.inspect_with_backtrace(STDERR)
    STDERR.flush
  ensure
    {% if flag?(:preview_mt) %}
      Crystal::Scheduler.enqueue_free_stack @stack
    {% else %}
      Fiber.stack_pool.release(@stack)
    {% end %}
    # Remove the current fiber from the linked list
    Fiber.fibers.delete(self)

    # Delete the resume event if it was used by `yield` or `sleep`
    @resume_event.try &.free

    @alive = false
    @done.send nil
    Crystal::Scheduler.reschedule
  end
end

```

```auto
def spawn(*, name : String? = nil, same_thread = false, &block)
  fiber = JoinableFiber.new(name, &block)
  if same_thread
    fiber.@current_thread.set(Thread.current)
  end
  Crystal::Scheduler.enqueue fiber
  fiber
end

```

Now you can run the following from you main, and see “hello” being printed!

```auto
spawn do
  puts "hello"
end.join

```

---

<div class="post-metadata">

**Author:** ![bcardiff](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/bcardiff/32/3_2.png) [@bcardiff](https://forum.crystal-lang.org/u/bcardiff)\
**Post date:** [November 22, 2019, 2:50am UTC](https://forum.crystal-lang.org/t/fiber-join/1390/8 "2019-11-22T02:50:08Z")

</div>

Note that:

- `done.send` will block until a receive is performed (mind if there are no joins executed)
- only one join will be awaken

But definitely the monkey-patch extension is a way to check if the functionality is worth it. It can probably be applied directly to `Fiber`, without subclassing it.

---

<div class="post-metadata">

**Author:** ![lbarasti](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/lbarasti/32/648_2.png) [@lbarasti](https://forum.crystal-lang.org/u/lbarasti)\
**Post date:** [November 22, 2019, 7:01am UTC](https://forum.crystal-lang.org/t/fiber-join/1390/9 "2019-11-22T07:01:24Z")

</div>

> [@bcardiff](#):
>
> - `done.send` will block until a receive is performed (mind if there are no joins executed)

`@done.send` won’t block, as `@done` has capacity 1 - I should have highlighted that.  
This means that `JoinableFiber` will complete properly, no matter if `join` is invoked or not.

---

<div class="post-metadata">

**Author:** ![straight-shoota](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/straight-shoota/32/36_2.png) [@straight-shoota](https://forum.crystal-lang.org/u/straight-shoota)\
**Post date:** [November 22, 2019, 11:10am UTC](https://forum.crystal-lang.org/t/fiber-join/1390/10 "2019-11-22T11:10:15Z")

</div>

> [@bcardiff](#):
>
> It can probably be applied directly to `Fiber` , without subclassing it.

But that would mean any fiber that is not explicitly joined will linger indefinitely.

In general, I’d prefer a solution as suggested in [[RFC] Structured Concurrency · Issue #6468 · crystal-lang/crystal · GitHub](https://github.com/crystal-lang/crystal/issues/6468) which would provide the possibility to wait for a single fiber, but offers far more features beyond that. For example you can easily wait for _n_ fibers, and can control what happens when a fiber raises.

---

<div class="post-metadata">

**Author:** ![bcardiff](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/bcardiff/32/3_2.png) [@bcardiff](https://forum.crystal-lang.org/u/bcardiff)\
**Post date:** [November 22, 2019, 12:38pm UTC](https://forum.crystal-lang.org/t/fiber-join/1390/12 "2019-11-22T12:38:17Z")

</div>

🤦‍♂️ I didn’t see it was a buffered channel. Sorry!

---

<div class="post-metadata">

**Author:** ![lbarasti](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/lbarasti/32/648_2.png) [@lbarasti](https://forum.crystal-lang.org/u/lbarasti)\
**Post date:** [November 22, 2019, 7:31pm UTC](https://forum.crystal-lang.org/t/fiber-join/1390/13 "2019-11-22T19:31:19Z")

</div>

Hey folks, I feel like I’m missing something here.

> [@straight-shoota](#):
>
> But that would mean any fiber that is not explicitly joined will linger indefinitely.

- Why would a fiber that is not explicitly joined linger indefinitely? The channel has a capacity of 1, so the fiber will not block on `send`.

> For example you can easily wait for _n_ fibers

Given a set of Fibers you want to wait on, you’d just `fibers.each { |f| f.join }`. This is what you’d do in ruby. The fact that we might be waiting on the fiber running the longest doesn’t change the outcome. But maybe you wanted something different?

---

<div class="post-metadata">

**Author:** ![asterite](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/asterite/32/60_2.png) [@asterite](https://forum.crystal-lang.org/u/asterite)\
**Post date:** [November 22, 2019, 8:09pm UTC](https://forum.crystal-lang.org/t/fiber-join/1390/14 "2019-11-22T20:09:48Z")

</div>

I don’t think we should let users play with fibers. `spawn` should return `nil`. The way to communicate and coordinate fibers is by using `spawn` + `Channel` and any other concurrency stuff we build in the future.

---

<div class="post-metadata">

**Author:** ![rogerdpack](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/rogerdpack/32/117_2.png) [@rogerdpack](https://forum.crystal-lang.org/u/rogerdpack)\
**Post date:** [November 23, 2019, 1:25am UTC](https://forum.crystal-lang.org/t/fiber-join/1390/15 "2019-11-23T01:25:31Z")

</div>

It wasn’t immediately clear to me how channels can be used for multiple listeners to know that a particular fiber has exited…especially if the fiber has already exited…

But you could write a wrapper that’s like

```auto
class FiberDone {
  @already_done = false
  @listeners = [] of Channel
  def fiber_done
    @already_done = true
    # notify all the listener channels
  end
  def join(channel)
    return if @already_done
    @listeners << channel
  end
}

```

then the fiber you want to join is responsible for calling (in an ensure?) the `#fiber_done` method. Something like this might be a good fit for the stdlib too? :)

---

<div class="post-metadata">

**Author:** ![straight-shoota](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/straight-shoota/32/36_2.png) [@straight-shoota](https://forum.crystal-lang.org/u/straight-shoota)\
**Post date:** [November 23, 2019, 10:00pm UTC](https://forum.crystal-lang.org/t/fiber-join/1390/16 "2019-11-23T22:00:49Z")

</div>

> [@lbarasti](#):
>
> Why would a fiber that is not explicitly joined linger indefinitely? The channel has a capacity of 1, so the fiber will not block on `send` .

Sorry, that statement was based on @bcardiff’s comment, which is inaccurate. So don’t mind my comment either ;)

> [@lbarasti](#):
>
> Given a set of Fibers you want to wait on, you’d just `fibers.each { |f| f.join }` . This is what you’d do in ruby. The fact that we might be waiting on the fiber running the longest doesn’t change the outcome. But maybe you wanted something different?

You can do that. But it’s inefficient. Fibers that finish quickly will block until a longer-running fiber in front of them has finished, when they could already release their stack memory.

---

<div class="post-metadata">

**Author:** ![lbarasti](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/lbarasti/32/648_2.png) [@lbarasti](https://forum.crystal-lang.org/u/lbarasti)\
**Post date:** [November 23, 2019, 10:41pm UTC](https://forum.crystal-lang.org/t/fiber-join/1390/17 "2019-11-23T22:41:01Z")

</div>

> [@straight-shoota](#):
>
> You can do that. But it’s inefficient. Fibers that finish quickly will block until a longer-running fiber in front of them has finished, when they could already release their stack memory.

I still don’t see why that’s the case, sorry. Fibers that finish quickly will just terminate, as far as I can see. But their stack memory will still be allocated? Probably just me now knowing how the internals work. I’ll try to wrap my head around this, thanks 👍

---

<div class="post-metadata">

**Author:** ![straight-shoota](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/straight-shoota/32/36_2.png) [@straight-shoota](https://forum.crystal-lang.org/u/straight-shoota)\
**Post date:** [November 23, 2019, 11:50pm UTC](https://forum.crystal-lang.org/t/fiber-join/1390/18 "2019-11-23T23:50:38Z")

</div>

> Fibers that finish quickly will just terminate, as far as I can see.

Okay, I double checked your implementation and the stack would actually be released before that.

The issue would exist when the done channel was implemented inside the fiber’s proc.

---

<div class="post-metadata">

**Author:** ![jgaskins](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/jgaskins/32/2449_2.png) [@jgaskins](https://forum.crystal-lang.org/u/jgaskins)\
**Post date:** [November 30, 2019, 3:39am UTC](https://forum.crystal-lang.org/t/fiber-join/1390/19 "2019-11-30T03:39:09Z")

</div>

This seems similar to what [`future { ... }.get`](https://crystal-lang.org/api/0.31.1/toplevel.html#future(&exp:-%3EUNDERSCORE)-class-method) does. At least, it’s what I usually use it for. :-)

```crystal
f = future { do_some_work }

do_some_other_work

f.get

```

---

<div class="post-metadata">

**Author:** ![rogerdpack](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/rogerdpack/32/117_2.png) [@rogerdpack](https://forum.crystal-lang.org/u/rogerdpack)\
**Post date:** [December 3, 2019, 5:07am UTC](https://forum.crystal-lang.org/t/fiber-join/1390/20 "2019-12-03T05:07:51Z")

</div>

Oh nice. And that uses channels behind the scenes. The only drawback is if you don’t control the fiber creation point you can’t as easily use it. But maybe enough for now, at least for my use case, thank you!

In retrospect Fiber could have like a list of channels it should send on when it “exits” (like listener joining/waiting channels) and accomplish #join reasonably easily. We’ll see if it’s ever necessary I suppose :)
