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 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
@@ -1,5 +1,6 @@
1
1
  TODO
2
2
  ===============
3
3
 
4
+ - rufus-scheduler integration for scheduled tasks
4
5
  - web admin UI to surface status() metrics
5
6
  - nicer project homepage
data/config.ru ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/lib'))
4
+ require 'girl_friday/server'
5
+
6
+ use Rack::ShowExceptions
7
+ run GirlFriday::Server.new
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
- ObjectSpace.each_object(WorkQueue).inject({}) { |memo, queue| memo.merge(queue.status) }
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
- m = Mutex.new
35
- var = ConditionVariable.new
36
-
37
- queues.each do |q|
38
- q.shutdown do |queue|
39
- m.synchronize do
40
- count -= 1
41
- var.signal if count == 0
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
- m.synchronize do
47
- var.wait(m, timeout)
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
@@ -1,3 +1,3 @@
1
1
  module GirlFriday
2
- VERSION = "0.9.0"
2
+ VERSION = "0.9.1"
3
3
  end
@@ -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 => @ready_workers.size,
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(@ready_workers, @persister)
58
+ drain
58
59
  else
59
60
  @busy_workers.delete(who.this)
60
- @ready_workers << who.this
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
- if !@shutdown && worker = @ready_workers.pop
79
+
80
+ if !@shutdown && worker = ready_workers.pop
76
81
  @busy_workers << worker
77
82
  worker << work
78
- drain(@ready_workers, @persister)
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
- @ready_workers << Actor.spawn_link(&work_loop)
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(ready, work)
148
+ def drain
135
149
  # give as much work to as many ready workers as possible
136
- todo = ready.size < work.size ? ready.size : work.size
150
+ ps = @persister.size
151
+ todo = ready_workers.size < ps ? ready_workers.size : ps
137
152
  todo.times do
138
- worker = ready.pop
153
+ worker = ready_workers.pop
139
154
  @busy_workers << worker
140
- worker << work.pop
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
+
@@ -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
- assert_equal total, metrics[:total_queued]
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
- segments:
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-04-20 00:00:00 -07:00
19
- default_executable:
20
- dependencies: []
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.5.2
81
+ rubygems_version: 1.7.2
81
82
  signing_key:
82
83
  specification_version: 3
83
84
  summary: Background processing, simplified