qwirk 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. data/History.md +7 -0
  2. data/LICENSE.txt +201 -0
  3. data/README.md +180 -0
  4. data/Rakefile +34 -0
  5. data/examples/README +1 -0
  6. data/examples/activemq.xml +84 -0
  7. data/examples/advanced_requestor/README.md +15 -0
  8. data/examples/advanced_requestor/base_request_worker.rb +18 -0
  9. data/examples/advanced_requestor/char_count_worker.rb +16 -0
  10. data/examples/advanced_requestor/config.ru +24 -0
  11. data/examples/advanced_requestor/exception_raiser_worker.rb +17 -0
  12. data/examples/advanced_requestor/length_worker.rb +14 -0
  13. data/examples/advanced_requestor/print_worker.rb +14 -0
  14. data/examples/advanced_requestor/publisher.rb +49 -0
  15. data/examples/advanced_requestor/qwirk.yml +16 -0
  16. data/examples/advanced_requestor/reverse_worker.rb +14 -0
  17. data/examples/advanced_requestor/triple_worker.rb +14 -0
  18. data/examples/batch/my_batch_worker.rb +30 -0
  19. data/examples/batch/my_line_worker.rb +8 -0
  20. data/examples/qwirk.yml +20 -0
  21. data/examples/requestor/README.md +13 -0
  22. data/examples/requestor/config.ru +13 -0
  23. data/examples/requestor/qwirk_persist.yml +5 -0
  24. data/examples/requestor/requestor.rb +68 -0
  25. data/examples/requestor/reverse_echo_worker.rb +15 -0
  26. data/examples/setup.rb +13 -0
  27. data/examples/shared/README.md +24 -0
  28. data/examples/shared/config.ru +13 -0
  29. data/examples/shared/publisher.rb +49 -0
  30. data/examples/shared/qwirk_persist.yml +5 -0
  31. data/examples/shared/shared_worker.rb +16 -0
  32. data/examples/simple/README +53 -0
  33. data/examples/simple/bar_worker.rb +10 -0
  34. data/examples/simple/baz_worker.rb +10 -0
  35. data/examples/simple/config.ru +14 -0
  36. data/examples/simple/publisher.rb +49 -0
  37. data/examples/simple/qwirk_persist.yml +4 -0
  38. data/examples/simple/tmp/kahadb/db-1.log +0 -0
  39. data/examples/simple/tmp/kahadb/db.data +0 -0
  40. data/examples/simple/tmp/kahadb/db.redo +0 -0
  41. data/examples/task/README +47 -0
  42. data/examples/task/config.ru +14 -0
  43. data/examples/task/foo_worker.rb +10 -0
  44. data/examples/task/messages.out +1000 -0
  45. data/examples/task/publisher.rb +25 -0
  46. data/examples/task/qwirk_persist.yml +5 -0
  47. data/examples/task/task.rb +36 -0
  48. data/lib/qwirk.rb +63 -0
  49. data/lib/qwirk/adapter.rb +45 -0
  50. data/lib/qwirk/base_worker.rb +96 -0
  51. data/lib/qwirk/batch.rb +4 -0
  52. data/lib/qwirk/batch/acquire_file_strategy.rb +47 -0
  53. data/lib/qwirk/batch/active_record.rb +3 -0
  54. data/lib/qwirk/batch/active_record/batch_job.rb +111 -0
  55. data/lib/qwirk/batch/active_record/failed_record.rb +5 -0
  56. data/lib/qwirk/batch/active_record/outstanding_record.rb +6 -0
  57. data/lib/qwirk/batch/file_status_strategy.rb +86 -0
  58. data/lib/qwirk/batch/file_worker.rb +228 -0
  59. data/lib/qwirk/batch/job_status.rb +29 -0
  60. data/lib/qwirk/batch/parse_file_strategy.rb +48 -0
  61. data/lib/qwirk/engine.rb +9 -0
  62. data/lib/qwirk/loggable.rb +23 -0
  63. data/lib/qwirk/manager.rb +140 -0
  64. data/lib/qwirk/marshal_strategy.rb +74 -0
  65. data/lib/qwirk/marshal_strategy/bson.rb +37 -0
  66. data/lib/qwirk/marshal_strategy/json.rb +37 -0
  67. data/lib/qwirk/marshal_strategy/none.rb +26 -0
  68. data/lib/qwirk/marshal_strategy/ruby.rb +26 -0
  69. data/lib/qwirk/marshal_strategy/string.rb +25 -0
  70. data/lib/qwirk/marshal_strategy/yaml.rb +25 -0
  71. data/lib/qwirk/publish_handle.rb +170 -0
  72. data/lib/qwirk/publisher.rb +67 -0
  73. data/lib/qwirk/queue_adapter.rb +3 -0
  74. data/lib/qwirk/queue_adapter/active_mq.rb +13 -0
  75. data/lib/qwirk/queue_adapter/active_mq/publisher.rb +12 -0
  76. data/lib/qwirk/queue_adapter/active_mq/worker_config.rb +16 -0
  77. data/lib/qwirk/queue_adapter/in_mem.rb +7 -0
  78. data/lib/qwirk/queue_adapter/in_mem/factory.rb +45 -0
  79. data/lib/qwirk/queue_adapter/in_mem/publisher.rb +98 -0
  80. data/lib/qwirk/queue_adapter/in_mem/queue.rb +88 -0
  81. data/lib/qwirk/queue_adapter/in_mem/reply_queue.rb +56 -0
  82. data/lib/qwirk/queue_adapter/in_mem/topic.rb +48 -0
  83. data/lib/qwirk/queue_adapter/in_mem/worker.rb +63 -0
  84. data/lib/qwirk/queue_adapter/in_mem/worker_config.rb +59 -0
  85. data/lib/qwirk/queue_adapter/jms.rb +50 -0
  86. data/lib/qwirk/queue_adapter/jms/connection.rb +42 -0
  87. data/lib/qwirk/queue_adapter/jms/consumer.rb +37 -0
  88. data/lib/qwirk/queue_adapter/jms/publisher.rb +126 -0
  89. data/lib/qwirk/queue_adapter/jms/worker.rb +89 -0
  90. data/lib/qwirk/queue_adapter/jms/worker_config.rb +38 -0
  91. data/lib/qwirk/remote_exception.rb +42 -0
  92. data/lib/qwirk/request_worker.rb +62 -0
  93. data/lib/qwirk/task.rb +177 -0
  94. data/lib/qwirk/task.rb.sav +194 -0
  95. data/lib/qwirk/version.rb +3 -0
  96. data/lib/qwirk/worker.rb +222 -0
  97. data/lib/qwirk/worker_config.rb +187 -0
  98. data/lib/rails/generators/qwirk/qwirk_generator.rb +82 -0
  99. data/lib/rails/generators/qwirk/templates/initializer.rb +6 -0
  100. data/lib/rails/generators/qwirk/templates/migration.rb +9 -0
  101. data/lib/rails/generators/qwirk/templates/schema.rb +28 -0
  102. data/lib/rails/railties/tasks.rake +8 -0
  103. data/lib/tasks/qwirk_tasks.rake +4 -0
  104. data/test/base_test.rb +248 -0
  105. data/test/database.yml +14 -0
  106. data/test/dummy/Rakefile +7 -0
  107. data/test/dummy/app/controllers/application_controller.rb +3 -0
  108. data/test/dummy/app/helpers/application_helper.rb +2 -0
  109. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  110. data/test/dummy/config.ru +4 -0
  111. data/test/dummy/config/application.rb +45 -0
  112. data/test/dummy/config/boot.rb +10 -0
  113. data/test/dummy/config/database.yml +22 -0
  114. data/test/dummy/config/environment.rb +5 -0
  115. data/test/dummy/config/environments/development.rb +26 -0
  116. data/test/dummy/config/environments/production.rb +49 -0
  117. data/test/dummy/config/environments/test.rb +35 -0
  118. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  119. data/test/dummy/config/initializers/inflections.rb +10 -0
  120. data/test/dummy/config/initializers/mime_types.rb +5 -0
  121. data/test/dummy/config/initializers/secret_token.rb +7 -0
  122. data/test/dummy/config/initializers/session_store.rb +8 -0
  123. data/test/dummy/config/locales/en.yml +5 -0
  124. data/test/dummy/config/routes.rb +58 -0
  125. data/test/dummy/log/development.log +0 -0
  126. data/test/dummy/log/production.log +0 -0
  127. data/test/dummy/log/server.log +0 -0
  128. data/test/dummy/log/test.log +0 -0
  129. data/test/dummy/public/404.html +26 -0
  130. data/test/dummy/public/422.html +26 -0
  131. data/test/dummy/public/500.html +26 -0
  132. data/test/dummy/public/favicon.ico +0 -0
  133. data/test/dummy/public/javascripts/application.js +2 -0
  134. data/test/dummy/public/javascripts/controls.js +965 -0
  135. data/test/dummy/public/javascripts/dragdrop.js +974 -0
  136. data/test/dummy/public/javascripts/effects.js +1123 -0
  137. data/test/dummy/public/javascripts/prototype.js +6001 -0
  138. data/test/dummy/public/javascripts/rails.js +191 -0
  139. data/test/dummy/script/rails +6 -0
  140. data/test/integration/navigation_test.rb +7 -0
  141. data/test/jms.yml +9 -0
  142. data/test/jms_fail_test.rb +149 -0
  143. data/test/jms_requestor_block_test.rb +278 -0
  144. data/test/jms_requestor_test.rb +238 -0
  145. data/test/jms_test.rb +287 -0
  146. data/test/marshal_strategy_test.rb +62 -0
  147. data/test/support/integration_case.rb +5 -0
  148. data/test/test_helper.rb +7 -0
  149. data/test/test_helper.rbold +22 -0
  150. data/test/test_helper_active_record.rb +61 -0
  151. data/test/unit/qwirk/batch/acquire_file_strategy_test.rb +101 -0
  152. data/test/unit/qwirk/batch/active_record/batch_job_test.rb +35 -0
  153. data/test/unit/qwirk/batch/parse_file_strategy_test.rb +49 -0
  154. metadata +366 -0
@@ -0,0 +1,3 @@
1
+ module Qwirk
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,222 @@
1
+ module Qwirk
2
+
3
+ # Base Worker Class for any class that will be processing messages from topics or queues
4
+ # By default, it will consume messages from a queue with the class name minus the Worker postfix.
5
+ # For example, the queue call is unnecessary as it will default to a value of 'Foo' anyways:
6
+ # class FooWorker
7
+ # include Qwirk::QueueAdapter::JMS::Worker
8
+ # queue 'Foo'
9
+ # def perform(obj)
10
+ # # Perform work on obj
11
+ # end
12
+ # end
13
+ #
14
+ # A topic can also be specified. Note that for JMS, this is only supported under ActiveMQ. On others,
15
+ # each thread for a given worker will act as a separate subscriber.
16
+ # (For ActiveMQ - see http://activemq.apache.org/virtual-destinations.html):
17
+ # class FooWorker
18
+ # include Qwirk::QueueAdapter::JMS::Worker
19
+ # topic 'Zulu'
20
+ # def perform(obj)
21
+ # # Perform work on obj
22
+ # end
23
+ # end
24
+ #
25
+ # TODO (maybe):
26
+ # Filters can also be specified within the class:
27
+ # class FooWorker
28
+ # include Qwirk::QueueAdapter::JMS::Worker
29
+ # filter 'age > 30'
30
+ # def perform(obj)
31
+ # # Perform work on obj
32
+ # end
33
+ # end
34
+ #
35
+ #
36
+ module Worker
37
+ include Qwirk::BaseWorker
38
+
39
+ attr_accessor :message
40
+ attr_reader :status, :adapter, :start_worker_time, :start_read_time, :start_processing_time
41
+
42
+ module ClassMethods
43
+ def queue(name, opts={})
44
+ # If we're using the default name but we still want to set queue options, then a name won't be given.
45
+ if name.kind_of?(Hash)
46
+ @queue_options = name
47
+ else
48
+ @queue_name = name.to_s
49
+ @queue_options = opts
50
+ end
51
+ end
52
+
53
+ def topic(name, options={})
54
+ @topic_name = name.to_s
55
+ @queue_options = options
56
+ end
57
+
58
+ # Set the fail_queue
59
+ # target =>
60
+ # boolean
61
+ # true - exceptions in the worker will cause the message to be forwarded to the queue of <default-name>Fail
62
+ # For instance, an Exception in FooWorker#perform will forward the message to the queue FooFail
63
+ # false - exceptions will not result in the message being forwarded to a fail queue
64
+ # string - equivalent to true but the string defines the name of the fail queue
65
+ def fail_queue(target, opts={})
66
+ @fail_queue_target = target
67
+ end
68
+
69
+ def fail_queue_target
70
+ @fail_queue_target
71
+ end
72
+
73
+ # Defines the default value of the fail_queue_target. For extenders of this class, the default will be true
74
+ # but extenders can change this (RequestWorker returns exceptions to the caller so it defaults to false).
75
+ def default_fail_queue_target
76
+ true
77
+ end
78
+
79
+ def queue_name(default_name)
80
+ puts "getting queue_name queue=#{@queue_name} topic=#{@topic_name} default=#{default_name}"
81
+ return @queue_name if @queue_name
82
+ return nil if @topic_name
83
+ return default_name
84
+ end
85
+
86
+ def topic_name
87
+ @topic_name
88
+ end
89
+
90
+ def queue_options
91
+ @queue_options ||= {}
92
+ end
93
+
94
+ def fail_queue_name(worker_config)
95
+ # TBD - Set up fail_queue as a config
96
+ target = self.class.fail_queue_target
97
+ # Don't overwrite if the user set to false, only if it was never set
98
+ target = self.class.default_fail_queue_target if target.nil?
99
+ if target == true
100
+ return Qwirk.fail_queue_name(config.name)
101
+ elsif target == false
102
+ return nil
103
+ elsif target.kind_of?(String)
104
+ return target
105
+ else
106
+ raise "Invalid fail queue: #{target}"
107
+ end
108
+ end
109
+ end
110
+
111
+ def self.included(base)
112
+ Qwirk::BaseWorker.included(base)
113
+ base.extend(ClassMethods)
114
+ end
115
+
116
+ def start(index, worker_config)
117
+ @status = 'Started'
118
+ @stopped = false
119
+ @processing_mutex = Mutex.new
120
+ self.index = index
121
+ self.config = worker_config
122
+ @adapter = worker_config.adapter.create_worker
123
+ self.thread = Thread.new do
124
+ java.lang.Thread.current_thread.name = "Qwirk worker: #{self}" if RUBY_PLATFORM == 'jruby'
125
+ #Qwirk.logger.debug "#{worker}: Started thread with priority #{Thread.current.priority}"
126
+ event_loop
127
+ end
128
+ end
129
+
130
+ # Workers will be starting and stopping on an as needed basis. Thus, when they get a stop command they should
131
+ # clean up any resources. We don't want to clobber resources while a message is being processed so processing_mutex will surround
132
+ # message processessing and worker closing.
133
+ # From a JMS perspective, stop all workers (close consumer and session), stop the config.
134
+ # From an InMem perspective, we don't want the workers stopping until all messages in the queue have been processed.
135
+ # Therefore we want to stop the
136
+ def stop
137
+ puts "#{self}: In base worker stop"
138
+ @status = 'Stopping'
139
+ @stopped = true
140
+ @processing_mutex.synchronize do
141
+ # This should interrupt @adapter.receive_message above and cause it to return nil
142
+ @adapter.stop
143
+ end
144
+ puts "#{self}: base worker stop complete"
145
+ end
146
+
147
+ def perform(object)
148
+ raise "#{self}: Need to override perform method in #{self.class.name} in order to act on #{object}"
149
+ end
150
+
151
+ def to_s
152
+ "#{config.name}:#{index}"
153
+ end
154
+
155
+ # Allow override of backtrace logging in case the client doesn't want to get spammed with it (maybe just config instead?)
156
+ def log_backtrace(e)
157
+ Qwirk.logger.error "\t#{e.backtrace.join("\n\t")}"
158
+ end
159
+
160
+ #########
161
+ protected
162
+ #########
163
+
164
+ # Start the event loop for handling messages off the queue
165
+ def event_loop
166
+ Qwirk.logger.debug "#{self}: Starting receive loop"
167
+ @start_worker_time = Time.now
168
+ while !@stopped && !config.adapter.stopped
169
+ puts "#{self}: Waiting for read"
170
+ @start_read_time = Time.now
171
+ msg = @adapter.receive_message
172
+ if msg
173
+ @start_processing_time = Time.now
174
+ Qwirk.logger.debug {"#{self}: Done waiting for read in #{@start_processing_time - @start_read_time} seconds"}
175
+ delta = config.timer.measure do
176
+ @processing_mutex.synchronize do
177
+ on_message(msg)
178
+ @adapter.acknowledge_message(msg)
179
+ end
180
+ end
181
+ Qwirk.logger.info {"#{self}::on_message (#{'%.1f' % delta}ms)"} if self.config.log_times
182
+ Qwirk.logger.flush if Qwirk.logger.respond_to?(:flush)
183
+ end
184
+ end
185
+ Qwirk.logger.info "#{self}: Exiting"
186
+ rescue Exception => e
187
+ @status = "Terminated: #{e.message}"
188
+ Qwirk.logger.error "#{self}: Exception, thread terminating: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
189
+ ensure
190
+ @status = 'Stopped'
191
+ # TODO: necessary?
192
+ @adapter.stop
193
+ Qwirk.logger.flush if Qwirk.logger.respond_to?(:flush)
194
+ config.worker_stopped(self)
195
+ end
196
+
197
+ def on_message(message)
198
+ # TBD - Is it necessary to provide underlying message to worker? Should we generically provide access to message attributes? Do filters somehow fit in here?
199
+ @message = message
200
+ object = @adapter.message_to_object(message)
201
+ Qwirk.logger.debug {"#{self}: Received Object: #{object}"}
202
+ perform(object)
203
+ rescue Exception => e
204
+ on_exception(e)
205
+ ensure
206
+ Qwirk.logger.debug {"#{self}: Finished processing message"}
207
+ end
208
+
209
+ def on_exception(e)
210
+ Qwirk.logger.error "#{self}: Messaging Exception: #{e.message}"
211
+ log_backtrace(e)
212
+ @adapter.handle_failure(message, @fail_queue_name) if @fail_queue_name
213
+ rescue Exception => e
214
+ Qwirk.logger.error "#{self}: Exception in exception reply: #{e.message}"
215
+ log_backtrace(e)
216
+ end
217
+
218
+ def fail_queue_name
219
+ @fail_queue_name
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,187 @@
1
+ require 'rumx'
2
+
3
+ module Qwirk
4
+ class WorkerConfig
5
+ include Rumx::Bean
6
+
7
+ attr_reader :name, :marshaler
8
+
9
+ bean_reader :count, :integer, 'Current number of workers'
10
+ bean_attr_accessor :min_count, :integer, 'Min number of workers allowed', :config_item => true
11
+ bean_attr_accessor :max_count, :integer, 'Max number of workers allowed', :config_item => true
12
+ bean_attr_accessor :idle_worker_timeout, :integer, 'Timeout where an idle worker will be removed from the worker pool and it\'s resources closed (0 for no removal)', :config_item => true
13
+ bean_attr_accessor :max_read_threshold, :float, 'Threshold where a new worker will be added if none of the workers have had to wait this amount of time on a read', :config_item => true
14
+ # The adapter refers to the corresponding class in Qwirk::QueueAdapter::<type>::WorkerConfig
15
+ bean_attr_reader :adapter, :bean, 'Adapter for worker queue interface'
16
+ bean_attr_reader :timer, :bean, 'Track the times for this worker'
17
+ bean_attr_accessor :log_times, :boolean, 'Log the times for this worker'
18
+
19
+ # Define the default config values for the attributes all workers will share. These will be sent as options to the constructor
20
+ def self.initial_default_config
21
+ {:min_count => 0, :max_count => 0, :idle_worker_timeout => 60, :max_read_threshold => 1.0}
22
+ end
23
+
24
+ # Create new WorkerConfig to manage workers of a common class
25
+ def initialize(queue_adapter, name, manager, worker_class, default_options, options)
26
+ @name = name
27
+ @manager = manager
28
+ @worker_class = worker_class
29
+ @workers = []
30
+ @stopped = false
31
+ @min_count = 0
32
+ @max_count = 0
33
+ @index_count = 0
34
+ @index_mutex = Mutex.new
35
+ @worker_mutex = Mutex.new
36
+ @worker_condition = ConditionVariable.new
37
+ response_options = worker_class.queue_options[:response] || {}
38
+ @adapter = queue_adapter.create_adapter_worker_config(self, worker_class.queue_name(@name), worker_class.topic_name, worker_class.queue_options, response_options)
39
+ # Defines how we will marshal the response
40
+ marshal_sym = (response_options[:marshal] || @adapter.default_marshal_sym)
41
+ @marshaler = MarshalStrategy.find(marshal_sym)
42
+ @log_times = queue_adapter.log_times
43
+
44
+ #Qwirk.logger.debug { "options=#{options.inspect}" }
45
+ default_options.each do |key, value|
46
+ begin
47
+ send(key.to_s+'=', value)
48
+ rescue Exception => e
49
+ # Let config_reader's set a default value
50
+ begin
51
+ instance_variable_set("@#{key}", value)
52
+ rescue Exception => e
53
+ Qwirk.logger.warn "WARNING: During initialization of #{worker_class.name} config=#{@name}, default assignment of #{key}=#{value} was invalid"
54
+ end
55
+ end
56
+ end
57
+ # Run the specified options after the default options, so that codependant options don't get overwritten (like min_count/max_count)
58
+ options.each do |key, value|
59
+ begin
60
+ send(key.to_s+'=', value)
61
+ rescue Exception => e
62
+ Qwirk.logger.warn "WARNING: During initialization of #{worker_class.name} config=#{@name}, assignment of #{key}=#{value} was invalid"
63
+ end
64
+ end
65
+ end
66
+
67
+ def count
68
+ @worker_mutex.synchronize { return @workers.size }
69
+ end
70
+
71
+ def min_count=(new_min_count)
72
+ return if @min_count == new_min_count
73
+ raise "#{@worker_class.name}-#{@name}: Can't change count since we've been stopped" if @stopped
74
+ Qwirk.logger.info "#{@worker_class.name}: Changing min number of workers from #{@min_count} to #{new_min_count}"
75
+ self.max_count = new_min_count if @max_count < new_min_count
76
+ @worker_mutex.synchronize do
77
+ add_worker while @workers.size < new_min_count
78
+ @min_count = new_min_count
79
+ end
80
+ end
81
+
82
+ def max_count=(new_max_count)
83
+ return if @max_count == new_max_count
84
+ raise "#{@worker_class.name}-#{@name}: Can't change count since we've been stopped" if @stopped
85
+ Qwirk.logger.info "#{@worker_class.name}: Changing max number of workers from #{@max_count} to #{new_max_count}"
86
+ self.min_count = new_max_count if @min_count > new_max_count
87
+ @min_count = 1 if @min_count == 0 && new_max_count > 0
88
+ @worker_mutex.synchronize do
89
+ @timer ||= Rumx::Beans::TimerAndError.new
90
+ if @workers.size > new_max_count
91
+ @workers[new_max_count..-1].each { |worker| worker.stop }
92
+ while @workers.size > new_max_count
93
+ @workers.last.stop
94
+ @worker_condition.wait(@worker_mutex)
95
+ end
96
+ end
97
+ @max_count = new_max_count
98
+ end
99
+ end
100
+
101
+ def stop
102
+ Qwirk.logger.debug { "In Base worker_config stop" }
103
+ # First stop the adapter. For InMem, this will not return until all the messages in the queue have
104
+ # been processed since these messages are not persistent.
105
+ @adapter.stop
106
+ @worker_mutex.synchronize do
107
+ @workers.each { |worker| worker.stop }
108
+ while @workers.size > 0
109
+ @worker_condition.wait(@worker_mutex)
110
+ end
111
+ @stopped = true
112
+ end
113
+ end
114
+
115
+ def worker_stopped(worker)
116
+ remove_worker(worker)
117
+ end
118
+
119
+ # Override rumx bean method
120
+ def bean_attributes_changed
121
+ super
122
+ @manager.save_persist_state
123
+ end
124
+
125
+ def marshal_response(object)
126
+ @marshaler.marshal(object)
127
+ end
128
+
129
+ def unmarshal_response(marshaled_object)
130
+ @marshaler.unmarshal(marshaled_object)
131
+ end
132
+
133
+ def periodic_call(poll_time)
134
+ now = Time.now
135
+ add_new_worker = true
136
+ worker_stopped = false
137
+ @worker_mutex.synchronize do
138
+ # reverse_each to remove later workers first
139
+ @workers.reverse_each do |worker|
140
+ start_worker_time = worker.start_worker_time
141
+ start_read_time = worker.start_read_time
142
+ if !start_read_time || (now - start_worker_time) < (poll_time + @max_read_threshold)
143
+ #Qwirk.logger.debug { "#{self}: Skipping newly created worker" }
144
+ add_new_worker = false
145
+ next
146
+ end
147
+ end_read_time = worker.start_processing_time
148
+ # If the processing time is actually from the previous processing, then we're probably still waiting for the read to complete.
149
+ if !end_read_time || end_read_time < start_read_time
150
+ if !worker_stopped && @workers.size > @min_count && (now - start_read_time) > @idle_worker_timeout
151
+ worker.stop
152
+ worker_stopped = true
153
+ end
154
+ end_read_time = now
155
+ end
156
+ #Qwirk.logger.debug { "#{self}: start=#{start_read_time} end=#{end_read_time} thres=#{@max_read_threshold} add_new_worker=#{add_new_worker}" }
157
+ add_new_worker = false if (end_read_time - start_read_time) > @max_read_threshold
158
+ end
159
+ add_worker if add_new_worker && @workers.size < @max_count
160
+ end
161
+ end
162
+
163
+ def to_s
164
+ @name
165
+ end
166
+
167
+ private
168
+
169
+ def add_worker
170
+ worker = @worker_class.new
171
+ worker.start(@index_count, self)
172
+ Qwirk.logger.debug {"#{self}: Adding worker #{worker}"}
173
+ @index_mutex.synchronize { @index_count += 1 }
174
+ @workers << worker
175
+ rescue Exception => e
176
+ Qwirk.logger.error("Unable to add #{@worker_class} worker: #{e.message}\n\t#{e.backtrace.join("\n\t")}")
177
+ end
178
+
179
+ def remove_worker(worker)
180
+ Qwirk.logger.debug {"#{self}: Deleting worker #{worker}"}
181
+ @worker_mutex.synchronize do
182
+ @workers.delete(worker)
183
+ @worker_condition.broadcast
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,82 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class QwirkGenerator < Rails::Generators::Base
5
+ include Rails::Generators::Migration
6
+
7
+ def self.source_root
8
+ File.join(File.dirname(__FILE__), 'templates')
9
+ end
10
+
11
+ def self.next_migration_number(dirname) #:nodoc:
12
+ if ActiveRecord::Base.timestamped_migrations
13
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
14
+ else
15
+ "%.3d" % (current_migration_number(dirname) + 1)
16
+ end
17
+ end
18
+
19
+ def initialize(args, *options) #:nodoc:
20
+ # Unfreeze name in case it's given as a frozen string
21
+ args[0] = args[0].dup if args[0].is_a?(String) && args[0].frozen?
22
+ super
23
+ assign_names!(self.name)
24
+ parse_attributes! if respond_to?(:attributes)
25
+ end
26
+
27
+ # Every method that is declared below will be automatically executed when the generator is run
28
+
29
+ def create_migration_file
30
+ f = File.open File.join(File.dirname(__FILE__), 'templates', 'schema.rb')
31
+ schema = f.read; f.close
32
+
33
+ schema.gsub!(/ActiveRecord::Schema.*\n/, '')
34
+ schema.gsub!(/^end\n*$/, '')
35
+
36
+ f = File.open File.join(File.dirname(__FILE__), 'templates', 'migration.rb')
37
+ migration = f.read; f.close
38
+ migration.gsub!(/SCHEMA_AUTO_INSERTED_HERE/, schema)
39
+
40
+ tmp = File.open "tmp/~migration_ready.rb", "w"
41
+ tmp.write migration
42
+ tmp.close
43
+
44
+ migration_template '../../../tmp/~migration_ready.rb',
45
+ 'db/migrate/create_qwirk_tables.rb'
46
+ remove_file 'tmp/~migration_ready.rb'
47
+ end
48
+
49
+ def copy_initializer_file
50
+ copy_file 'initializer.rb', 'config/initializers/qwirk.rb'
51
+ end
52
+
53
+ def update_application_template
54
+ f = File.open "app/views/layouts/application.html.erb"
55
+ layout = f.read; f.close
56
+
57
+ if layout =~ /<%=[ ]+yield[ ]+%>/
58
+ print " \e[1m\e[34mquestion\e[0m Your layouts/application.html.erb layout currently has the line <%= yield %>. This gem needs to change this line to <%= content_for?(:content) ? yield(:content) : yield %> to support its nested layouts. This change should not affect any of your existing layouts or views. Is this okay? [y/n] "
59
+ begin
60
+ answer = gets.chomp
61
+ end while not answer =~ /[yn]/i
62
+
63
+ if answer =~ /y/i
64
+
65
+ layout.gsub!(/<%=[ ]+yield[ ]+%>/, '<%= content_for?(:content) ? yield(:content) : yield %>')
66
+
67
+ tmp = File.open "tmp/~application.html.erb", "w"
68
+ tmp.write layout; tmp.close
69
+
70
+ remove_file 'app/views/layouts/application.html.erb'
71
+ copy_file '../../../tmp/~application.html.erb',
72
+ 'app/views/layouts/application.html.erb'
73
+ remove_file 'tmp/~application.html.erb'
74
+ end
75
+ elsif layout =~ /<%=[ ]+content_for\?\(:content\) \? yield\(:content\) : yield[ ]+%>/
76
+ puts " \e[1m\e[33mskipping\e[0m layouts/application.html.erb modification is already done."
77
+ else
78
+ puts " \e[1m\e[31mconflict\e[0m The gem is confused by your layouts/application.html.erb. It does not contain the default line <%= yield %>, you may need to make manual changes to get this gem's nested layouts working. Visit ###### for details."
79
+ end
80
+ end
81
+
82
+ end