# \[ANN\] nbfifo-0.0.0 - non blocking fifos for threads

**URL:** <https://rubytalk.org/t/ann-nbfifo-0-0-0-non-blocking-fifos-for-threads/21043>\
**Category:** ruby-talk\
**Created:** [23 September 2005 22:57 UTC](https://rubytalk.org/t/ann-nbfifo-0-0-0-non-blocking-fifos-for-threads/21043 "2005-09-23T22:57:06Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Ara.T.Howard6](https://avatars.discourse-cdn.com/v4/letter/a/958977/32.png) [@Ara.T.Howard6](https://rubytalk.org/u/Ara.T.Howard6)\
**Post date:** [23 September 2005 22:57 UTC](https://rubytalk.org/t/ann-nbfifo-0-0-0-non-blocking-fifos-for-threads/21043/1 "2005-09-23T22:57:06Z")

</div>

URIS

&nbsp;&nbsp;&nbsp;http::/codeforpeople.com/lib/ruby/nbfifo

NAME

&nbsp;&nbsp;&nbsp;nbfifo.rb

SYSNOPSIS

&nbsp;&nbsp;&nbsp;thread and process safe non-blocking fifos for ruby.

&nbsp;&nbsp;&nbsp;when using fifos from ruby threads it is extremely easy to block the entire  
&nbsp;&nbsp;&nbsp;process via some operation done on a fifo such as opening it or  
&nbsp;&nbsp;&nbsp;reading/writing to/from it. rbfifo.rb is a proof-of-concept class showing  
&nbsp;&nbsp;&nbsp;how fifos can be used from a ruby multi-threaded process in a way that  
&nbsp;&nbsp;&nbsp;ensures no threaded operation will block the entire process. as an added  
&nbsp;&nbsp;&nbsp;benefit the nbfifo class sets up fifos in such a way that they are durable  
&nbsp;&nbsp;&nbsp;across process invocations so that a server may leave a connection open  
&nbsp;&nbsp;&nbsp;while clients come and go and clients do not even block waiting for a server  
&nbsp;&nbsp;&nbsp;to be on the other end of a fifo opened for writing. another feature is  
&nbsp;&nbsp;&nbsp;that an eof signal can be sent down the fifo to signal end-of-file to  
&nbsp;&nbsp;&nbsp;clients, who will have EOFError raised in there code, however they can  
&nbsp;&nbsp;&nbsp;catch this error and simply reset the fifo to begin reading again. the  
&nbsp;&nbsp;&nbsp;relationship of readers and writers is such that they may even exist in  
&nbsp;&nbsp;&nbsp;the same process without deadlocking.

CONCEPT

&nbsp;&nbsp;&nbsp;opening a fifo in 'r+' mode do not block as opening them with only 'r' or  
&nbsp;&nbsp;&nbsp;'w' does. nbfifo opens all fifos in this way to avoid blocking a process on  
&nbsp;&nbsp;&nbsp;an open. the reading and writing protocol 'blocks' in the sense that the  
&nbsp;&nbsp;&nbsp;application does not need to handle EAGAIN or some such but not in a way  
&nbsp;&nbsp;&nbsp;that blocks an entire process - the blocking is thread local. therefore all  
&nbsp;&nbsp;&nbsp;one need do to accomplish non-blocking io is operate in ruby threads (which  
&nbsp;&nbsp;&nbsp;are normally quite prone to a process-blocking operation killing all  
&nbsp;&nbsp;&nbsp;threads).

&nbsp;&nbsp;&nbsp;the writing is done as follows:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- never write more than PIPE\_MAX bytes at a time to ensure atomicity. you  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;don't worry about this - data is chunked by the api for you.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- each msg is preceded by a byte indicating it's length. the length plus  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;the msg is less that PIPE\_MAX when combined.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- select is called before writing

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- when select returns the msg is sent

&nbsp;&nbsp;&nbsp;the reading is done as follows:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- select is called to avoid reading 'no-data'. when pipes are opened in  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'r+' mode a blind read with no data waiting will return the emtpy string  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;("") and the application would end up busy-looping on this value unless  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;select is used.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- when select returns the number of bytes is read. then the message is  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;read.

EXAMPLES

> **···**
>
> ------------------------------------------  
> &nbsp;&nbsp;&nbsp;here's the producer process:  
> &nbsp;&nbsp;&nbsp;------------------------------------------
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;jib:~/eg/ruby/nbfifo/nbfifo-0.0.0 \> cat producer.rb  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;require 'nbfifo'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;STDOUT.sync = true  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$VERBOSE = nil
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# die nicely on getchar  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;trap('INT'){ exit }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread::new(){ Thread::main.exit if select [STDIN]}
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# this guy will stay running  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread::new{ loop{ puts "random producer thread stays running..."; sleep 1 }}
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# loop doing an open, which does not block, and the sending two virtual  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# doccuments down the pipe but following the first with eof  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loop do  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread::new do  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nbfifo = NBFifo::new 'fifo'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2.times{ nbfifo.send Time::now; sleep 1 }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nbfifo.send\_eof  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2.times{ nbfifo.send Time::now; sleep 1 }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end.join  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;------------------------------------------  
> &nbsp;&nbsp;&nbsp;here's the concumer process  
> &nbsp;&nbsp;&nbsp;------------------------------------------
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;jib:~/eg/ruby/nbfifo/nbfifo-0.0.0 \> cat consumer.rb  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;require 'nbfifo'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;STDOUT.sync = true  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$VERBOSE = nil
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# die nicely on getchar  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;trap('INT'){ exit }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread::new(){ Thread::main.exit if select [STDIN]}
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# this guy will stay running  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread::new{ loop{ puts "random consumer thread stays running..."; sleep 1 }}
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# just loop opening fifo, which does not block, and reading whatever data  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# can be read until end-of-file is hit. when it is reset and go again  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loop do  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread::new do  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nbfifo = NBFifo::new 'fifo'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2.times{ puts nbfifo.recv }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rescue EOFError  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nbfifo.reset  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;retry  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end.join  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;------------------------------------------  
> &nbsp;&nbsp;&nbsp;running the consumer  
> &nbsp;&nbsp;&nbsp;------------------------------------------
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;jib:~/eg/ruby/nbfifo/nbfifo-0.0.0 \> ruby consumer.rb  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;random consumer thread stays running...  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Fri Sep 23 15:35:31 MDT 2005  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Fri Sep 23 15:35:32 MDT 2005  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;random consumer thread stays running...  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Fri Sep 23 15:35:35 MDT 2005
> 
> &nbsp;&nbsp;&nbsp;------------------------------------------  
> &nbsp;&nbsp;&nbsp;running the producer  
> &nbsp;&nbsp;&nbsp;------------------------------------------
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;jib:~/eg/ruby/nbfifo/nbfifo-0.0.0 \> sleep 3; ruby producer.rb  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;random producer thread stays running...
> 
> &nbsp;&nbsp;&nbsp;------------------------------------------  
> &nbsp;&nbsp;&nbsp;running the consumer after killing it  
> &nbsp;&nbsp;&nbsp;------------------------------------------
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;jib:~/eg/ruby/nbfifo/nbfifo-0.0.0 \> ruby consumer.rb  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;random consumer thread stays running...  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Fri Sep 23 15:43:15 MDT 2005  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Fri Sep 23 15:43:17 MDT 2005  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;random consumer thread stays running...  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Fri Sep 23 15:43:26 MDT 2005
> 
> &nbsp;&nbsp;&nbsp;note that killing the consumer and restarting did not affect the producer at  
> &nbsp;&nbsp;&nbsp;all. this is quite different than the way normal fifo operation is - in that  
> &nbsp;&nbsp;&nbsp;case the reader must always open the fifo after a writer is on the other end  
> &nbsp;&nbsp;&nbsp;and a writer - even if opening using O\_NONBLOCK will explode if no reader is  
> &nbsp;&nbsp;&nbsp;on the other end. using this method neither open (on either end) will block  
> &nbsp;&nbsp;&nbsp;but subsequent reads/writes will, but in a way that blocks only thread and not  
> &nbsp;&nbsp;&nbsp;the entire process. in fact, you can easily open up a fifo for reading and  
> &nbsp;&nbsp;&nbsp;writing in the same process, regardless of the order they are opened, and  
> &nbsp;&nbsp;&nbsp;easily write well behaved threads that use this channel to communicate:
> 
> &nbsp;&nbsp;&nbsp;------------------------------------------  
> &nbsp;&nbsp;&nbsp;running a producer and consumer at once  
> &nbsp;&nbsp;&nbsp;------------------------------------------
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;jib:~/eg/ruby/nbfifo/nbfifo-0.0.0 \> cat producer\_and\_consumer.rb  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;require 'nbfifo'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;STDOUT.sync = true  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$VERBOSE = nil
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;trap('INT'){ exit }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread::new(){ Thread::main.exit if select [STDIN]}
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;producer =  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread::new do  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nbfifo = NBFifo::new 'fifo'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loop{ nbfifo.send Time::now; sleep 1 }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;consumer =  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread::new do  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nbfifo = NBFifo::new 'fifo'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loop{ puts nbfifo.recv }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sleep
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;jib:~/eg/ruby/nbfifo/nbfifo-0.0.0 \> ruby producer\_and\_consumer.rb  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Fri Sep 23 15:47:13 MDT 2005  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Fri Sep 23 15:47:14 MDT 2005  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Fri Sep 23 15:47:15 MDT 2005  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Fri Sep 23 15:47:16 MDT 2005  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Fri Sep 23 15:47:17 MDT 2005
> 
> &nbsp;&nbsp;&nbsp;note that this program would deadlock instantly using fifos in the normal way  
> &nbsp;&nbsp;&nbsp;('r', or 'w') and would blow up with Errno::ENXIO if File::NONBLOCK were  
> &nbsp;&nbsp;&nbsp;specified to the open and the writer thread attempted the open before the  
> &nbsp;&nbsp;&nbsp;reader. the other issue with using File::NONBLOCK for fifos is that reads  
> &nbsp;&nbsp;&nbsp;give the empty string ("") whether we're at the end-of-file or if there is  
> &nbsp;&nbsp;&nbsp;simply no data and we're forced into a busy loop.
> 
> CAVEATS
> 
> &nbsp;&nbsp;&nbsp;like all my code these days - this is experimental. you io gurus out there  
> &nbsp;&nbsp;&nbsp;will no doubt have comments and, for you, this process may seem easy (that's  
> &nbsp;&nbsp;&nbsp;you tanaka) but it was giving me fits so i decided to wrap it up.
> 
> &nbsp;&nbsp;&nbsp;i've run the code on linux only. i'd be grateful for some bsd and solaris  
> &nbsp;&nbsp;&nbsp;testers. also, i played around with gunwin32 utils (for mkfifo) on windows  
> &nbsp;&nbsp;&nbsp;and couldn't get it worked - but it was close. please contact me off/online  
> &nbsp;&nbsp;&nbsp;if you have helpful info in this area.
> 
> WHY?
> 
> &nbsp;&nbsp;&nbsp;say you were writing a fastcgi replacement and wanted a multi-threaded pure  
> &nbsp;&nbsp;&nbsp;ruby server which could handled multiple incming requests without blocking  
> &nbsp;&nbsp;&nbsp;the entire process opening/reading/writing to/from fifos. then you'll  
> &nbsp;&nbsp;&nbsp;really need something like this.
> 
> AUTHOR
> 
> &nbsp;&nbsp;&nbsp;ara.t.howard@noaa.gov
> 
> # -a --
> 
> > email :: ara [dot] t [dot] howard [at] noaa [dot] gov  
> > phone :: 303.497.6469  
> > Your life dwells amoung the causes of death  
> > Like a lamp standing in a strong breeze. --Nagarjuna
> 
> ===============================================================================
