Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

A working version, with specs too

  • Loading branch information...
commit 042bdd6434d19137105f7fb0cd51651755f95d53 1 parent 6568ff3
@deadprogram authored
View
1  .gitignore
@@ -0,0 +1 @@
+specs/coverage
View
28 README
@@ -0,0 +1,28 @@
+- what is spork?
+A way to cleanly handle process forking in Sinatra when using Passenger, aka "sporking some code".
+This will allow you to properly execute some code asynchronously, which otherwise does not work correctly.
+
+- what if I do not spork?
+You would like to have your route matcher trigger some long running task, but quickly return to the caller. So you might just try to use Kernal.fork, right? However, if you are running under Passenger, you would discover to your great unhappiness that browser sits there waiting for your long running process to finish before returning.
+
+- so spork instead
+Instead, just use your humble spork to get the job done. spork will fixup the underlying Passenger/Rack problems, so your code sporks, er forks they way your expect. For example:
+
+ get "/" do
+ @log = Logger.new(STDERR)
+ spock = Spork.spork(:logger => @log) do
+ sleep 5
+ end
+ "Our work here is done, my friends" # returns immediately, not waiting for task
+ end
+
+If you inspect the Sinatra log, you will discover something like the following:
+ 127.0.0.1 - - [30/Jan/2009 15:17:48] "GET / HTTP/1.1" 200 33 0.0028
+ D, [2009-01-30T15:17:48.316859 #29286] DEBUG -- : spork> child PID = 29286
+ I, [2009-01-30T15:17:53.316363 #29286] INFO -- : spork> child[29286] took 4.999625 sec
+
+- who made spork?
+Written by Ron Evans
+More info at http://deadprogrammersociety.com
+
+Mostly lifted from the Spawn plugin for Rails (http://github.com/tra/spawn) but with all of the Rails stuff removed.... cause you are using Sinatra. If you are using Rails, Spawn is what you need. If you are using something else besides Sinatra that is Rack-based under Passenger, and you are having trouble with asynch processing, let me know if spork helped you.
View
26 Rakefile
@@ -0,0 +1,26 @@
+require 'rubygems'
+require 'spec/rake/spectask'
+
+task :default => 'spec'
+
+desc "Run all specs."
+Spec::Rake::SpecTask.new("spec") do |t|
+ t.spec_files = FileList["specs/**/*_spec.rb"]
+end
+
+namespace :spec do
+
+ desc "Run all specs for lib."
+ Spec::Rake::SpecTask.new("lib") do |t|
+ t.spec_files = FileList["specs/lib/*_spec.rb"]
+ end
+
+ desc "Run coverage report."
+ Spec::Rake::SpecTask.new("rcov") do |t|
+ t.spec_files = FileList["specs/**/*_spec.rb"]
+ t.rcov = true
+ t.rcov_dir = "specs/coverage"
+ t.rcov_opts = ['--exclude', "specs,/Library/Ruby/Gems/1.8/gems"]
+ end
+
+end
View
13 example/example.rb
@@ -0,0 +1,13 @@
+require 'rubygems'
+require 'sinatra'
+require 'logger'
+
+require File.join(File.dirname(__FILE__), "/../lib/spork")
+
+get "/" do
+ @log = Logger.new(STDERR)
+ spock = Spork.spork(:logger => @log) do
+ sleep 5
+ end
+ "Our work here is done, my friends" # returns immediately, not waiting for task
+end
View
81 lib/spork.rb
@@ -0,0 +1,81 @@
+# A way to cleanly handle process forking in Sinatra when using Passenger, aka "sporking some code".
+# This will allow you to properly execute some code asynchronously, which otherwise does not work correctly.
+#
+# Written by Ron Evans
+# More info at http://deadprogrammersociety.com
+#
+# Mostly lifted from the Spawn plugin for Rails (http://github.com/tra/spawn)
+# but with all of the Rails stuff removed.... cause you are using Sinatra. If you are using Rails, Spawn is
+# what you need. If you are using something else besides Sinatra that is Rack-based under Passenger, and you are having trouble with
+# asynch processing, let me know if spork helped you.
+#
+module Spork
+ # things to close in child process
+ @@resources = []
+ def self.resources
+ @@resources
+ end
+
+ # set the resource to disconnect from in the child process (when forking)
+ def self.resource_to_close(resource)
+ @@resources << resource
+ end
+
+ # close all the resources added by calls to resource_to_close
+ def self.close_resources
+ @@resources.each do |resource|
+ resource.close if resource && resource.respond_to?(:close) && !resource.closed?
+ end
+ @@resources = []
+ end
+
+ # actually perform the fork... er, spork
+ # valid options are:
+ # :priority => to set the process priority of the child
+ # :logger => a logger object to use from the child
+ # :no_detach => true if you want to keep the child process under the parent control. usually you do NOT want this
+ def self.spork(options={})
+ logger = options[:logger]
+ logger.debug "spork> parent PID = #{Process.pid}" if logger
+ child = fork do
+ begin
+ start = Time.now
+ logger.debug "spork> child PID = #{Process.pid}" if logger
+
+ # set the nice priority if needed
+ Process.setpriority(Process::PRIO_PROCESS, 0, options[:priority]) if options[:priority]
+
+ # disconnect from the rack
+ Spork.close_resources
+
+ # run the block of code that takes so long
+ yield
+
+ rescue => ex
+ logger.error "spork> Exception in child[#{Process.pid}] - #{ex.class}: #{ex.message}" if logger
+ ensure
+ logger.info "spork> child[#{Process.pid}] took #{Time.now - start} sec" if logger
+ # this form of exit doesn't call at_exit handlers
+ exit!(0)
+ end
+ end
+
+ # detach from child process (parent may still wait for detached process if they wish)
+ Process.detach(child) unless options[:no_detach]
+
+ return child
+ end
+
+end
+
+# Patch to work with passenger
+if defined? Passenger::Rack::RequestHandler
+ class Passenger::Rack::RequestHandler
+ alias_method :orig_process_request, :process_request
+ def process_request(env, input, output)
+ Spork.resource_to_close(input)
+ Spork.resource_to_close(output)
+ orig_process_request(env, input, output)
+ end
+ end
+end
View
30 specs/spork_spec.rb
@@ -0,0 +1,30 @@
+require File.join(File.dirname(__FILE__), "/../lib/spork")
+require 'tempfile'
+
+describe Spork do
+ it "should return empty array with no resources" do
+ Spork.resources.should == []
+ end
+
+ it "should close any resources when close_resources is called" do
+ @request = mock('request')
+ @request.stub!(:close).and_return(true)
+ @request.stub!(:closed?).and_return(false)
+
+ Spork.resource_to_close(@request)
+ Spork.resources.should == [@request]
+ Spork.close_resources
+ Spork.resources.should == []
+ end
+
+ it "should be able to fork off another process to do some work" do
+ @temp_file = Tempfile.new('spock')
+ spock = Spork.spork(:no_detach => true) do
+ @temp_file.print "123"
+ @temp_file.flush
+ sleep 3
+ end
+ Process.wait(spock)
+ @temp_file.open.gets.should == "123"
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.