girl_friday 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.md +5 -0
- data/TODO.md +1 -0
- data/config.ru +7 -0
- data/girl_friday.gemspec +2 -1
- data/lib/girl_friday.rb +31 -15
- data/lib/girl_friday/persistence.rb +5 -1
- data/lib/girl_friday/server.rb +49 -0
- data/lib/girl_friday/version.rb +1 -1
- data/lib/girl_friday/work_queue.rb +34 -18
- data/server/public/css/style.css +263 -0
- data/server/views/index.erb +51 -0
- data/test/test_girl_friday.rb +17 -2
- metadata +19 -18
data/History.md
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
Changes
|
2
2
|
================
|
3
3
|
|
4
|
+
0.9.1
|
5
|
+
---------
|
6
|
+
|
7
|
+
* Lazy initialize the worker actors to avoid dead thread problems with Unicorn forking processes.
|
8
|
+
* Add initial pass at girl_friday Rack server (see wiki). It's awful looking, trust me, help wanted.
|
4
9
|
|
5
10
|
|
6
11
|
0.9.0
|
data/TODO.md
CHANGED
data/config.ru
ADDED
data/girl_friday.gemspec
CHANGED
@@ -12,8 +12,9 @@ Gem::Specification.new do |s|
|
|
12
12
|
|
13
13
|
s.rubyforge_project = "girl_friday"
|
14
14
|
|
15
|
-
s.files = `git ls-files`.split("\n")
|
15
|
+
s.files = `git ls-files`.split("\n").reject { |path| path =~ /rails_app/}
|
16
16
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
18
|
s.require_paths = ["lib"]
|
19
|
+
s.add_development_dependency 'sinatra', '~> 1.0'
|
19
20
|
end
|
data/lib/girl_friday.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'thread'
|
2
|
+
require 'weakref'
|
2
3
|
begin
|
3
4
|
# Rubinius
|
4
5
|
require 'actor'
|
@@ -15,8 +16,13 @@ require 'girl_friday/persistence'
|
|
15
16
|
|
16
17
|
module GirlFriday
|
17
18
|
|
19
|
+
@@queues = []
|
20
|
+
def self.queues
|
21
|
+
@@queues
|
22
|
+
end
|
23
|
+
|
18
24
|
def self.status
|
19
|
-
|
25
|
+
queues.delete_if { |q| !q.weakref_alive? }.inject({}) { |memo, queue| queue.weakref_alive? ? memo.merge(queue.status) : memo }
|
20
26
|
end
|
21
27
|
|
22
28
|
##
|
@@ -27,26 +33,36 @@ module GirlFriday
|
|
27
33
|
#
|
28
34
|
# Note that shutdown! just works with existing queues. If you create a
|
29
35
|
# new queue, it will act as normal.
|
36
|
+
#
|
37
|
+
# WeakRefs make this method full of race conditions with GC. :-(
|
30
38
|
def self.shutdown!(timeout=30)
|
31
|
-
queues
|
32
|
-
ObjectSpace.each_object(WorkQueue).each { |q| queues << q }
|
39
|
+
queues.delete_if { |q| !q.weakref_alive? }
|
33
40
|
count = queues.size
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
41
|
+
|
42
|
+
if count > 0
|
43
|
+
m = Mutex.new
|
44
|
+
var = ConditionVariable.new
|
45
|
+
|
46
|
+
queues.each do |q|
|
47
|
+
q.shutdown do |queue|
|
48
|
+
m.synchronize do
|
49
|
+
count -= 1
|
50
|
+
var.signal if count == 0
|
51
|
+
end
|
42
52
|
end
|
43
53
|
end
|
44
|
-
end
|
45
54
|
|
46
|
-
|
47
|
-
|
55
|
+
m.synchronize do
|
56
|
+
var.wait(m, timeout)
|
57
|
+
end
|
58
|
+
#puts "girl_friday shutdown complete"
|
48
59
|
end
|
49
60
|
count
|
50
61
|
end
|
51
62
|
|
52
|
-
end
|
63
|
+
end
|
64
|
+
|
65
|
+
at_exit do
|
66
|
+
GirlFriday.shutdown!
|
67
|
+
end
|
68
|
+
|
@@ -23,7 +23,7 @@ module GirlFriday
|
|
23
23
|
class Redis
|
24
24
|
def initialize(name, options)
|
25
25
|
@opts = options
|
26
|
-
@key = "girl_friday-#{name}"
|
26
|
+
@key = "girl_friday-#{name}-#{environment}"
|
27
27
|
end
|
28
28
|
|
29
29
|
def push(work)
|
@@ -43,6 +43,10 @@ module GirlFriday
|
|
43
43
|
|
44
44
|
private
|
45
45
|
|
46
|
+
def environment
|
47
|
+
ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'none'
|
48
|
+
end
|
49
|
+
|
46
50
|
def redis
|
47
51
|
@redis ||= ::Redis.new(*@opts)
|
48
52
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'erb'
|
3
|
+
require 'girl_friday'
|
4
|
+
|
5
|
+
# QUEUE1 = GirlFriday::Queue.new('ham_cannon', :size => 15) do |msg|
|
6
|
+
# puts msg
|
7
|
+
# end
|
8
|
+
# QUEUE2 = GirlFriday::Queue.new('image_crawler', :size => 5) do |msg|
|
9
|
+
# puts msg
|
10
|
+
# end
|
11
|
+
|
12
|
+
module GirlFriday
|
13
|
+
class Server < Sinatra::Base
|
14
|
+
basedir = File.expand_path(File.dirname(__FILE__) + '/../../server')
|
15
|
+
|
16
|
+
set :views, "#{basedir}/views"
|
17
|
+
set :public, "#{basedir}/public"
|
18
|
+
set :static, true
|
19
|
+
|
20
|
+
helpers do
|
21
|
+
include Rack::Utils
|
22
|
+
alias_method :h, :escape_html
|
23
|
+
|
24
|
+
def dashboard(stats)
|
25
|
+
if stats[:busy] == stats[:pool_size] && stats[:backlog] < stats[:pool_size]
|
26
|
+
['#ffc', 'Busy']
|
27
|
+
elsif stats[:busy] == stats[:pool_size] && stats[:backlog] >= stats[:pool_size]
|
28
|
+
['#fcc', 'Busy and Backlogged']
|
29
|
+
else
|
30
|
+
['white', 'OK']
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
get '/' do
|
36
|
+
redirect "#{request.env['REQUEST_URI']}/status"
|
37
|
+
end
|
38
|
+
|
39
|
+
get '/status' do
|
40
|
+
@status = GirlFriday.status
|
41
|
+
erb :index
|
42
|
+
end
|
43
|
+
|
44
|
+
get '/status.json' do
|
45
|
+
content_type :json
|
46
|
+
GirlFriday.status.to_json
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/girl_friday/version.rb
CHANGED
@@ -5,20 +5,21 @@ module GirlFriday
|
|
5
5
|
Work = Struct.new(:msg, :callback)
|
6
6
|
Shutdown = Struct.new(:callback)
|
7
7
|
|
8
|
+
|
8
9
|
attr_reader :name
|
9
10
|
def initialize(name, options={}, &block)
|
10
|
-
@name = name
|
11
|
+
@name = name.to_s
|
11
12
|
@size = options[:size] || 5
|
12
13
|
@processor = block
|
13
14
|
@error_handler = (options[:error_handler] || ErrorHandler.default).new
|
14
15
|
|
15
16
|
@shutdown = false
|
16
|
-
@ready_workers = []
|
17
17
|
@busy_workers = []
|
18
18
|
@created_at = Time.now.to_i
|
19
19
|
@total_processed = @total_errors = @total_queued = 0
|
20
20
|
@persister = (options[:store] || Store::InMemory).new(name, (options[:store_config] || []))
|
21
21
|
start
|
22
|
+
GirlFriday.queues << WeakRef.new(self)
|
22
23
|
end
|
23
24
|
|
24
25
|
def push(work, &block)
|
@@ -30,7 +31,7 @@ module GirlFriday
|
|
30
31
|
{ @name => {
|
31
32
|
:pid => $$,
|
32
33
|
:pool_size => @size,
|
33
|
-
:ready =>
|
34
|
+
:ready => ready_workers.size,
|
34
35
|
:busy => @busy_workers.size,
|
35
36
|
:backlog => @persister.size,
|
36
37
|
:total_queued => @total_queued,
|
@@ -54,12 +55,15 @@ module GirlFriday
|
|
54
55
|
@total_processed += 1
|
55
56
|
if !@shutdown && work = @persister.pop
|
56
57
|
who.this << work
|
57
|
-
drain
|
58
|
+
drain
|
58
59
|
else
|
59
60
|
@busy_workers.delete(who.this)
|
60
|
-
|
61
|
+
ready_workers << who.this
|
61
62
|
shutdown_complete if @shutdown && @busy_workers.size == 0
|
62
63
|
end
|
64
|
+
rescue => ex
|
65
|
+
# Redis network error? Log and ignore.
|
66
|
+
@error_handler.handle(ex)
|
63
67
|
end
|
64
68
|
|
65
69
|
def shutdown_complete
|
@@ -72,19 +76,34 @@ module GirlFriday
|
|
72
76
|
|
73
77
|
def on_work(work)
|
74
78
|
@total_queued += 1
|
75
|
-
|
79
|
+
|
80
|
+
if !@shutdown && worker = ready_workers.pop
|
76
81
|
@busy_workers << worker
|
77
82
|
worker << work
|
78
|
-
drain
|
83
|
+
drain
|
79
84
|
else
|
80
85
|
@persister << work
|
81
86
|
end
|
87
|
+
rescue => ex
|
88
|
+
# Redis network error? Log and ignore.
|
89
|
+
@error_handler.handle(ex)
|
90
|
+
end
|
91
|
+
|
92
|
+
def ready_workers
|
93
|
+
@ready_workers ||= begin
|
94
|
+
workers = []
|
95
|
+
@size.times do |x|
|
96
|
+
# start N workers
|
97
|
+
workers << Actor.spawn_link(&@work_loop)
|
98
|
+
end
|
99
|
+
workers
|
100
|
+
end
|
82
101
|
end
|
83
102
|
|
84
103
|
def start
|
85
104
|
@supervisor = Actor.spawn do
|
86
105
|
supervisor = Actor.current
|
87
|
-
work_loop = Proc.new do
|
106
|
+
@work_loop = Proc.new do
|
88
107
|
loop do
|
89
108
|
work = Actor.receive
|
90
109
|
result = @processor.call(work.msg)
|
@@ -94,11 +113,6 @@ module GirlFriday
|
|
94
113
|
end
|
95
114
|
|
96
115
|
Actor.trap_exit = true
|
97
|
-
@size.times do |x|
|
98
|
-
# start N workers
|
99
|
-
@ready_workers << Actor.spawn_link(&work_loop)
|
100
|
-
end
|
101
|
-
|
102
116
|
begin
|
103
117
|
loop do
|
104
118
|
Actor.receive do |f|
|
@@ -117,7 +131,7 @@ module GirlFriday
|
|
117
131
|
# TODO Provide current message contents as error context
|
118
132
|
@total_errors += 1
|
119
133
|
@busy_workers.delete(exit.actor)
|
120
|
-
|
134
|
+
ready_workers << Actor.spawn_link(&@work_loop)
|
121
135
|
@error_handler.handle(exit.reason)
|
122
136
|
end
|
123
137
|
end
|
@@ -131,15 +145,17 @@ module GirlFriday
|
|
131
145
|
end
|
132
146
|
end
|
133
147
|
|
134
|
-
def drain
|
148
|
+
def drain
|
135
149
|
# give as much work to as many ready workers as possible
|
136
|
-
|
150
|
+
ps = @persister.size
|
151
|
+
todo = ready_workers.size < ps ? ready_workers.size : ps
|
137
152
|
todo.times do
|
138
|
-
worker =
|
153
|
+
worker = ready_workers.pop
|
139
154
|
@busy_workers << worker
|
140
|
-
worker <<
|
155
|
+
worker << @persister.pop
|
141
156
|
end
|
142
157
|
end
|
143
158
|
|
144
159
|
end
|
160
|
+
Queue = WorkQueue
|
145
161
|
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
/**
|
2
|
+
* HTML5 ✰ Boilerplate
|
3
|
+
*
|
4
|
+
* style.css contains a reset, font normalization and some base styles.
|
5
|
+
*
|
6
|
+
* Credit is left where credit is due.
|
7
|
+
* Much inspiration was taken from these projects:
|
8
|
+
* - yui.yahooapis.com/2.8.1/build/base/base.css
|
9
|
+
* - camendesign.com/design/
|
10
|
+
* - praegnanz.de/weblog/htmlcssjs-kickstart
|
11
|
+
*/
|
12
|
+
|
13
|
+
|
14
|
+
/**
|
15
|
+
* html5doctor.com Reset Stylesheet (Eric Meyer's Reset Reloaded + HTML5 baseline)
|
16
|
+
* v1.6.1 2010-09-17 | Authors: Eric Meyer & Richard Clark
|
17
|
+
* html5doctor.com/html-5-reset-stylesheet/
|
18
|
+
*/
|
19
|
+
|
20
|
+
html, body, div, span, object, iframe,
|
21
|
+
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
22
|
+
abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp,
|
23
|
+
small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li,
|
24
|
+
fieldset, form, label, legend,
|
25
|
+
table, caption, tbody, tfoot, thead, tr, th, td,
|
26
|
+
article, aside, canvas, details, figcaption, figure,
|
27
|
+
footer, header, hgroup, menu, nav, section, summary,
|
28
|
+
time, mark, audio, video {
|
29
|
+
margin: 0;
|
30
|
+
padding: 0;
|
31
|
+
border: 0;
|
32
|
+
font-size: 100%;
|
33
|
+
font: inherit;
|
34
|
+
vertical-align: baseline;
|
35
|
+
}
|
36
|
+
|
37
|
+
article, aside, details, figcaption, figure,
|
38
|
+
footer, header, hgroup, menu, nav, section {
|
39
|
+
display: block;
|
40
|
+
}
|
41
|
+
|
42
|
+
blockquote, q { quotes: none; }
|
43
|
+
|
44
|
+
blockquote:before, blockquote:after,
|
45
|
+
q:before, q:after { content: ""; content: none; }
|
46
|
+
|
47
|
+
ins { background-color: #ff9; color: #000; text-decoration: none; }
|
48
|
+
|
49
|
+
mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; }
|
50
|
+
|
51
|
+
del { text-decoration: line-through; }
|
52
|
+
|
53
|
+
abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; }
|
54
|
+
|
55
|
+
table { border-collapse: collapse; border-spacing: 0; }
|
56
|
+
|
57
|
+
hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
|
58
|
+
|
59
|
+
input, select { vertical-align: middle; }
|
60
|
+
|
61
|
+
|
62
|
+
/**
|
63
|
+
* Font normalization inspired by YUI Library's fonts.css: developer.yahoo.com/yui/
|
64
|
+
*/
|
65
|
+
|
66
|
+
body { font:13px/1.231 sans-serif; *font-size:small; } /* Hack retained to preserve specificity */
|
67
|
+
select, input, textarea, button { font:99% sans-serif; }
|
68
|
+
|
69
|
+
/* Normalize monospace sizing:
|
70
|
+
en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome */
|
71
|
+
pre, code, kbd, samp { font-family: monospace, sans-serif; }
|
72
|
+
|
73
|
+
|
74
|
+
/**
|
75
|
+
* Minimal base styles.
|
76
|
+
*/
|
77
|
+
|
78
|
+
/* Always force a scrollbar in non-IE */
|
79
|
+
html { overflow-y: scroll; }
|
80
|
+
|
81
|
+
/* j.mp/webkit-tap-highlight-color */
|
82
|
+
a:link { -webkit-tap-highlight-color: #FF5E99; }
|
83
|
+
|
84
|
+
/* Accessible focus treatment: people.opera.com/patrickl/experiments/keyboard/test */
|
85
|
+
a:hover, a:active { outline: none; }
|
86
|
+
|
87
|
+
a, a:active, a:visited { color: #607890; }
|
88
|
+
a:hover { color: #036; }
|
89
|
+
|
90
|
+
ul, ol { margin-left: 2em; }
|
91
|
+
ol { list-style-type: decimal; }
|
92
|
+
|
93
|
+
/* Remove margins for navigation lists */
|
94
|
+
nav ul, nav li { margin: 0; list-style:none; list-style-image: none; }
|
95
|
+
|
96
|
+
small { font-size: 85%; }
|
97
|
+
strong, th { font-weight: bold; }
|
98
|
+
|
99
|
+
td { vertical-align: top; }
|
100
|
+
|
101
|
+
/* Set sub, sup without affecting line-height: gist.github.com/413930 */
|
102
|
+
sub, sup { font-size: 75%; line-height: 0; position: relative; }
|
103
|
+
sup { top: -0.5em; }
|
104
|
+
sub { bottom: -0.25em; }
|
105
|
+
|
106
|
+
pre {
|
107
|
+
/* www.pathf.com/blogs/2008/05/formatting-quoted-code-in-blog-posts-css21-white-space-pre-wrap/ */
|
108
|
+
white-space: pre; white-space: pre-wrap; word-wrap: break-word;
|
109
|
+
padding: 15px;
|
110
|
+
}
|
111
|
+
|
112
|
+
.ie6 legend, .ie7 legend { margin-left: -7px; }
|
113
|
+
|
114
|
+
/* Align checkboxes, radios, text inputs with their label by: Thierry Koblentz tjkdesign.com/ez-css/css/base.css */
|
115
|
+
input[type="radio"] { vertical-align: text-bottom; }
|
116
|
+
input[type="checkbox"] { vertical-align: bottom; }
|
117
|
+
.ie7 input[type="checkbox"] { vertical-align: baseline; }
|
118
|
+
.ie6 input { vertical-align: text-bottom; }
|
119
|
+
|
120
|
+
/* Hand cursor on clickable input elements */
|
121
|
+
label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; }
|
122
|
+
|
123
|
+
/* 1) Make inputs and buttons play nice in IE: www.viget.com/inspire/styling-the-button-element-in-internet-explorer/
|
124
|
+
2) WebKit browsers add a 2px margin outside the chrome of form elements.
|
125
|
+
Firefox adds a 1px margin above and below textareas */
|
126
|
+
button, input, select, textarea { width: auto; overflow: visible; margin: 0; }
|
127
|
+
|
128
|
+
textarea { overflow: auto; } /* www.sitepoint.com/blogs/2010/08/20/ie-remove-textarea-scrollbars/ */
|
129
|
+
|
130
|
+
/* Remove extra padding and inner border in Firefox */
|
131
|
+
input::-moz-focus-inner,
|
132
|
+
button::-moz-focus-inner { border: 0; padding: 0; }
|
133
|
+
|
134
|
+
/* Colors for form validity */
|
135
|
+
input:valid, textarea:valid { }
|
136
|
+
input:invalid, textarea:invalid {
|
137
|
+
border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red;
|
138
|
+
}
|
139
|
+
.no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; }
|
140
|
+
|
141
|
+
/* These selection declarations have to be separate
|
142
|
+
No text-shadow: twitter.com/miketaylr/status/12228805301
|
143
|
+
Also: hot pink! */
|
144
|
+
::-moz-selection{ background: #FF5E99; color:#fff; text-shadow: none; }
|
145
|
+
::selection { background:#FF5E99; color:#fff; text-shadow: none; }
|
146
|
+
|
147
|
+
/* Bicubic resizing for non-native sized IMG:
|
148
|
+
code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ */
|
149
|
+
.ie7 img { -ms-interpolation-mode: bicubic; }
|
150
|
+
|
151
|
+
|
152
|
+
/**
|
153
|
+
* You might tweak these..
|
154
|
+
*/
|
155
|
+
|
156
|
+
body, select, input, textarea {
|
157
|
+
/* #444 looks better than black: twitter.com/H_FJ/statuses/11800719859 */
|
158
|
+
color: #444;
|
159
|
+
/* Set your base font here, to apply evenly */
|
160
|
+
/* font-family: Georgia, serif; */
|
161
|
+
}
|
162
|
+
|
163
|
+
/* Headers (h1, h2, etc) have no default font-size or margin; define those yourself */
|
164
|
+
h1, h2, h3, h4, h5, h6 { font-weight: bold; }
|
165
|
+
|
166
|
+
|
167
|
+
/**
|
168
|
+
* Primary styles
|
169
|
+
*
|
170
|
+
* Author:
|
171
|
+
*/
|
172
|
+
|
173
|
+
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
|
178
|
+
|
179
|
+
|
180
|
+
|
181
|
+
|
182
|
+
|
183
|
+
|
184
|
+
|
185
|
+
|
186
|
+
|
187
|
+
|
188
|
+
/**
|
189
|
+
* Non-semantic helper classes: please define your styles before this section.
|
190
|
+
*/
|
191
|
+
|
192
|
+
/* For image replacement */
|
193
|
+
.ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; }
|
194
|
+
|
195
|
+
/* Hide for both screenreaders and browsers:
|
196
|
+
css-discuss.incutio.com/wiki/Screenreader_Visibility */
|
197
|
+
.hidden { display: none; visibility: hidden; }
|
198
|
+
|
199
|
+
/* Hide only visually, but have it available for screenreaders: by Jon Neal.
|
200
|
+
www.webaim.org/techniques/css/invisiblecontent/ & j.mp/visuallyhidden */
|
201
|
+
.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
|
202
|
+
/* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: drupal.org/node/897638 */
|
203
|
+
.visuallyhidden.focusable:active,
|
204
|
+
.visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; }
|
205
|
+
|
206
|
+
/* Hide visually and from screenreaders, but maintain layout */
|
207
|
+
.invisible { visibility: hidden; }
|
208
|
+
|
209
|
+
/* The Magnificent Clearfix: Updated to prevent margin-collapsing on child elements in most situations.
|
210
|
+
nicolasgallagher.com/micro-clearfix-hack/ */
|
211
|
+
.clearfix:before, .clearfix:after { content: ""; display: block; overflow:hidden; }
|
212
|
+
.clearfix:after { clear: both; }
|
213
|
+
/* Fix clearfix: blueprintcss.lighthouseapp.com/projects/15318/tickets/5-extra-margin-padding-bottom-of-page */
|
214
|
+
.clearfix { zoom: 1; }
|
215
|
+
|
216
|
+
|
217
|
+
|
218
|
+
/**
|
219
|
+
* Media queries for responsive design.
|
220
|
+
*
|
221
|
+
* These follow after primary styles so they will successfully override.
|
222
|
+
*/
|
223
|
+
|
224
|
+
@media all and (orientation:portrait) {
|
225
|
+
/* Style adjustments for portrait mode goes here */
|
226
|
+
|
227
|
+
}
|
228
|
+
|
229
|
+
@media all and (orientation:landscape) {
|
230
|
+
/* Style adjustments for landscape mode goes here */
|
231
|
+
|
232
|
+
}
|
233
|
+
|
234
|
+
/* Grade-A Mobile Browsers (Opera Mobile, Mobile Safari, Android Chrome)
|
235
|
+
consider this: www.cloudfour.com/css-media-query-for-mobile-is-fools-gold/ */
|
236
|
+
@media screen and (max-device-width: 480px) {
|
237
|
+
|
238
|
+
|
239
|
+
/* Uncomment if you don't want iOS and WinMobile to mobile-optimize the text for you: j.mp/textsizeadjust */
|
240
|
+
/* html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } */
|
241
|
+
}
|
242
|
+
|
243
|
+
|
244
|
+
/**
|
245
|
+
* Print styles.
|
246
|
+
*
|
247
|
+
* Inlined to avoid required HTTP connection: www.phpied.com/delay-loading-your-print-css/
|
248
|
+
*/
|
249
|
+
@media print {
|
250
|
+
* { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important;
|
251
|
+
-ms-filter: none !important; } /* Black prints faster: sanbeiji.com/archives/953 */
|
252
|
+
a, a:visited { color: #444 !important; text-decoration: underline; }
|
253
|
+
a[href]:after { content: " (" attr(href) ")"; }
|
254
|
+
abbr[title]:after { content: " (" attr(title) ")"; }
|
255
|
+
.ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } /* Don't show links for images, or javascript/internal links */
|
256
|
+
pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
|
257
|
+
thead { display: table-header-group; } /* css-discuss.incutio.com/wiki/Printing_Tables */
|
258
|
+
tr, img { page-break-inside: avoid; }
|
259
|
+
@page { margin: 0.5cm; }
|
260
|
+
p, h2, h3 { orphans: 3; widows: 3; }
|
261
|
+
h2, h3{ page-break-after: avoid; }
|
262
|
+
}
|
263
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>girl_friday status</title>
|
4
|
+
<link href="/css/style.css" media="screen" rel="stylesheet" type="text/css" />
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<style>
|
8
|
+
h1 { font-size: 2em; padding-bottom: 5px;}
|
9
|
+
h2 { font-size: 1.5em; padding-bottom: 5px;}
|
10
|
+
h3 { font-size: 1.3em; padding-bottom: 5px;}
|
11
|
+
body { margin: 0 auto; width: 1000px; padding: 0; font-size: 14px; font-family: sans-serif; background-color: white; }
|
12
|
+
#header { padding: 10px 0;}
|
13
|
+
#queues { border-collapse: collapse; width: 100%; border: 1px solid black; }
|
14
|
+
#queues TD { border: 1px solid black; padding: 5px; }
|
15
|
+
#queues TH { padding: 5px; text-align: left; background: #444; color: #ddf; border: 1px solid black; }
|
16
|
+
</style>
|
17
|
+
<div id="header">
|
18
|
+
<h1>girl_friday</h1>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<div id="main">
|
22
|
+
<table id="queues">
|
23
|
+
<thead>
|
24
|
+
<tr>
|
25
|
+
<th>Name</th>
|
26
|
+
<th>Size</th>
|
27
|
+
<th>Busy</th>
|
28
|
+
<th>Backlog</th>
|
29
|
+
<th>Status</th>
|
30
|
+
</tr>
|
31
|
+
</thead>
|
32
|
+
<tbody>
|
33
|
+
<% @status.keys.sort.each do |name|
|
34
|
+
stats = @status[name]
|
35
|
+
(bg, note) = dashboard(stats)
|
36
|
+
%>
|
37
|
+
<tr style="background-color: <%= bg %>">
|
38
|
+
<td><%= h name %></td>
|
39
|
+
<td><%= stats[:pool_size] %></td>
|
40
|
+
<td><%= stats[:busy] %></td>
|
41
|
+
<td><%= stats[:backlog] %></td>
|
42
|
+
<td><%= note %></td>
|
43
|
+
</tr>
|
44
|
+
<% end %>
|
45
|
+
</tbody>
|
46
|
+
</table>
|
47
|
+
</div>
|
48
|
+
|
49
|
+
</body>
|
50
|
+
</html>
|
51
|
+
|
data/test/test_girl_friday.rb
CHANGED
@@ -69,7 +69,8 @@ class TestGirlFriday < MiniTest::Unit::TestCase
|
|
69
69
|
refute_nil actual
|
70
70
|
refute_nil actual['image_crawler']
|
71
71
|
metrics = actual['image_crawler']
|
72
|
-
|
72
|
+
assert metrics[:total_queued] > 0
|
73
|
+
assert metrics[:total_queued] <= total
|
73
74
|
assert_equal 3, metrics[:pool_size]
|
74
75
|
assert_equal 3, metrics[:busy]
|
75
76
|
assert_equal 0, metrics[:ready]
|
@@ -128,7 +129,8 @@ class TestGirlFriday < MiniTest::Unit::TestCase
|
|
128
129
|
queue.push(:text => 'foo')
|
129
130
|
end
|
130
131
|
|
131
|
-
GirlFriday.shutdown!
|
132
|
+
count = GirlFriday.shutdown!
|
133
|
+
assert_equal 0, count
|
132
134
|
s = queue.status
|
133
135
|
assert_equal 0, s['shutdown'][:busy]
|
134
136
|
assert_equal 2, s['shutdown'][:ready]
|
@@ -137,4 +139,17 @@ class TestGirlFriday < MiniTest::Unit::TestCase
|
|
137
139
|
end
|
138
140
|
end
|
139
141
|
|
142
|
+
def test_should_create_workers_lazily
|
143
|
+
async_test do |cb|
|
144
|
+
queue = GirlFriday::Queue.new('shutdown', :size => 2) do |msg|
|
145
|
+
assert_equal 1, queue.instance_variable_get(:@ready_workers).size
|
146
|
+
cb.call
|
147
|
+
end
|
148
|
+
assert_nil queue.instance_variable_get(:@ready_workers)
|
149
|
+
# don't instantiate the worker threads until we actually put
|
150
|
+
# work onto the queue.
|
151
|
+
queue << 'empty msg'
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
140
155
|
end
|
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: girl_friday
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 59
|
5
4
|
prerelease:
|
6
|
-
|
7
|
-
- 0
|
8
|
-
- 9
|
9
|
-
- 0
|
10
|
-
version: 0.9.0
|
5
|
+
version: 0.9.1
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Mike Perham
|
@@ -15,10 +10,19 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date: 2011-
|
19
|
-
|
20
|
-
|
21
|
-
|
13
|
+
date: 2011-05-14 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: sinatra
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "1.0"
|
24
|
+
type: :development
|
25
|
+
version_requirements: *id001
|
22
26
|
description: Background processing, simplified
|
23
27
|
email:
|
24
28
|
- mperham@gmail.com
|
@@ -36,18 +40,21 @@ files:
|
|
36
40
|
- README.md
|
37
41
|
- Rakefile
|
38
42
|
- TODO.md
|
43
|
+
- config.ru
|
39
44
|
- girl_friday.gemspec
|
40
45
|
- lib/girl_friday.rb
|
41
46
|
- lib/girl_friday/actor.rb
|
42
47
|
- lib/girl_friday/error_handler.rb
|
43
48
|
- lib/girl_friday/monkey_patches.rb
|
44
49
|
- lib/girl_friday/persistence.rb
|
50
|
+
- lib/girl_friday/server.rb
|
45
51
|
- lib/girl_friday/version.rb
|
46
52
|
- lib/girl_friday/work_queue.rb
|
53
|
+
- server/public/css/style.css
|
54
|
+
- server/views/index.erb
|
47
55
|
- test/helper.rb
|
48
56
|
- test/test_girl_friday.rb
|
49
57
|
- test/timed_queue.rb
|
50
|
-
has_rdoc: true
|
51
58
|
homepage: http://github.com/mperham/girl_friday
|
52
59
|
licenses: []
|
53
60
|
|
@@ -61,23 +68,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
61
68
|
requirements:
|
62
69
|
- - ">="
|
63
70
|
- !ruby/object:Gem::Version
|
64
|
-
hash: 3
|
65
|
-
segments:
|
66
|
-
- 0
|
67
71
|
version: "0"
|
68
72
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
73
|
none: false
|
70
74
|
requirements:
|
71
75
|
- - ">="
|
72
76
|
- !ruby/object:Gem::Version
|
73
|
-
hash: 3
|
74
|
-
segments:
|
75
|
-
- 0
|
76
77
|
version: "0"
|
77
78
|
requirements: []
|
78
79
|
|
79
80
|
rubyforge_project: girl_friday
|
80
|
-
rubygems_version: 1.
|
81
|
+
rubygems_version: 1.7.2
|
81
82
|
signing_key:
|
82
83
|
specification_version: 3
|
83
84
|
summary: Background processing, simplified
|