azurewraith
azurewraith

Reputation: 388

Sinatra 1.3 Streaming w/ Ruby stdout redirection

I would like to use Sinatra's Streaming capability introduced in 1.3 coupled with some stdout redirection. It would basically be a live streaming output of a long running job. I looked into this question and the Sinatra streaming sample in the README.

Running 1.8.7 on OSX:

require 'stringio'
require 'sinatra'

$stdout.sync = true

module Kernel
  def capture_stdout
    out = StringIO.new
    $stdout = out
    yield out
  ensure
    $stdout = STDOUT
  end
end

get '/' do
  stream do |out|
    out << "Part one of a three part series... <br>\n"
    sleep 1
    out << "...part two... <br>\n"
    sleep 1
    out << "...and now the conclusion...\n"

    Kernel.capture_stdout do |stream|
        Thread.new do
            until (line = stream.gets).nil? do
               out << line
             end
        end
        method_that_prints_text
    end
  end
end

def method_that_prints_text
    puts "starting long running job..."
    sleep 3
    puts "almost there..."
    sleep 3
    puts "work complete!"
end

So this bit of code prints out the first three strings properly, and blocks while the method_that_prints_text executes and does not print anything to the browser. My feeling is that stdout is empty on the first call and it never outputs to the out buffer. I'm not quite sure what the proper ordering would be and would appreciate any suggestions.

I tried a few of the EventMachine implementations mentioned in the question above, but couldn't get them to work.

UPDATE

I tried something slightly different to where I had the method run in a new thread, and override STDOUT for that thread as described here...

Instead of Kernel.capture_stdout above...

s = StringIO.new

Thread.start do
    Thread.current[:stdout] = s
    method_that_prints_text
end.join

while line = s.gets do
    out << line
end

out << s.string

With the ThreadOut module listed in the link above, this seems to work a bit better. However it doesn't stream. The only time something is printed to the browser is on the final line out << s.string. Does StringIO not have the capability to stream?

Upvotes: 0

Views: 1672

Answers (1)

azurewraith
azurewraith

Reputation: 388

I ended up solving this by discovering that s.string was updated periodically as time went on, so I just captured the output in a separate thread and grabbed the differences and streamed them out. It appears as though string redirection doesn't behave like a normal IO object.

s = StringIO.new
t = Thread.start do
  Thread.current[:stdout] = s
  method_that_prints_text
  sleep 2
end

displayed_text = ''
while t.alive? do
  current_text = s.string
  unless (current_text.eql?(displayed_text))
    new_text = current_text[displayed_text.length..current_text.length]
    out << new_text
    displayed_text = current_text * 1
  end
  sleep 2   
end

Upvotes: 1

Related Questions