garcun 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +17 -0
  3. data/.gitignore +197 -0
  4. data/.rspec +2 -0
  5. data/Gemfile +22 -0
  6. data/LICENSE +201 -0
  7. data/README.md +521 -0
  8. data/Rakefile +47 -0
  9. data/garcun.gemspec +83 -0
  10. data/lib/garcon.rb +290 -0
  11. data/lib/garcon/chef/chef_helpers.rb +343 -0
  12. data/lib/garcon/chef/coerce/coercer.rb +134 -0
  13. data/lib/garcon/chef/coerce/coercions/boolean_definitions.rb +34 -0
  14. data/lib/garcon/chef/coerce/coercions/date_definitions.rb +32 -0
  15. data/lib/garcon/chef/coerce/coercions/date_time_definitions.rb +32 -0
  16. data/lib/garcon/chef/coerce/coercions/fixnum_definitions.rb +34 -0
  17. data/lib/garcon/chef/coerce/coercions/float_definitions.rb +32 -0
  18. data/lib/garcon/chef/coerce/coercions/hash_definitions.rb +29 -0
  19. data/lib/garcon/chef/coerce/coercions/integer_definitions.rb +31 -0
  20. data/lib/garcon/chef/coerce/coercions/string_definitions.rb +45 -0
  21. data/lib/garcon/chef/coerce/coercions/time_definitions.rb +32 -0
  22. data/lib/garcon/chef/handler/devreporter.rb +127 -0
  23. data/lib/garcon/chef/log.rb +64 -0
  24. data/lib/garcon/chef/node.rb +100 -0
  25. data/lib/garcon/chef/provider/civilize.rb +209 -0
  26. data/lib/garcon/chef/provider/development.rb +159 -0
  27. data/lib/garcon/chef/provider/download.rb +420 -0
  28. data/lib/garcon/chef/provider/house_keeping.rb +265 -0
  29. data/lib/garcon/chef/provider/node_cache.rb +31 -0
  30. data/lib/garcon/chef/provider/partial.rb +183 -0
  31. data/lib/garcon/chef/provider/recovery.rb +80 -0
  32. data/lib/garcon/chef/provider/zip_file.rb +271 -0
  33. data/lib/garcon/chef/resource/attribute.rb +52 -0
  34. data/lib/garcon/chef/resource/base_dsl.rb +174 -0
  35. data/lib/garcon/chef/resource/blender.rb +140 -0
  36. data/lib/garcon/chef/resource/lazy_eval.rb +66 -0
  37. data/lib/garcon/chef/resource/resource_name.rb +109 -0
  38. data/lib/garcon/chef/secret_bag.rb +204 -0
  39. data/lib/garcon/chef/validations.rb +76 -0
  40. data/lib/garcon/chef_inclusions.rb +151 -0
  41. data/lib/garcon/configuration.rb +138 -0
  42. data/lib/garcon/core_ext.rb +39 -0
  43. data/lib/garcon/core_ext/array.rb +27 -0
  44. data/lib/garcon/core_ext/binding.rb +64 -0
  45. data/lib/garcon/core_ext/boolean.rb +66 -0
  46. data/lib/garcon/core_ext/duration.rb +271 -0
  47. data/lib/garcon/core_ext/enumerable.rb +34 -0
  48. data/lib/garcon/core_ext/file.rb +127 -0
  49. data/lib/garcon/core_ext/filetest.rb +62 -0
  50. data/lib/garcon/core_ext/hash.rb +279 -0
  51. data/lib/garcon/core_ext/kernel.rb +159 -0
  52. data/lib/garcon/core_ext/lazy.rb +222 -0
  53. data/lib/garcon/core_ext/method_access.rb +243 -0
  54. data/lib/garcon/core_ext/module.rb +92 -0
  55. data/lib/garcon/core_ext/nil.rb +53 -0
  56. data/lib/garcon/core_ext/numeric.rb +44 -0
  57. data/lib/garcon/core_ext/object.rb +342 -0
  58. data/lib/garcon/core_ext/pathname.rb +152 -0
  59. data/lib/garcon/core_ext/process.rb +41 -0
  60. data/lib/garcon/core_ext/random.rb +497 -0
  61. data/lib/garcon/core_ext/string.rb +312 -0
  62. data/lib/garcon/core_ext/struct.rb +49 -0
  63. data/lib/garcon/core_ext/symbol.rb +170 -0
  64. data/lib/garcon/core_ext/time.rb +234 -0
  65. data/lib/garcon/exceptions.rb +101 -0
  66. data/lib/garcon/inflections.rb +237 -0
  67. data/lib/garcon/inflections/defaults.rb +79 -0
  68. data/lib/garcon/inflections/inflections.rb +182 -0
  69. data/lib/garcon/inflections/rules_collection.rb +37 -0
  70. data/lib/garcon/secret.rb +271 -0
  71. data/lib/garcon/stash/format.rb +114 -0
  72. data/lib/garcon/stash/journal.rb +226 -0
  73. data/lib/garcon/stash/queue.rb +83 -0
  74. data/lib/garcon/stash/serializer.rb +86 -0
  75. data/lib/garcon/stash/store.rb +435 -0
  76. data/lib/garcon/task.rb +31 -0
  77. data/lib/garcon/task/atomic.rb +151 -0
  78. data/lib/garcon/task/atomic_boolean.rb +127 -0
  79. data/lib/garcon/task/condition.rb +99 -0
  80. data/lib/garcon/task/copy_on_notify_observer_set.rb +154 -0
  81. data/lib/garcon/task/copy_on_write_observer_set.rb +153 -0
  82. data/lib/garcon/task/count_down_latch.rb +92 -0
  83. data/lib/garcon/task/delay.rb +196 -0
  84. data/lib/garcon/task/dereferenceable.rb +144 -0
  85. data/lib/garcon/task/event.rb +119 -0
  86. data/lib/garcon/task/executor.rb +275 -0
  87. data/lib/garcon/task/executor_options.rb +59 -0
  88. data/lib/garcon/task/future.rb +107 -0
  89. data/lib/garcon/task/immediate_executor.rb +84 -0
  90. data/lib/garcon/task/ivar.rb +171 -0
  91. data/lib/garcon/task/lazy_reference.rb +74 -0
  92. data/lib/garcon/task/monotonic_time.rb +69 -0
  93. data/lib/garcon/task/obligation.rb +256 -0
  94. data/lib/garcon/task/observable.rb +101 -0
  95. data/lib/garcon/task/priority_queue.rb +234 -0
  96. data/lib/garcon/task/processor_count.rb +128 -0
  97. data/lib/garcon/task/read_write_lock.rb +304 -0
  98. data/lib/garcon/task/safe_task_executor.rb +58 -0
  99. data/lib/garcon/task/single_thread_executor.rb +97 -0
  100. data/lib/garcon/task/thread_pool/cached.rb +71 -0
  101. data/lib/garcon/task/thread_pool/executor.rb +294 -0
  102. data/lib/garcon/task/thread_pool/fixed.rb +61 -0
  103. data/lib/garcon/task/thread_pool/worker.rb +90 -0
  104. data/lib/garcon/task/timer.rb +44 -0
  105. data/lib/garcon/task/timer_set.rb +194 -0
  106. data/lib/garcon/task/timer_task.rb +377 -0
  107. data/lib/garcon/task/waitable_list.rb +58 -0
  108. data/lib/garcon/utility/ansi.rb +199 -0
  109. data/lib/garcon/utility/at_random.rb +77 -0
  110. data/lib/garcon/utility/crypto.rb +292 -0
  111. data/lib/garcon/utility/equalizer.rb +146 -0
  112. data/lib/garcon/utility/faker/extensions/array.rb +22 -0
  113. data/lib/garcon/utility/faker/extensions/symbol.rb +9 -0
  114. data/lib/garcon/utility/faker/faker.rb +164 -0
  115. data/lib/garcon/utility/faker/faker/company.rb +17 -0
  116. data/lib/garcon/utility/faker/faker/hacker.rb +30 -0
  117. data/lib/garcon/utility/faker/faker/version.rb +3 -0
  118. data/lib/garcon/utility/faker/locales/en-US.yml +83 -0
  119. data/lib/garcon/utility/faker/locales/en.yml +21 -0
  120. data/lib/garcon/utility/file_helper.rb +170 -0
  121. data/lib/garcon/utility/hookers.rb +178 -0
  122. data/lib/garcon/utility/interpolation.rb +90 -0
  123. data/lib/garcon/utility/memstash.rb +364 -0
  124. data/lib/garcon/utility/misc.rb +54 -0
  125. data/lib/garcon/utility/msg_from_god.rb +62 -0
  126. data/lib/garcon/utility/retry.rb +238 -0
  127. data/lib/garcon/utility/timeout.rb +58 -0
  128. data/lib/garcon/utility/uber/builder.rb +91 -0
  129. data/lib/garcon/utility/uber/callable.rb +7 -0
  130. data/lib/garcon/utility/uber/delegates.rb +13 -0
  131. data/lib/garcon/utility/uber/inheritable_attr.rb +37 -0
  132. data/lib/garcon/utility/uber/options.rb +101 -0
  133. data/lib/garcon/utility/uber/uber_version.rb +3 -0
  134. data/lib/garcon/utility/uber/version.rb +33 -0
  135. data/lib/garcon/utility/url_helper.rb +100 -0
  136. data/lib/garcon/utils.rb +29 -0
  137. data/lib/garcon/version.rb +62 -0
  138. data/lib/garcun.rb +24 -0
  139. metadata +680 -0
@@ -0,0 +1,61 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ # License: Apache License, Version 2.0
5
+ # Copyright: (C) 2014-2015 Stefano Harding
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require_relative 'executor'
21
+
22
+ module Garcon
23
+
24
+ class FixedThreadPool < ThreadPoolExecutor
25
+
26
+ # Create a new thread pool.
27
+ #
28
+ # @param [Integer] num_threads
29
+ # The number of threads to allocate.
30
+ #
31
+ # @param [Hash] opts
32
+ # The options defining pool behavior.
33
+ #
34
+ # @option opts [Symbol] :fallback (`:abort`)
35
+ # The fallback policy
36
+ #
37
+ # @raise [ArgumentError] if `num_threads` is less than or equal to zero
38
+ #
39
+ # @raise [ArgumentError] if `fallback` is not a known policy
40
+ #
41
+ # @api public
42
+ def initialize(num_threads, opts = {})
43
+ fallback = opts.fetch(:fallback, :abort)
44
+
45
+ if num_threads < 1
46
+ raise ArgumentError, 'number of threads must be greater than zero'
47
+ elsif !FALLBACK_POLICY.include?(fallback)
48
+ raise ArgumentError, "#{fallback} is not a valid fallback policy"
49
+ end
50
+
51
+ opts = opts.merge(
52
+ min_threads: num_threads,
53
+ max_threads: num_threads,
54
+ fallback: fallback,
55
+ max_queue: DEFAULT_MAX_QUEUE_SIZE,
56
+ idletime: DEFAULT_THREAD_IDLETIMEOUT)
57
+
58
+ super(opts)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,90 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ # License: Apache License, Version 2.0
5
+ # Copyright: (C) 2014-2015 Stefano Harding
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'thread'
21
+ require_relative '../monotonic_time'
22
+
23
+ module Garcon
24
+
25
+ # @!visibility private
26
+ class ThreadPoolWorker
27
+
28
+ # @!visibility private
29
+ def initialize(queue, parent)
30
+ @queue = queue
31
+ @parent = parent
32
+ @mutex = Mutex.new
33
+ @last_activity = Garcon.monotonic_time
34
+ @thread = nil
35
+ end
36
+
37
+ # @!visibility private
38
+ def dead?
39
+ return @mutex.synchronize do
40
+ @thread.nil? ? false : ! @thread.alive?
41
+ end
42
+ end
43
+
44
+ # @!visibility private
45
+ def last_activity
46
+ @mutex.synchronize { @last_activity }
47
+ end
48
+
49
+ def status
50
+ @mutex.synchronize do
51
+ return 'not running' if @thread.nil?
52
+ @thread.status
53
+ end
54
+ end
55
+
56
+ # @!visibility private
57
+ def kill
58
+ @mutex.synchronize do
59
+ Thread.kill(@thread) unless @thread.nil?
60
+ @thread = nil
61
+ end
62
+ end
63
+
64
+ # @!visibility private
65
+ def run(thread = Thread.current)
66
+ @mutex.synchronize do
67
+ raise StandardError, 'already running' unless @thread.nil?
68
+ @thread = thread
69
+ end
70
+
71
+ loop do
72
+ task = @queue.pop
73
+ if task == :stop
74
+ @thread = nil
75
+ @parent.on_worker_exit(self)
76
+ break
77
+ end
78
+
79
+ begin
80
+ task.last.call(*task.first)
81
+ rescue => e
82
+ Chef::Log.debug "Caught exception => #{e}"
83
+ ensure
84
+ @last_activity = Garcon.monotonic_time
85
+ @parent.on_end_task
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ # License: Apache License, Version 2.0
5
+ # Copyright: (C) 2014-2015 Stefano Harding
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'thread'
21
+
22
+ module Garcon
23
+
24
+ # Perform the given operation asynchronously after the given number of
25
+ # seconds.
26
+ #
27
+ # @param [Fixnum] seconds
28
+ # The interval in seconds to wait before executing the task
29
+ #
30
+ # @yield the task to execute
31
+ #
32
+ # @return [Boolean] true
33
+ #
34
+ def timer(seconds, *args, &block)
35
+ raise ArgumentError, 'no block given' unless block_given?
36
+ if seconds < 0
37
+ raise ArgumentError, 'interval must be greater than or equal to zero'
38
+ end
39
+
40
+ Garcon.global_timer_set.post(seconds, *args, &block)
41
+ true
42
+ end
43
+ module_function :timer
44
+ end
@@ -0,0 +1,194 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ # License: Apache License, Version 2.0
5
+ # Copyright: (C) 2014-2015 Stefano Harding
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'thread'
21
+ require_relative 'event'
22
+ require_relative 'priority_queue'
23
+ require_relative 'executor'
24
+ require_relative 'single_thread_executor'
25
+ require_relative 'monotonic_time'
26
+ require_relative 'executor_options'
27
+
28
+ module Garcon
29
+
30
+ # Executes a collection of tasks, each after a given delay. A master task
31
+ # monitors the set and schedules each task for execution at the appropriate
32
+ # time. Tasks are run on the global task pool or on the supplied executor.
33
+ #
34
+ class TimerSet
35
+ include RubyExecutor
36
+ include ExecutorOptions
37
+
38
+ # Create a new set of timed tasks.
39
+ #
40
+ # @!macro [attach] executor_options
41
+ #
42
+ # @param [Hash] opts
43
+ # The options used to specify the executor on which to perform actions.
44
+ #
45
+ # @option opts [Executor] :executor
46
+ # When set use the given `Executor` instance. Three special values are
47
+ # also supported: `:task` returns the global task pool, `:operation`
48
+ # returns the global operation pool, and `:immediate` returns a new
49
+ # `ImmediateExecutor` object.
50
+ #
51
+ def initialize(opts = {})
52
+ @queue = PriorityQueue.new(order: :min)
53
+ @task_executor = get_executor_from(opts) || Garcon.global_io_executor
54
+ @timer_executor = SingleThreadExecutor.new
55
+ @condition = Condition.new
56
+ init_executor
57
+ enable_at_exit_handler!(opts)
58
+ end
59
+
60
+ # Post a task to be execute run after a given delay (in seconds). If the
61
+ # delay is less than 1/100th of a second the task will be immediately post
62
+ # to the executor.
63
+ #
64
+ # @param [Float] delay
65
+ # The number of seconds to wait for before executing the task.
66
+ #
67
+ # @yield the task to be performed.
68
+ #
69
+ # @raise [ArgumentError] f the intended execution time is not in the future.
70
+ #
71
+ # @raise [ArgumentError] if no block is given.
72
+ #
73
+ # @return [Boolean]
74
+ # True if the message is post, false after shutdown.
75
+ #
76
+ def post(delay, *args, &task)
77
+ raise ArgumentError, 'no block given' unless block_given?
78
+ delay = TimerSet.calculate_delay!(delay)
79
+
80
+ mutex.synchronize do
81
+ return false unless running?
82
+
83
+ if (delay) <= 0.01
84
+ @task_executor.post(*args, &task)
85
+ else
86
+ @queue.push(Task.new(Garcon.monotonic_time + delay, args, task))
87
+ @timer_executor.post(&method(:process_tasks))
88
+ end
89
+ end
90
+
91
+ @condition.signal
92
+ true
93
+ end
94
+
95
+ # @!visibility private
96
+ def <<(task)
97
+ post(0.0, &task)
98
+ self
99
+ end
100
+
101
+ # @!macro executor_method_shutdown
102
+ def kill
103
+ shutdown
104
+ end
105
+
106
+ # Schedule a task to be executed after a given delay (in seconds).
107
+ #
108
+ # @param [Float] delay
109
+ # The number of seconds to wait for before executing the task.
110
+ #
111
+ # @raise [ArgumentError] if the intended execution time is not in the future
112
+ #
113
+ # @raise [ArgumentError] if no block is given.
114
+ #
115
+ # @return [Float]
116
+ # The number of seconds to delay.
117
+ #
118
+ def self.calculate_delay!(delay)
119
+ if delay.is_a?(Time)
120
+ if delay <= now
121
+ raise ArgumentError, 'schedule time must be in the future'
122
+ end
123
+ delay.to_f - now.to_f
124
+ else
125
+ if delay.to_f < 0.0
126
+ raise ArgumentError, 'seconds must be greater than zero'
127
+ end
128
+ delay.to_f
129
+ end
130
+ end
131
+
132
+ private # P R O P R I E T À P R I V A T A Vietato L'accesso
133
+
134
+ # A struct for encapsulating a task and its intended execution time.
135
+ # It facilitates proper prioritization by overriding the comparison
136
+ # (spaceship) operator as a comparison of the intended execution
137
+ # times.
138
+ #
139
+ # @!visibility private
140
+ Task = Struct.new(:time, :args, :op) do
141
+ include Comparable
142
+
143
+ def <=>(other)
144
+ self.time <=> other.time
145
+ end
146
+ end
147
+
148
+ private_constant :Task
149
+
150
+ # @!visibility private
151
+ def shutdown_execution
152
+ @queue.clear
153
+ @timer_executor.kill
154
+ stopped_event.set
155
+ end
156
+
157
+ # Run a loop and execute tasks in the scheduled order and at the approximate
158
+ # scheduled time. If no tasks remain the thread will exit gracefully so that
159
+ # garbage collection can occur. If there are no ready tasks it will sleep
160
+ # for up to 60 seconds waiting for the next scheduled task.
161
+ #
162
+ # @!visibility private
163
+ def process_tasks
164
+ loop do
165
+ task = mutex.synchronize { @queue.peek }
166
+ break unless task
167
+
168
+ now = Garcon.monotonic_time
169
+ diff = task.time - now
170
+
171
+ if diff <= 0
172
+ # We need to remove the task from the queue before passing it to the
173
+ # executor, to avoid race conditions where we pass the peek'ed task
174
+ # to the executor and then pop a different one that's been added in
175
+ # the meantime.
176
+ #
177
+ # Note that there's no race condition between the peek and this pop -
178
+ # this pop could retrieve a different task from the peek, but that
179
+ # task would be due to fire now anyway (because @queue is a priority
180
+ # queue, and this thread is the only reader, so whatever timer is at
181
+ # the head of the queue now must have the same pop time, or a closer
182
+ # one, as when we peeked).
183
+ #
184
+ task = mutex.synchronize { @queue.pop }
185
+ @task_executor.post(*task.args, &task.op)
186
+ else
187
+ mutex.synchronize do
188
+ @condition.wait(mutex, [diff, 60].min)
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,377 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ # License: Apache License, Version 2.0
5
+ # Copyright: (C) 2014-2015 Stefano Harding
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require_relative 'dereferenceable'
21
+ require_relative 'observable'
22
+ require_relative 'atomic_boolean'
23
+ require_relative 'executor'
24
+ require_relative 'safe_task_executor'
25
+
26
+ module Garcon
27
+
28
+ # A very common currency pattern is to run a thread that performs a task at
29
+ # regular intervals. The thread that performs the task sleeps for the given
30
+ # interval then wakes up and performs the task. Lather, rinse, repeat... This
31
+ # pattern causes two problems. First, it is difficult to test the business
32
+ # logic of the task because the task itself is tightly coupled with the
33
+ # concurrency logic. Second, an exception raised while performing the task can
34
+ # cause the entire thread to abend. In a long-running application where the
35
+ # task thread is intended to run for days/weeks/years a crashed task thread
36
+ # can pose a significant problem. `TimerTask` alleviates both problems.
37
+ #
38
+ # When a `TimerTask` is launched it starts a thread for monitoring the
39
+ # execution interval. The `TimerTask` thread does not perform the task,
40
+ # however. Instead, the TimerTask launches the task on a separate thread.
41
+ # Should the task experience an unrecoverable crash only the task thread will
42
+ # crash. This makes the `TimerTask` very fault tolerant Additionally, the
43
+ # `TimerTask` thread can respond to the success or failure of the task,
44
+ # performing logging or ancillary operations. `TimerTask` can also be
45
+ # configured with a timeout value allowing it to kill a task that runs too
46
+ # long.
47
+ #
48
+ # One other advantage of `TimerTask` is that it forces the business logic to
49
+ # be completely decoupled from the concurrency logic. The business logic can
50
+ # be tested separately then passed to the `TimerTask` for scheduling and
51
+ # running.
52
+ #
53
+ # In some cases it may be necessary for a `TimerTask` to affect its own
54
+ # execution cycle. To facilitate this, a reference to the TimerTask instance
55
+ # is passed as an argument to the provided block every time the task is
56
+ # executed.
57
+ #
58
+ # The `TimerTask` class includes the `Dereferenceable` mixin module so the
59
+ # result of the last execution is always available via the `#value` method.
60
+ # Derefencing options can be passed to the `TimerTask` during construction or
61
+ # at any later time using the `#set_deref_options` method.
62
+ #
63
+ # `TimerTask` supports notification through the Ruby standard library
64
+ # {http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html
65
+ # Observable} module. On execution the `TimerTask` will notify the observers
66
+ # with three arguments: time of execution, the result of the block (or nil on
67
+ # failure), and any raised exceptions (or nil on success). If the timeout
68
+ # interval is exceeded the observer will receive a `Garcon::TimeoutError`
69
+ # object as the third argument.
70
+ #
71
+ # @example Basic usage
72
+ # tt = Garcon::TimerTask.new { puts 'Run! Go! Execute! GO! GO! GO!' }
73
+ # tt.execute
74
+ #
75
+ # tt.execution_interval # => 60 (default)
76
+ # tt.timeout_interval # => 30 (default)
77
+ #
78
+ # # wait 60 seconds...
79
+ # # => 'Run! Go! Execute! GO! GO! GO!'
80
+ #
81
+ # tt.shutdown # => true
82
+ #
83
+ # @example Configuring `:execution_interval` and `:timeout_interval`
84
+ # tt = Garcon::TimerTask.new(execution_interval: 5, timeout_interval: 5) do
85
+ # puts 'Execute! Execute! GO! GO! GO!'
86
+ # end
87
+ #
88
+ # tt.execution_interval # => 5
89
+ # tt.timeout_interval # => 5
90
+ #
91
+ # @example Immediate execution with `:run_now`
92
+ # tt = Garcon::TimerTask.new(run_now: true) { puts 'GO! GO! GO!' }
93
+ # tt.execute
94
+ #
95
+ # # => 'GO! GO! GO!'
96
+ #
97
+ # @example Last `#value` and `Dereferenceable` mixin
98
+ # tt = Garcon::TimerTask.new(dup_on_deref: true, execution_interval: 5) do
99
+ # Time.now
100
+ # end
101
+ #
102
+ # tt.execute
103
+ # Time.now # => 2015-03-21 08:56:50 -0700
104
+ # sleep(10)
105
+ # tt.value # => 2015-03-21 08:56:55 -0700
106
+ #
107
+ # @example Controlling execution from within the block
108
+ # timer_task = Garcon::TimerTask.new(execution_interval: 1) do |task|
109
+ # task.execution_interval.times { print 'Execute! ' }
110
+ # print "\n"
111
+ # task.execution_interval += 1
112
+ # if task.execution_interval > 5
113
+ # puts 'Executed...'
114
+ # task.shutdown
115
+ # end
116
+ # end
117
+ #
118
+ # timer_task.execute # blocking call - this task will stop itself
119
+ # # => Execute!
120
+ # # => Execute! Execute!
121
+ # # => Execute! Execute! Execute!
122
+ # # => Execute! Execute! Execute! Execute!
123
+ # # => Execute! Execute! Execute! Execute! Execute!
124
+ # # => Executed...
125
+ #
126
+ # @example Observation
127
+ # class TaskObserver
128
+ # def update(time, result, ex)
129
+ # if result
130
+ # print "(#{time}) Execution successfully returned #{result}\n"
131
+ # elsif ex.is_a?(Garcon::TimeoutError)
132
+ # print "(#{time}) Execution timed out\n"
133
+ # else
134
+ # print "(#{time}) Execution failed with error #{ex}\n"
135
+ # end
136
+ # end
137
+ # end
138
+ #
139
+ # tt = Garcon::TimerTask.new(execution_interval: 1, timeout_interval: 1) {
140
+ # 42
141
+ # }
142
+ # tt.add_observer(TaskObserver.new)
143
+ # tt.execute
144
+ #
145
+ # # => (2015-03-21 09:06:07 -0700) Execution successfully returned 42
146
+ # # => (2015-03-21 09:06:08 -0700) Execution successfully returned 42
147
+ # # => (2015-03-21 09:06:09 -0700) Execution successfully returned 42
148
+ # tt.shutdown
149
+ #
150
+ # tt = Garcon::TimerTask.new(execution_interval: 1, timeout_interval: 1) {
151
+ # sleep
152
+ # }
153
+ # tt.add_observer(TaskObserver.new)
154
+ # tt.execute
155
+ #
156
+ # # => (2015-03-21 09:07:10 -0700) Execution timed out
157
+ # # => (2015-03-21 09:07:12 -0700) Execution timed out
158
+ # # => (2015-03-21 09:07:14 -0700) Execution timed out
159
+ # tt.shutdown
160
+ #
161
+ # tt = Garcon::TimerTask.new(execution_interval: 1) { raise StandardError }
162
+ # tt.add_observer(TaskObserver.new)
163
+ # tt.execute
164
+ #
165
+ # # => (2015-03-21 09:12:11 -0700) Execution failed with error StandardError
166
+ # # => (2015-03-21 09:12:12 -0700) Execution failed with error StandardError
167
+ # # => (2015-03-21 09:12:13 -0700) Execution failed with error StandardError
168
+ # tt.shutdown
169
+ #
170
+ class TimerTask
171
+ include Dereferenceable
172
+ include RubyExecutor
173
+ include Observable
174
+
175
+ # Default :execution_interval in seconds.
176
+ EXECUTION_INTERVAL = 60
177
+
178
+ # Default :timeout_interval in seconds.
179
+ TIMEOUT_INTERVAL = 30
180
+
181
+ # Create a new TimerTask with the given task and configuration.
182
+ #
183
+ # @!macro [attach] timer_task_initialize
184
+ # @note
185
+ # Calls Garcon::Dereferenceable# set_deref_options passing opts. All
186
+ # options supported by Garcon::Dereferenceable can be set during object
187
+ # initialization.
188
+ #
189
+ # @param [Hash] opts
190
+ # The options defining task execution.
191
+ # @option opts [Integer] :execution_interval
192
+ # The number of seconds between task executions (defaults to:
193
+ # EXECUTION_INTERVAL)
194
+ # @option opts [Integer] :timeout_interval
195
+ # The number of seconds a task can run before it is considered to have
196
+ # failed (default: TIMEOUT_INTERVAL)
197
+ # @option opts [Boolean] :run_now
198
+ # Whether to run the task immediately upon instantiation or to wait
199
+ # until the first execution_interval has passed (default: false)
200
+ #
201
+ # @raise ArgumentError
202
+ # when no block is given.
203
+ #
204
+ # @yield to the block after :execution_interval seconds have passed since
205
+ # the last yield
206
+ # @yieldparam task a reference to the TimerTask instance so that the
207
+ # block can control its own lifecycle. Necessary since self will
208
+ # refer to the execution context of the block rather than the running
209
+ # TimerTask.
210
+ #
211
+ # @return [TimerTask]
212
+ # the new TimerTask.
213
+ #
214
+ # @see Garcon::Dereferenceable# set_deref_options
215
+ def initialize(opts = {}, &task)
216
+ raise ArgumentError.new('no block given') unless block_given?
217
+
218
+ init_executor
219
+ set_deref_options(opts)
220
+
221
+ self.execution_interval =
222
+ opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
223
+
224
+ self.timeout_interval =
225
+ opts[:timeout] || opts[:timeout_interval] || TIMEOUT_INTERVAL
226
+
227
+ @run_now = opts[:now] || opts[:run_now]
228
+ @executor = Garcon::SafeTaskExecutor.new(task)
229
+ @running = Garcon::AtomicBoolean.new(false)
230
+
231
+ self.observers = CopyOnNotifyObserverSet.new
232
+ end
233
+
234
+ # Is the executor running?
235
+ #
236
+ # @return [Boolean]
237
+ # True when running, false when shutting down or shutdown
238
+ def running?
239
+ @running.true?
240
+ end
241
+
242
+ # Execute a previously created TimerTask.
243
+ #
244
+ # @example Instance and execute in separate steps
245
+ # tt = Garcon::TimerTask.new(execution_interval: 10) { puts 'Sup!' }
246
+ # tt.running? # => false
247
+ # tt.execute
248
+ # tt.running? # => true
249
+ #
250
+ # @example Instance and execute in one line
251
+ # tt = Garcon::TimerTask.new(execution_interval: 10) { puts 'hi' }.execute
252
+ # tt.running? # => true
253
+ #
254
+ # @return [TimerTask]
255
+ # A reference to self.
256
+ def execute
257
+ mutex.synchronize do
258
+ if @running.false?
259
+ @running.make_true
260
+ schedule_next_task(@run_now ? 0 : @execution_interval)
261
+ end
262
+ end
263
+ self
264
+ end
265
+
266
+ # Create and execute a new TimerTask.
267
+ #
268
+ # @!macro timer_task_initialize
269
+ #
270
+ # @example
271
+ # task = Garcon::TimerTask.execute(execution_interval: 10) do
272
+ # puts 'Sappening d00d?'
273
+ # end
274
+ # task.running? # => true
275
+ def self.execute(opts = {}, &task)
276
+ TimerTask.new(opts, &task).execute
277
+ end
278
+
279
+ # @!attribute [rw] execution_interval
280
+ # @return [Fixnum]
281
+ # Number of seconds after the task completes before it is performed again.
282
+ def execution_interval
283
+ mutex.lock
284
+ @execution_interval
285
+ ensure
286
+ mutex.unlock
287
+ end
288
+
289
+ # @!attribute [rw] execution_interval
290
+ # @return [Fixnum]
291
+ # Number of seconds after the task completes before it is performed again.
292
+ def execution_interval=(value)
293
+ if (value = value.to_f) <= 0.0
294
+ raise ArgumentError.new 'must be greater than zero'
295
+ else
296
+ begin
297
+ mutex.lock
298
+ @execution_interval = value
299
+ ensure
300
+ mutex.unlock
301
+ end
302
+ end
303
+ end
304
+
305
+ # @!attribute [rw] timeout_interval
306
+ # @return [Fixnum]
307
+ # Number of seconds the task can run before it is considered failed.
308
+ def timeout_interval
309
+ mutex.lock
310
+ @timeout_interval
311
+ ensure
312
+ mutex.unlock
313
+ end
314
+
315
+ # @!attribute [rw] timeout_interval
316
+ # @return [Fixnum]
317
+ # Number of seconds the task can run before it is considered failed.
318
+ def timeout_interval=(value)
319
+ if (value = value.to_f) <= 0.0
320
+ raise ArgumentError.new('must be greater than zero')
321
+ else
322
+ begin
323
+ mutex.lock
324
+ @timeout_interval = value
325
+ ensure
326
+ mutex.unlock
327
+ end
328
+ end
329
+ end
330
+
331
+ private :post, :<<
332
+
333
+ protected # A T T E N Z I O N E A R E A P R O T E T T A
334
+
335
+ # @!visibility private
336
+ def shutdown_execution
337
+ @running.make_false
338
+ super
339
+ end
340
+
341
+ # @!visibility private
342
+ def kill_execution
343
+ @running.make_false
344
+ super
345
+ end
346
+
347
+ # @!visibility private
348
+ def schedule_next_task(interval = execution_interval)
349
+ Garcon::timer(interval, Garcon::Event.new, &method(:execute_task))
350
+ end
351
+
352
+ # @!visibility private
353
+ def execute_task(completion)
354
+ return unless @running.true?
355
+ Garcon::timer(execution_interval, completion, &method(:timeout_task))
356
+ _success, value, reason = @executor.execute(self)
357
+ if completion.try?
358
+ self.value = value
359
+ schedule_next_task
360
+ time = Time.now
361
+ observers.notify_observers do
362
+ [time, self.value, reason]
363
+ end
364
+ end
365
+ end
366
+
367
+ # @!visibility private
368
+ def timeout_task(completion)
369
+ return unless @running.true?
370
+ if completion.try?
371
+ self.value = value
372
+ schedule_next_task
373
+ observers.notify_observers(Time.now, nil, Garcon::TimeoutError.new)
374
+ end
375
+ end
376
+ end
377
+ end