threadless 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Manifest +4 -0
- data/README.rdoc +17 -0
- data/Rakefile +14 -0
- data/lib/threadless.rb +85 -0
- data/threadless.gemspec +30 -0
- metadata +65 -0
data/Manifest
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
We all know that rails has a rocky history regarding threads, to say the least.
|
2
|
+
Sadly, that seems also include the Rails port of one of my favourite Merb features: [run_later](http://github.com/mattmatt/run_later).
|
3
|
+
|
4
|
+
Basically run_later takes a block, turns it into a Proc, and sends it to a worker threads for later execution. That way the request processing is not hindered in any way. Say, you want to send a "you have just signed-up" email to your users: this is a perfect solution: easy to use, lightweight (you don't need extra middle ware), and semi reliable (no fallback when your system breaks down).
|
5
|
+
|
6
|
+
However, I ran into few problems using mattmatt's solution in, at least, development mode:
|
7
|
+
|
8
|
+
- the app regularly ran into class (un)loading issues, and
|
9
|
+
- Rails' mysql adapter apparently didn't disposed of used database connections, refusing new connections
|
10
|
+
|
11
|
+
While all of the above can be explained as some of the quirks of the development environment it didn't increase the "trust level" into that solution (and I have to point out here, that markmark's code looks pretty good to me, and that these problems more likely arise from the somewhat idiosyncratic behaviour of Rails towards threads)
|
12
|
+
|
13
|
+
That made me thinking: why use threads in the first place? After all, what we need is a defined point during request processing that gives us a handle to yield of some piece of code, and which occurs after the request's response has been sent back to the client. And, yes, thanks to metal, I found one.
|
14
|
+
|
15
|
+
So here is a solution. It is not as feature complete as markmark's, tests are still missing, and it blocks the server process until the run_later block is finished. (But you have more than one server process running, haven't you?)
|
16
|
+
|
17
|
+
Someone out there wants to help move that into a regular plugin?
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('threadless', '0.1.0') do |p|
|
6
|
+
p.description = "run_later without threads"
|
7
|
+
p.url = "http://github.com/pboy/threadless"
|
8
|
+
p.author = "pboy"
|
9
|
+
p.email = "eno-pboy@open-lab.org"
|
10
|
+
p.ignore_pattern = ["tmp/*", "script/*"]
|
11
|
+
p.development_dependencies = []
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
data/lib/threadless.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
#
|
2
|
+
# A single threaded runlater implementation
|
3
|
+
class Threadless
|
4
|
+
ENV_KEY = "rack.threadless.run_later"
|
5
|
+
|
6
|
+
module InstanceMethods
|
7
|
+
def threadless
|
8
|
+
@threadless ||= Adapter.new(request)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# The adapter; use it as
|
14
|
+
#
|
15
|
+
# threadless.run_later do ... end
|
16
|
+
#
|
17
|
+
# from within a controller or a view.
|
18
|
+
#
|
19
|
+
class Adapter
|
20
|
+
def initialize(request)
|
21
|
+
@request = request
|
22
|
+
end
|
23
|
+
|
24
|
+
def run_later(run_later = true)
|
25
|
+
if run_later
|
26
|
+
@request.env[ENV_KEY] ||= []
|
27
|
+
@request.env[ENV_KEY].push Proc.new
|
28
|
+
else
|
29
|
+
yield
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# This replaces the metal body piece. It intercepts all messages and
|
36
|
+
# adjusts "close" in such a way that close closes the original body,
|
37
|
+
# and then executes all the procs that are passed into the c'tor.
|
38
|
+
class Executor
|
39
|
+
def method_missing(sym, *args, &block)
|
40
|
+
@body.__send__ sym, *args, &block
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(body, procs)
|
44
|
+
@body, @procs = body, procs
|
45
|
+
end
|
46
|
+
|
47
|
+
def close
|
48
|
+
return if @closed
|
49
|
+
@closed = true
|
50
|
+
|
51
|
+
@body.close if @body.respond_to?(:close)
|
52
|
+
|
53
|
+
@procs.each do |proc|
|
54
|
+
proc.call
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# The metal interface: initializing
|
61
|
+
def initialize(app)
|
62
|
+
@app = app
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# The metal interface: call
|
67
|
+
def call(env)
|
68
|
+
status, headers, body = *@app.call(env)
|
69
|
+
|
70
|
+
body = Executor.new(body, env[ENV_KEY]) if env[ENV_KEY]
|
71
|
+
|
72
|
+
[ status, headers, body ]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# -- activate instance methods and middleware, on Rails
|
78
|
+
|
79
|
+
if defined?(ActionController)
|
80
|
+
ActionController::Base.send(:include, Threadless::InstanceMethods)
|
81
|
+
ActionController::Base.send(:helper, Threadless::InstanceMethods)
|
82
|
+
|
83
|
+
middleware = ActionController::MiddlewareStack::Middleware.new(self)
|
84
|
+
ActionController::Dispatcher.middleware.push middleware
|
85
|
+
end
|
data/threadless.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{threadless}
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["pboy"]
|
9
|
+
s.date = %q{2009-12-12}
|
10
|
+
s.description = %q{run_later without threads}
|
11
|
+
s.email = %q{eno-pboy@open-lab.org}
|
12
|
+
s.extra_rdoc_files = ["README.rdoc", "lib/threadless.rb"]
|
13
|
+
s.files = ["README.rdoc", "Rakefile", "lib/threadless.rb", "Manifest", "threadless.gemspec"]
|
14
|
+
s.homepage = %q{http://github.com/pboy/threadless}
|
15
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Threadless", "--main", "README.mdown"]
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubyforge_project = %q{threadless}
|
18
|
+
s.rubygems_version = %q{1.3.5}
|
19
|
+
s.summary = %q{run_later without threads}
|
20
|
+
|
21
|
+
if s.respond_to? :specification_version then
|
22
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
23
|
+
s.specification_version = 3
|
24
|
+
|
25
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
26
|
+
else
|
27
|
+
end
|
28
|
+
else
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: threadless
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- pboy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-12 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: run_later without threads
|
17
|
+
email: eno-pboy@open-lab.org
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
- lib/threadless.rb
|
25
|
+
files:
|
26
|
+
- README.rdoc
|
27
|
+
- Rakefile
|
28
|
+
- lib/threadless.rb
|
29
|
+
- Manifest
|
30
|
+
- threadless.gemspec
|
31
|
+
has_rdoc: true
|
32
|
+
homepage: http://github.com/pboy/threadless
|
33
|
+
licenses: []
|
34
|
+
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options:
|
37
|
+
- --line-numbers
|
38
|
+
- --inline-source
|
39
|
+
- --title
|
40
|
+
- Threadless
|
41
|
+
- --main
|
42
|
+
- README.mdown
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "1.2"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project: threadless
|
60
|
+
rubygems_version: 1.3.5
|
61
|
+
signing_key:
|
62
|
+
specification_version: 3
|
63
|
+
summary: run_later without threads
|
64
|
+
test_files: []
|
65
|
+
|