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,42 @@
1
+ module Qwirk
2
+ class RemoteException < Exception
3
+ attr_accessor :originating_exception_name
4
+ attr_accessor :originating_exception_message
5
+
6
+ def initialize(originating_exception=nil, message=nil)
7
+ super(message)
8
+ if originating_exception
9
+ @originating_exception_name = originating_exception.class.name
10
+ @originating_exception_message = originating_exception.message
11
+ set_backtrace(originating_exception.backtrace)
12
+ end
13
+ @message = message
14
+ end
15
+
16
+ def message
17
+ @message || @originating_exception_message
18
+ end
19
+
20
+ def message=(msg)
21
+ @message = msg
22
+ end
23
+
24
+ def to_hash
25
+ {
26
+ 'message' => @message,
27
+ 'originating_exception_name' => @originating_exception_name,
28
+ 'originating_exception_message' => @originating_exception_message,
29
+ 'backtrace' => backtrace
30
+ }
31
+ end
32
+
33
+ def self.from_hash(hash)
34
+ exc = new
35
+ exc.message = hash['message']
36
+ exc.originating_exception_name = hash['originating_exception_name']
37
+ exc.originating_exception_message = hash['originating_exception_message']
38
+ exc.set_backtrace(hash['backtrace'])
39
+ return exc
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,62 @@
1
+ module Qwirk
2
+
3
+ # Base Worker Class for any class that will be processing requests from queues and replying
4
+ module RequestWorker
5
+ include Worker
6
+
7
+ module ClassMethods
8
+ # Define the marshaling and time_to_live that will occur on the response
9
+ def response(options)
10
+ queue_options[:response] = options
11
+ end
12
+
13
+ # By default, exceptions don't get forwarded to a fail queue (they get returned to the caller)
14
+ def default_fail_queue_target
15
+ false
16
+ end
17
+ end
18
+
19
+ def self.included(base)
20
+ Worker.included(base)
21
+ base.extend(ClassMethods)
22
+ end
23
+
24
+ def perform(object)
25
+ begin
26
+ response = request(object)
27
+ rescue Exception => e
28
+ on_exception(e)
29
+ else
30
+ adapter.send_response(message, config.marshaler.marshal(response))
31
+ end
32
+ post_request(object)
33
+ rescue Exception => e
34
+ Qwirk.logger.error("Exception in send_response or post_request: #{e.message}")
35
+ log_backtrace(e)
36
+ end
37
+
38
+ def request(object)
39
+ raise "#{self}: Need to override request method in #{self.class.name} in order to act on #{object}"
40
+ end
41
+
42
+ # Handle any processing that you want to perform after the reply
43
+ def post_request(object)
44
+ end
45
+
46
+ #########
47
+ protected
48
+ #########
49
+
50
+ def on_exception(e)
51
+ begin
52
+ adapter.send_exception(message, e)
53
+ rescue Exception => e
54
+ Qwirk.logger.error("Exception in exception reply: #{e.message}")
55
+ log_backtrace(e)
56
+ end
57
+
58
+ # Send it on to the fail queue if it was explicitly set (See default_fail_queue_target above)
59
+ super
60
+ end
61
+ end
62
+ end
data/lib/qwirk/task.rb ADDED
@@ -0,0 +1,177 @@
1
+ module Qwirk
2
+
3
+ # The following options can be used for configuring the class
4
+ # :
5
+ module Task
6
+ #include Qwirk::BaseWorker
7
+ include Rumx::Bean
8
+
9
+ bean_attr_reader :task_id, :string, 'The ID for this task'
10
+ bean_attr_reader :publish_count, :integer, 'The number of requests that have been published'
11
+ bean_attr_reader :success_count, :integer, 'The number of successful responses'
12
+ bean_attr_reader :exception_count, :integer, 'The number of exception responses'
13
+ bean_attr_reader :total_count, :integer, 'The total expected records to be published (optional)'
14
+ bean_attr_accessor :retry, :boolean, 'Retry all the exception responses'
15
+ bean_attr_accessor :auto_retry, :boolean, 'Continuously retry all the exception responses while at least 1 or more succeeds'
16
+ bean_attr_reader :exceptions_per_run, :list, 'Number of exceptions per run, i.e., index 0 contains the count of exceptions in the first run, index 1 in the first retry, etc.', :list_type => :integer
17
+
18
+ module ClassMethods
19
+ end
20
+
21
+ def self.included(base)
22
+ #Qwirk::BaseWorker.included(base)
23
+ Rumx::Bean.included(base)
24
+ base.extend(ClassMethods)
25
+ end
26
+
27
+ def initialize(publisher, task_id, total_count, opts={})
28
+ @publisher = publisher
29
+ @mutex = Mutex.new
30
+ @condition = ConditionVariable.new
31
+ @task_id = task_id
32
+ @stopped = false
33
+ @finished_publishing = false
34
+ @retry = opts[:retry]
35
+ @auto_retry = opts[:auto_retry]
36
+ @publish_count = 0
37
+ @success_count = 0
38
+ @exception_count = 0
39
+ @total_count = total_count
40
+ @exceptions_per_run = []
41
+
42
+ @producer, @consumer = publisher.create_producer_consumer_pair(self)
43
+ @reply_thread = Thread.new do
44
+ java.lang.Thread.current_thread.name = "Qwirk task: #{task_id}"
45
+ reply_event_loop
46
+ on_done
47
+ end
48
+ end
49
+
50
+ # Stuff to override
51
+ def on_response(response)
52
+ end
53
+
54
+ def on_exception(exception)
55
+ end
56
+
57
+ def on_update()
58
+ end
59
+
60
+ def on_done()
61
+ end
62
+
63
+ def retry=(val)
64
+ @retry = val
65
+ if val
66
+ @mutex.synchronize { check_retry }
67
+ end
68
+ end
69
+
70
+ def auto_retry=(val)
71
+ @auto_retry = val
72
+ if val
73
+ @mutex.synchronize { check_retry }
74
+ end
75
+ end
76
+
77
+ def publish(object)
78
+ marshaled_object = @publisher.marshaler.marshal(object)
79
+ @mutex.synchronize do
80
+ unless @stopped
81
+ @producer.send(marshaled_object)
82
+ @publish_count += 1
83
+ end
84
+ end
85
+ end
86
+
87
+ # TODO: Needed?
88
+ def start
89
+ end
90
+
91
+ def stop
92
+ @mutex.synchronize { do_stop }
93
+ @reply_thread.join
94
+ end
95
+
96
+ def finished_publishing
97
+ @total_count = @publish_count
98
+ @finished_publishing = true
99
+ @mutex.synchronize { check_finish }
100
+ @reply_thread.join
101
+ end
102
+
103
+ #######
104
+ private
105
+ #######
106
+
107
+ # Must be called within a mutex synchronize
108
+ def do_stop
109
+ return if @stopped
110
+ @consumer.stop if @consumer
111
+ @fail_consumer.stop if @fail_consumer
112
+ @stopped = true
113
+ end
114
+
115
+ def reply_event_loop
116
+ while !@stopped && response = @consumer.receive
117
+ @mutex.synchronize do
118
+ unless @stopped
119
+ if response.kind_of?(RemoteException)
120
+ @exception_count += 1
121
+ on_exception(response)
122
+ else
123
+ @success_count += 1
124
+ on_response(response)
125
+ end
126
+ @consumer.acknowledge_message
127
+ check_finish
128
+ @condition.signal
129
+ end
130
+ end
131
+ end
132
+ do_stop
133
+ Qwirk.logger.info "#{self}: Exiting"
134
+ rescue Exception => e
135
+ do_stop
136
+ Qwirk.logger.error "#{self}: Exception, thread terminating: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
137
+ end
138
+
139
+ # Must be called within a mutex synchronize
140
+ def check_finish
141
+ if @finished_publishing
142
+ if @success_count >= @total_count
143
+ do_stop
144
+ else
145
+ check_retry
146
+ end
147
+ end
148
+ end
149
+
150
+ # Must be called within a mutex synchronize
151
+ def check_retry
152
+ if @finished_publishing && @exception_count > 0 && (@exception_count+@success_count) == @total_count && (@retry || @auto_retry)
153
+ # If we're just doing auto_retry but nothing succeeded last time, then don't run again
154
+ return if !@retry && @auto_retry && @exception_count == @exceptions_per_run.last
155
+ Qwirk.logger.info "#{self}: Retrying exception records, exception count = #{@exception_count}"
156
+ @exceptions_per_run << @exception_count
157
+ @exception_count = 0
158
+ @finished_publishing = false
159
+ @fail_thread = Thread.new(@exceptions_per_run.last) do |count|
160
+ begin
161
+ java.lang.Thread.current_thread.name = "Qwirk fail task: #{task_id}"
162
+ while !@stopped && (count > 0) && (object = @fail_consumer.receive)
163
+ count -= 1
164
+ publish(object)
165
+ @fail_consumer.acknowledge_message
166
+ end
167
+ @finished_publishing = true
168
+ @mutex.synchronize { check_finish }
169
+ rescue Exception => e
170
+ do_stop
171
+ Qwirk.logger.error "#{self}: Exception, thread terminating: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,194 @@
1
+ module Qwirk
2
+
3
+ # The following options can be used for configuring the class
4
+ # :max_pending_records => <integer>
5
+ # This is how many records can be queued at a time.
6
+ # :
7
+ module Task
8
+ #include Qwirk::BaseWorker
9
+ include Rumx::Bean
10
+
11
+ bean_attr_accessor :max_pending_records, :integer, 'The max number of records that can be published without having been responded to (publishing blocks at this point).'
12
+ bean_attr_reader :task_id, :string, 'The ID for this task'
13
+ bean_attr_reader :success_count, :integer, 'The number of successful responses'
14
+ bean_attr_reader :exception_count, :integer, 'The number of exception responses'
15
+ bean_attr_reader :total_count, :integer, 'The total expected records to be published (optional)'
16
+ bean_attr_accessor :retry, :boolean, 'Retry all the exception responses'
17
+ bean_attr_accessor :auto_retry, :boolean, 'Continuously retry all the exception responses while at least 1 or more succeeds'
18
+ bean_attr_reader :exceptions_per_run, :list, 'Number of exceptions per run, i.e., index 0 contains the count of exceptions in the first run, index 1 in the first retry, etc.', :list_type => :integer
19
+
20
+ module ClassMethods
21
+ end
22
+
23
+ def self.included(base)
24
+ #Qwirk::BaseWorker.included(base)
25
+ Rumx::Bean.included(base)
26
+ base.extend(ClassMethods)
27
+ end
28
+
29
+ def initialize(publisher, task_id, total_count, opts={})
30
+ @publisher = publisher
31
+ @pending_hash = Hash.new
32
+ @pending_hash_mutex = Mutex.new
33
+ @pending_hash_condition = ConditionVariable.new
34
+ @task_id = task_id
35
+ @stopped = false
36
+ @finished_publishing = false
37
+ @max_pending_records = opts[:max_pending_records] || 100
38
+ @retry = opts[:retry]
39
+ @auto_retry = opts[:auto_retry]
40
+ @success_count = 0
41
+ @exception_count = 0
42
+ @total_count = total_count
43
+ @exceptions_per_run = []
44
+
45
+ @producer, @consumer = publisher.create_producer_consumer_pair(self)
46
+ @reply_thread = Thread.new do
47
+ java.lang.Thread.current_thread.name = "Qwirk task: #{task_id}"
48
+ reply_event_loop
49
+ on_done
50
+ end
51
+ end
52
+
53
+ # Stuff to override
54
+ def on_response(request, response)
55
+ end
56
+
57
+ def on_exception(request, exception)
58
+ end
59
+
60
+ def on_update()
61
+ end
62
+
63
+ def on_done()
64
+ end
65
+
66
+ def retry=(val)
67
+ @retry = val
68
+ if val
69
+ @pending_hash_mutex.synchronize { check_retry }
70
+ end
71
+ end
72
+
73
+ def publish(object)
74
+ marshaled_object = @publisher.marshaler.marshal(object)
75
+ @pending_hash_mutex.synchronize do
76
+ while !@stopped && @pending_hash.size >= @max_pending_records
77
+ @pending_hash_condition.wait(@pending_hash_mutex)
78
+ end
79
+ unless @stopped
80
+ message_id = @producer.send(marshaled_object)
81
+ @pending_hash[message_id] = object
82
+ end
83
+ end
84
+ end
85
+
86
+ # TODO: Needed?
87
+ def start
88
+ end
89
+
90
+ def stop
91
+ @pending_hash_mutex.synchronize { do_stop }
92
+ @reply_thread.join
93
+ end
94
+
95
+ def finished_publishing
96
+ @finished_publishing = true
97
+ @pending_hash_mutex.synchronize { check_finish }
98
+ @reply_thread.join
99
+ end
100
+
101
+ #######
102
+ private
103
+ #######
104
+
105
+ def verify_fail_queue_creation
106
+ unless @fail_producer
107
+ @fail_producer, @fail_consumer = publisher.create_producer_fail_consumer_pair(@task_id)
108
+ end
109
+ end
110
+
111
+ def publish_fail_request(object)
112
+ verify_fail_queue_creation
113
+ marshaled_object = @publisher.marshaler.marshal(object)
114
+ @fail_producer.send(marshaled_object)
115
+ end
116
+
117
+ # Must be called within a mutex synchronize
118
+ def do_stop
119
+ return if @stopped
120
+ @consumer.stop if @consumer
121
+ @fail_consumer.stop if @fail_consumer
122
+ @stopped = true
123
+ end
124
+
125
+ def reply_event_loop
126
+ while !@stopped && pair = @consumer.receive
127
+ message_id, response = pair
128
+ @pending_hash_mutex.synchronize do
129
+ unless @stopped
130
+ request = @pending_hash.delete(message_id)
131
+ if request
132
+ if response.kind_of?(RemoteException)
133
+ publish_fail_request(request)
134
+ on_exception(request, response)
135
+ @exception_count += 1
136
+ else
137
+ on_response(request, response)
138
+ @success_count += 1
139
+ end
140
+ else
141
+ Qwirk.logger.warn("#{self}: Read unexpected response with message_id=#{message_id}")
142
+ end
143
+ @consumer.acknowledge_message
144
+ check_finish
145
+ @pending_hash_condition.signal
146
+ end
147
+ end
148
+ end
149
+ do_stop
150
+ Qwirk.logger.info "#{self}: Exiting"
151
+ rescue Exception => e
152
+ do_stop
153
+ Qwirk.logger.error "#{self}: Exception, thread terminating: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
154
+ end
155
+
156
+ # Must be called within a mutex synchronize
157
+ def check_finish
158
+ if @finished_publishing && @pending_hash.empty?
159
+ if @exception_count == 0
160
+ do_stop
161
+ else
162
+ check_retry
163
+ end
164
+ end
165
+ end
166
+
167
+ # Must be called within a mutex synchronize
168
+ def check_retry
169
+ if @finished_publishing && @pending_hash.empty? && @exception_count > 0 && (@retry || @auto_retry)
170
+ # If we're just doing auto_retry but nothing succeeded last time, then don't run again
171
+ return if !@retry && @auto_retry && @exception_count == @exceptions_per_run.last
172
+ Qwirk.logger.info "#{self}: Retrying exception records, exception count = #{@exception_count}"
173
+ @exceptions_per_run << @exception_count
174
+ @exception_count = 0
175
+ @finished_publishing = false
176
+ @fail_thread = Thread.new(@exceptions_per_run.last) do |count|
177
+ begin
178
+ java.lang.Thread.current_thread.name = "Qwirk fail task: #{task_id}"
179
+ while !@stopped && (count > 0) && (object = @fail_consumer.receive)
180
+ count -= 1
181
+ publish(object)
182
+ @fail_consumer.acknowledge_message
183
+ end
184
+ @finished_publishing = true
185
+ @pending_hash_mutex.synchronize { check_finish }
186
+ rescue Exception => e
187
+ do_stop
188
+ Qwirk.logger.error "#{self}: Exception, thread terminating: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end