# Second fiber does not complete

**URL:** <https://forum.crystal-lang.org/t/second-fiber-does-not-complete/8523>\
**Category:** Help & Support\
**Tags:** fiber-scheduler\
**Created:** [October 31, 2025, 4:49pm UTC](https://forum.crystal-lang.org/t/second-fiber-does-not-complete/8523 "2025-10-31T16:49:59Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![axd99](https://avatars.discourse-cdn.com/v4/letter/a/22d042/32.png) [@axd99](https://forum.crystal-lang.org/u/axd99)\
**Post date:** [October 31, 2025, 4:49pm UTC](https://forum.crystal-lang.org/t/second-fiber-does-not-complete/8523/1 "2025-10-31T16:49:59Z")

</div>

Hi, I am experimenting with crystal’s fibers and to test with a buffered channel I wrote this:

```auto
# generate a list of file names.
# This is the data source.
list = [] of String
(1..4).each do |i|
  list << "file#{i}"
end

# provides communication between the producing fiber and the
# consuming one.
ch = Channel(String).new(3)

# fiber1
# this fiber loads the data into the channel
# This is the producer.
spawn do
  puts "--- Entering fiber1"
  list.each do |val|
    puts "fiber1, before send #{val}"
    ch.send val
    puts "fiber1, after send #{val}"
  end
  puts "--- Exiting fiber1"
end

# fiber2
# this fiber empties the channel
# This is the consumer.
spawn do
  puts "---- Entering fiber 2"
  while val2 = ch.receive
    puts "Received: #{val2}"
  end
  puts "---------------------" # The code does not get to here
  puts "---- Exiting fiber2"
  puts "---------------------"
end

puts "Starting...\n"

# Start the fibers
Fiber.yield

# When the control gets back here all data has been exhausted.
puts "Goodbye!!!"

# shouldn't either of these 2 restart fiber2?
Fiber.yield
ch.close 
p ch.closed?

```

Now, when I run the above, all data is produced and consumed as expected.  
However the ‘---- Exiting Fiber 2’ never executed.

This is the output:

```auto
$ crystal list-files.cr
Starting...
--- Entering fiber1
fiber1, before send file1
fiber1, after send file1
fiber1, before send file2
fiber1, after send file2
fiber1, before send file3
fiber1, after send file3
fiber1, before send file4
---- Entering fiber 2
Received: file1
Received: file2
Received: file3
Received: file4
fiber1, after send file4
--- Exiting fiber1
Goodbye!!!
true

```

Any pointer would be most welcome.  
Many thanks.

---

<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:** [October 31, 2025, 6:56pm UTC](https://forum.crystal-lang.org/t/second-fiber-does-not-complete/8523/2 "2025-10-31T18:56:41Z")

</div>

When your main fiber completes, the program exits regardless of what other fibers are doing.

> ```crystal
> # shouldn't either of these 2 restart fiber2?
> 
> ```

It resumes rather than restarts, but then `puts` in `fiber2` yields the fiber back to either the main fiber (outside of the `spawn` blocks) or `fiber1`, whichever was re-enqueued first.

* * *

I’ve updated your code to give what I believe is the desired result. If you want to ensure you consume all of the data, you need to consume synchronously (using `receive?` rather than `receive`) and only _produce_ in a background fiber rather than spinning up separate fibers for both. It’s also important that the _producer_ fiber is the one that closes the channel when it’s done producing — this is a signal to the consumer that there will be no more data coming beyond what’s already in the channel. I’ve also replaced all usage of `puts` with `Log.debug`, which doesn’t yield the fiber.

```crystal
require "log"

Log.setup :debug

# generate a list of file names.
# This is the data source.
list = [] of String
(1..4).each do |i|
  list << "file#{i}"
end

# provides communication between the producing fiber and the
# consuming one.
ch = Channel(String).new(3)

# fiber1
# this fiber loads the data into the channel
# This is the producer.
spawn do
  Log.debug { "--- Entering producer" }
  list.each do |val|
    Log.debug { "producer, before send #{val}" }
    ch.send val
    Log.debug { "producer, after send #{val}" }
  end
  Log.debug { "--- Exiting producer" }
ensure
  ch.close
end

Log.debug { "Starting...\n" }

Log.debug { "---- Starting consumer" }
while val2 = ch.receive?
  Log.debug { "Received: #{val2}" }
end
Log.debug { "---------------------" } # The code does not get to here
Log.debug { "---- Consumer finished" }
Log.debug { "---------------------" }

# When the control gets back here all data has been exhausted.
Log.debug { "Goodbye!!!" }

```

Alternatively, you can use a [`WaitGroup`](https://crystal-lang.org/api/1.18.2/WaitGroup.html) to ensure that both the producer and consumer fibers complete before moving on:

```crystal
require "wait_group"

WaitGroup.wait do |wg|
  ch = Channel(String).new(3)

  wg.spawn do
    # producer
  end

  wg.spawn do
    # consumer
  end
end
# By the time you get to here, both producer and consumer will have completed.

```

With the `WaitGroup`, it’s important that you still follow the same pattern: only close the channel after you know there will be no more data coming — this can only be known in the producer fiber. If you have multiple producers, you’ll need to wait on a second `WaitGroup` specifically for the producers before closing the channel:

```crystal
require "wait_group"
require "log"

Log.setup :debug

WaitGroup.wait do |wg|
  ch = Channel(String).new
  producer_concurrency = 10

  wg.spawn do
    # producer
    WaitGroup.wait do |producer|
      producer_concurrency.times do
        producer.spawn do
          100.times { |i| ch.send i.to_s }
        end
      end
    end
  ensure
    # The producer WaitGroup is complete by the time we get here
    ch.close
  end

  wg.spawn do
    while value = ch.receive?
      Log.debug { value }
    end
  end
end

```

---

<div class="post-metadata">

**Author:** ![axd99](https://avatars.discourse-cdn.com/v4/letter/a/22d042/32.png) [@axd99](https://forum.crystal-lang.org/u/axd99)\
**Post date:** [November 1, 2025, 11:14am UTC](https://forum.crystal-lang.org/t/second-fiber-does-not-complete/8523/3 "2025-11-01T11:14:08Z")

</div>

Many thanks for such a prompt and thorough answer.

You have provided a lot of information and explanation in there.  
This is very much appreciated.

I’m off to play with that code now 🙂

Thanks again.

---

<div class="post-metadata">

**Author:** ![axd99](https://avatars.discourse-cdn.com/v4/letter/a/22d042/32.png) [@axd99](https://forum.crystal-lang.org/u/axd99)\
**Post date:** [November 1, 2025, 11:40am UTC](https://forum.crystal-lang.org/t/second-fiber-does-not-complete/8523/4 "2025-11-01T11:40:07Z")

</div>

Thanks also for pointing this out:

_I’ve also replaced all usage of `puts` with `Log.debug`, which doesn’t yield the fiber._

How does one know how a method behave in a fiber (yield / does not yield)?

---

<div class="post-metadata">

**Author:** ![ysbaddaden](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/ysbaddaden/32/2252_2.png) [@ysbaddaden](https://forum.crystal-lang.org/u/ysbaddaden)\
**Post date:** [November 1, 2025, 7:57pm UTC](https://forum.crystal-lang.org/t/second-fiber-does-not-complete/8523/5 "2025-11-01T19:57:31Z")

</div>

Anything that triggers IO (read, write, accept, …), sleeps, and anything that communicates (channel, mutex, …) may switch control to another fiber. It’s only ever a \_may\_ because it may not have to wait or block and continue immediately.

For example receiving from an empty channel has to wait, so the current fiber will yield, but another fiber sending to the channel won’t yield because there’s a receiver waiting.

---

<div class="post-metadata">

**Author:** ![axd99](https://avatars.discourse-cdn.com/v4/letter/a/22d042/32.png) [@axd99](https://forum.crystal-lang.org/u/axd99)\
**Post date:** [November 2, 2025, 2:45pm UTC](https://forum.crystal-lang.org/t/second-fiber-does-not-complete/8523/6 "2025-11-02T14:45:27Z")

</div>

Thank you.

---

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [January 23, 2026, 3:58am UTC](https://forum.crystal-lang.org/t/second-fiber-does-not-complete/8523/7 "2026-01-23T03:58:05Z")

</div>

I don’t know why you output result no error, for me (Crystal 1.19.0 on linux), I get error like this:

```  
Starting…  
— Entering fiber1  
fiber1, before send file1  
fiber1, after send file1  
fiber1, before send file2  
fiber1, after send file2  
fiber1, before send file3  
fiber1, after send file3  
fiber1, before send file4  
---- Entering fiber 2  
Received: file1  
Received: file2  
Received: file3  
Received: file4  
fiber1, after send file4  
— Exiting fiber1  
Goodbye!!!  
true  
Unhandled exception in spawn: Channel is closed (Channel::ClosedError)  
from /home/zw963/Crystal/share/crystal/src/channel.cr:232:10 in ‘receive’  
from 1.cr:30:9 in ‘-\>’  
from /home/zw963/Crystal/share/crystal/src/fiber.cr:170:11 in ‘run’  
from /home/zw963/Crystal/share/crystal/src/fiber.cr:105:3 in ‘-\>’  
from ???  
```

This is really expected, because you are invoke “ch.receive” on closed channel.

Change to “while val2 = ch.receive?”, will work as expected, because `receive?` on closed channel return nil, but `.receive` raise error.
