qwirk 0.0.1

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.
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