Crystal 1.18.0 is released!

We are announcing a new Crystal release 1.18.0 with several new features and bug fixes.

Pre-built packages are available on GitHub Releases and our official distribution channels. See crystal-lang.org/install for installation instructions.


This is a companion discussion topic for the original entry at https://crystal-lang.org/2025/10/14/1.18.0-released
9 Likes

I just compiled parallel code that last worked correctly with Crystal 1.15.1.

This is the parallel code section.

  cnts     = Array(UInt64).new(pairscnt, 0)  
  lastwins = Array(UInt64).new(pairscnt, 0)
  done     = Channel(Nil).new(pairscnt)
  
  threadscnt = Atomic.new(0) 
  restwins.each_with_index do |r_hi, i|
    spawn do
      lastwins[i], cnts[i] = twins_sieve(r_hi, kmin, kmax, ks, start_num, end_num, modpg, primes, resinvrs)
      print "\r#{threadscnt.add(1)} of #{pairscnt} twinpairs done"
      done.send(nil)
  end end
  pairscnt.times { done.receive }

Here’s how it was compiled:

crystal build --release -Dpreview_mt --mcpu native twinprimes_ssozgist_newrefy1new.cr

Here’s correct output using Crystal 1.15.1

➜  crystal-projects CRYSTAL_WORKERS=16 ./twinprimes_ssozgist_newrefy1new.1151 1_000_000_000_000
threads = 16
using Prime Generator parameters for P13
segment size = 2,007,040 resgroups; seg array is [1 x 31,360] 64-bits
twinprime candidates = 49,450,550,490; resgroups = 33,300,034
each of 1,485 threads has nextp[2 x 78,492] array
setup time = 0.002048 secs
perform twinprimes ssoz sieve
1485 of 1485 twinpairs done
sieve time = 8.798977 secs
total time = 8.801025 secs
last segment = 1,187,394 resgroups; segment slices = 17
total twins = 1,870,585,220; last twin = 999,999,999,960+/-1

Here’s sample output using 1.18.0.

1182 of 1485 twinpairs doneUnhandled exception in spawn: Can't transfer fd=3 to another polling event loop with pending reader/writer fibers (RuntimeError)
  from ./twinprimes_ssozgist_newrefy1new.1163 in '??'
  from ./twinprimes_ssozgist_newrefy1new.1163 in '??'
  from ./twinprimes_ssozgist_newrefy1new.1163 in '??'
  from ./twinprimes_ssozgist_newrefy1new.1163 in '??'
  from ./twinprimes_ssozgist_newrefy1new.1163 in '??'
  from ./twinprimes_ssozgist_newrefy1new.1163 in '??'
  from ???
1210 of 1485 twinpairs done^C

What do I need to change (code/compiler flags) to make it work now?

File descriptor based IOs are currently not thread safe. Your program probably fails because one thread is trying to write to STDOUT while another is still waiting on it.

As a workaround you should not print directly from each fiber. Instead you could send the summary message through the done channel and print all message from the main fiber:

  done     = Channel(String).new(pairscnt)

  threadscnt = Atomic.new(0) 
  restwins.each_with_index do |r_hi, i|
    spawn do
      lastwins[i], cnts[i] = twins_sieve(r_hi, kmin, kmax, ks, start_num, end_num, modpg, primes, resinvrs)
      done.send "\r#{threadscnt.add(1)} of #{pairscnt} twinpairs done"
    end
  end
  pairscnt.times { 
    print done.receive
  }
1 Like

That fixed the previous output messages, but now it doesn’t print out the counts for each thread. So the program just runs without the progress counts, and just ends when it finishes.

I’ve tried playing around (and will some more) to see if I can get the previous output behavior.