symphony-metronome 0.2.2 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b9c864e52255a682b12651d0abab1479f1f4553f
4
- data.tar.gz: 2aa2e5dc892ee095913bf60e39f71301c788a87d
2
+ SHA256:
3
+ metadata.gz: 7924d613c9657d90bf4214bb9408d1f23bb9ecb677fdad75e47517fb8f30846d
4
+ data.tar.gz: e1ee7ba0db1f90c3d0e71ab05209493590d00f00d560b743938f986e869a872c
5
5
  SHA512:
6
- metadata.gz: ac3275fe2bd79a7c80f85d8ee8fb12dc83ea8ce9d9837c6be51c568b032db549b88fb50df37250fbe39fb5f37a304a8da7f1958f4b344423e1daf3444a4b435e
7
- data.tar.gz: 969541bea91973b7f98b4cc62eb03652d42fcfe167da37a66e186c407fd35ef9d948683ae0523784454b089393226fb683b25e34a40fdc87ccde7de016f3ad89
6
+ metadata.gz: a4146c565cf102bf1adf6979a4f57ffd5c2f8baedc8f6570da3654b0d65fd5183ae53778ee17d489de735c2f235875951d03347a84fe6143cf7fae86656ce95f
7
+ data.tar.gz: d9e6dd409f5206a1d98c8899626c2921465b013c2c160c84ffc43add998c3e32a672f5e75dbaa2f0c68f082dc6329f8b6cbaa9ac1def09847add88f7ca7549a2
checksums.yaml.gz.sig ADDED
Binary file
data/History.md ADDED
@@ -0,0 +1,27 @@
1
+ # Release History for Symphony-Metronome
2
+
3
+ ---
4
+
5
+ ## v0.3.0 [2023-03-20] Mahlon E. Smith <mahlon@martini.nu>
6
+
7
+ - Updates for Ruby 3.
8
+ - Dependency updates.
9
+ - Conversion to more modern ruby development tooling.
10
+
11
+
12
+ ## v0.2.1 [2015-07-08] Mahlon E. Smith <mahlon@martini.nu>
13
+
14
+ Enhancements:
15
+
16
+ - Add "last run" timestamps for recurring events.
17
+
18
+ Fixes:
19
+
20
+ - Repair exact start times for recurring events, ie
21
+ 'starting at 2015-01-01 09:00:00 run every other minute for 2 days'
22
+
23
+
24
+ ## v0.1.0 [2014-04-22] Mahlon E. Smith <mahlon@martini.nu>
25
+
26
+ Initial release.
27
+
data/README.md ADDED
@@ -0,0 +1,224 @@
1
+ # Metronome
2
+
3
+ home
4
+ : https://code.martini.nu/fossil/symphony-metronome
5
+
6
+ docs
7
+ : https://martini.nu/docs/symphony-metronome
8
+
9
+ github_mirror
10
+ : https://github.com/mahlonsmith/symphony-metronome
11
+
12
+
13
+
14
+ ## Description
15
+
16
+ Metronome is an interval scheduler and task runner. It can be used locally as a
17
+ cron replacement, or as a network-wide job executor. It is intended to be run
18
+ alongside Symphony, a Ruby AMQP event consumer.
19
+
20
+ Events are stored via simple database rows, and optionally themselves managed
21
+ via AMQP events. Interval/time values are expressed with intuitive English
22
+ phrases, ie.: 'at 2pm', or 'Starting in 20 minutes, run every 10 seconds and
23
+ then finish in 2 days', or 'execute 12 times during the next minute'.
24
+
25
+ ## Synopsis
26
+
27
+ Here's an example of a simple cron clone:
28
+
29
+ ```
30
+ #!ruby
31
+
32
+ require 'symphony/metronome'
33
+
34
+ Symphony.load_config
35
+
36
+ Symphony::Metronome.run do |opts, id|
37
+ Thread.new do
38
+ pid = fork { exec opts.delete('command') }
39
+ Process.waitpid( pid )
40
+ end
41
+ end
42
+ ```
43
+
44
+
45
+ And here's a simplistic timed AMQP message broadcaster, using existing Symphony
46
+ connection information:
47
+
48
+ ```
49
+ #!ruby
50
+
51
+ require 'symphony/metronome'
52
+
53
+ Symphony.load_config
54
+
55
+ Symphony::Metronome.run do |opts, id|
56
+ key = opts.delete( 'routing_key' ) or next
57
+ exchange = Symphony::Queue.amqp_exchange
58
+ exchange.publish( 'hi from Metronome!', routing_key: key )
59
+ end
60
+ ```
61
+
62
+ ## Adding Actions
63
+
64
+ There are two primary components to Metronome -- getting actions into
65
+ its database, and performing some task with those actions when the time
66
+ is appropriate.
67
+
68
+ By default, Metronome will start up an AMQP listener, attached to your
69
+ Symphony exchange, and wait for new scheduling messages. There are two
70
+ events it will take action on:
71
+
72
+ metronome.create:
73
+
74
+ Create a new scheduled event. The payload should be a hash. An
75
+ 'expression' key is required, that provides the interval description.
76
+ Anything additional is serialized to 'options', that are passed to the
77
+ block when the interval fires. You can populate it with anything
78
+ your task requires to execute.
79
+
80
+ metronome.delete:
81
+
82
+ The payload is the row ID of the action. Metronome removes it from
83
+ the database.
84
+
85
+ If you'd prefer not to use the AMQP listener, you can put actions into
86
+ Metronome using any database methodology you please. When the daemon
87
+ starts up or receives a HUP signal, it will re-read and schedule out
88
+ upcoming work.
89
+
90
+
91
+ ## Options
92
+
93
+ Metronome uses
94
+ [Configurability](https://rubygems.org/gems/configurability) to determine
95
+ behavior. The configuration is a [YAML](http://www.yaml.org/) file. It
96
+ shares AMQP configuration with Symphony, and adds metronome specific
97
+ controls in the 'metronome' key.
98
+
99
+ metronome:
100
+ splay: 0
101
+ listen: true
102
+ db: sqlite:///tmp/metronome.db
103
+
104
+
105
+ ### splay
106
+
107
+ Randomize all start times for actions by this many seconds on either
108
+ side of the original execution time. Defaults to none.
109
+
110
+ ### listen
111
+
112
+ Start up an AMQP listener using Symphony configuration, for remote
113
+ administration of schedule events. Defaults to true.
114
+
115
+ ### db
116
+
117
+ A [Sequel](https://rubygems.org/gems/sequel) connection URI. Currently,
118
+ Metronome is tested under SQLite and PostgreSQL. Defaults to a SQLite
119
+ file at /tmp/metronome.db.
120
+
121
+
122
+ ## Scheduling Examples
123
+
124
+ Note that Metronome is designed as an interval scheduler, not a
125
+ calendaring app. It doesn't have any concepts around phrases like "next
126
+ tuesday", or "the 3rd sunday after christmas". If that's what you're
127
+ after, check out the [chronic](http://rubygems.org/gems/chronic)
128
+ library instead.
129
+
130
+ Here are a small set of example expressions. Feel free to use the
131
+ *metronome-exp* utility to get a feel for what Metronome anticipates.
132
+
133
+ ```
134
+ in 30.5 minutes
135
+ once an hour
136
+ every 15 minutes for 2 days
137
+ at 2014-05-01
138
+ at 2014-04-01 14:00:25
139
+ at 2pm
140
+ starting at 2pm once a day
141
+ start in 1 hour from now run every 5 seconds end at 11:15pm
142
+ every other hour
143
+ run every 7th minute for a day
144
+ once a day ending in 1 week
145
+ run once a minute for an hour starting in 6 days
146
+ 10 times a minute for 2 days
147
+ run 45 times every hour
148
+ 30 times per day
149
+ start at 2010-01-02 run 12 times and end on 2010-01-03
150
+ starting in an hour from now run 6 times a minute for 2 hours
151
+ beginning a day from now, run 30 times per minute and finish in 2 weeks
152
+ execute 12 times during the next 2 minutes
153
+ once a minute beginning in 5 minutes
154
+ ```
155
+
156
+ In general, you can use reasonably intuitive phrasings. Capitalization,
157
+ whitespace, and punctuation doesn't matter. When describing numbers,
158
+ use digit/integer form instead of words, ie: '1 hour' instead of 'one
159
+ hour'.
160
+
161
+
162
+ ## Installation
163
+
164
+ ```
165
+ gem install symphony-metronome
166
+ ```
167
+
168
+ ## Contributing
169
+
170
+ You can check out the source via Fossil from the following uri:
171
+
172
+ % fossil clone https://code.martini.nu/fossil/symphony-metronome
173
+
174
+ or via its GitHub mirror at:
175
+
176
+ % git clone https://github.com/mahlonsmith/Symphony-Metronome
177
+
178
+ After checking out the source, run:
179
+
180
+ $ gem install -Ng
181
+ $ rake setup
182
+
183
+ This will install dependencies, and do any other necessary setup for
184
+ development.
185
+
186
+ Please report any issues
187
+ [here](https://code.martini.nu/fossil/symphony-metronome/tktnew).
188
+
189
+
190
+ ## Authors
191
+
192
+ - Mahlon E. Smith <mahlon@martini.nu>
193
+
194
+
195
+ ## License
196
+
197
+ Copyright (c) 2014-2023 Mahlon E. Smith
198
+ All rights reserved.
199
+
200
+ Redistribution and use in source and binary forms, with or without
201
+ modification, are permitted provided that the following conditions are met:
202
+
203
+ * Redistributions of source code must retain the above copyright notice,
204
+ this list of conditions and the following disclaimer.
205
+
206
+ * Redistributions in binary form must reproduce the above copyright notice,
207
+ this list of conditions and the following disclaimer in the documentation
208
+ and/or other materials provided with the distribution.
209
+
210
+ * Neither the name of the author/s, nor the names of the project's
211
+ contributors may be used to endorse or promote products derived from this
212
+ software without specific prior written permission.
213
+
214
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
215
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
216
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
217
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
218
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
219
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
220
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
221
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
222
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
223
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
224
+
@@ -1916,7 +1916,7 @@ self.interval_expression_en_main = 1;
1916
1916
  ### an expression was generated, you can 'reconstitute' an interval
1917
1917
  ### object this way.
1918
1918
  ###
1919
- def self::parse( exp, time=nil )
1919
+ def self::parse( exp, time=Time.now )
1920
1920
 
1921
1921
  # Normalize the expression before parsing
1922
1922
  #
@@ -1927,7 +1927,7 @@ self.interval_expression_en_main = 1;
1927
1927
  gsub( /([:\-])+/, '\1' ). # collapse multiple - or : chars
1928
1928
  gsub( /\.+$/, '' ) # trailing periods
1929
1929
 
1930
- event = new( exp, time || Time.now )
1930
+ event = new( exp, time )
1931
1931
  data = event.instance_variable_get( :@data )
1932
1932
 
1933
1933
  # Ragel interface variables
@@ -21,19 +21,17 @@ class Symphony::Metronome::ScheduledEvent
21
21
  config_key :metronome
22
22
 
23
23
 
24
- # Configure defaults.
25
- #
26
- CONFIG_DEFAULTS = {
27
- :db => 'sqlite:///tmp/metronome.db',
28
- :splay => 0
29
- }
30
-
31
- class << self
24
+ ### Configurability API.
25
+ ###
26
+ configurability do
27
+
28
+ ##
32
29
  # A Sequel-style DB connection URI.
33
- attr_reader :db
30
+ setting :db, default: 'sqlite:///tmp/metronome.db'
34
31
 
32
+ ##
35
33
  # Adjust recurring intervals by a random window.
36
- attr_reader :splay
34
+ setting :splay, default: 0
37
35
  end
38
36
 
39
37
 
@@ -62,7 +60,6 @@ class Symphony::Metronome::ScheduledEvent
62
60
  ### Delete any rows that are invalid expressions.
63
61
  ###
64
62
  def self::load
65
- now = Time.now
66
63
  events = SortedSet.new
67
64
 
68
65
  # Force reset the DB handle.
@@ -17,22 +17,15 @@ class Symphony::Metronome::Scheduler
17
17
  # Signals the daemon responds to.
18
18
  SIGNALS = [ :HUP, :INT, :TERM ]
19
19
 
20
- CONFIG_DEFAULTS = {
21
- :listen => false
22
- }
23
20
 
24
- class << self
21
+ ### Configurability API.
22
+ ###
23
+ configurability do
24
+
25
25
  # Should Metronome register and schedule events via AMQP?
26
26
  # If +false+, you'll need a separate way to add event actions
27
27
  # to the database, and manually HUP the daemon.
28
- attr_reader :listen
29
- end
30
-
31
- ### Configurability API
32
- ###
33
- def self::configure( config=nil )
34
- config = self.defaults.merge( config || {} )
35
- @listen = config.delete( :listen )
28
+ setting :listen, default: false
36
29
  end
37
30
 
38
31
 
@@ -131,17 +124,19 @@ class Symphony::Metronome::Scheduler
131
124
  def process_events
132
125
  now = Time.now
133
126
 
127
+ events_to_delete = []
128
+ events_to_add = []
134
129
  self.queue.each do |ev|
135
130
  next unless now >= ev.runtime
136
131
 
137
- self.queue.delete( ev )
132
+ events_to_delete << ev
138
133
  rv = ev.fire( &@proc )
139
134
 
140
135
  # Reschedule the event and place it back on the queue.
141
136
  #
142
137
  if ev.event.recurring
143
138
  ev.reset_runtime
144
- self.queue.add( ev ) unless rv.nil?
139
+ events_to_add << ev unless rv.nil?
145
140
 
146
141
  # It was a single run event, torch it!
147
142
  #
@@ -150,6 +145,9 @@ class Symphony::Metronome::Scheduler
150
145
 
151
146
  end
152
147
  end
148
+
149
+ events_to_delete.map{|e| self.queue.delete( e ) }
150
+ events_to_add.map{|e| self.queue.add( e ) }
153
151
  end
154
152
 
155
153
  end # Symphony::Metronome::Scheduler
@@ -9,10 +9,7 @@ module Symphony::Metronome
9
9
  Configurability
10
10
 
11
11
  # Library version constant
12
- VERSION = '0.2.2'
13
-
14
- # Version-control revision constant
15
- REVISION = %q$Revision: 87e45dca63b3 $
12
+ VERSION = '0.3.0'
16
13
 
17
14
  # The name of the environment variable to check for config file overrides
18
15
  CONFIG_ENV = 'METRONOME_CONFIG'
@@ -23,11 +20,11 @@ module Symphony::Metronome
23
20
  # The data directory that contains migration files.
24
21
  #
25
22
  DATADIR = if ENV['METRONOME_DATADIR']
26
- Pathname.new( ENV['METRONOME_DATADIR'] )
27
- elsif Gem.datadir( 'symphony-metronome' )
28
- Pathname.new( Gem.datadir('symphony-metronome') )
23
+ Pathname( ENV['METRONOME_DATADIR'] )
24
+ elsif Gem.loaded_specs[ 'symphony-metronome' ] && File.exist?( Gem.loaded_specs['symphony-metronome'].datadir )
25
+ Pathname( Gem.loaded_specs['symphony-metronome'].datadir )
29
26
  else
30
- Pathname.new( __FILE__ ).dirname.parent.parent + 'data/symphony-metronome'
27
+ Pathname( __FILE__ ).dirname.parent.parent + 'data/symphony-metronome'
31
28
  end
32
29
 
33
30
 
data.tar.gz.sig ADDED
@@ -0,0 +1 @@
1
+ ���)�����;�Dҫ~7���^ە��9�b���e�1?�J��"LX��2/�'ΒV����Ie5�Q)E��y�&bӧ�Zm�,��eaE�'>�e'��a^�܏j1Lֈy+��w�ʭI�M�d�u�l�: I�K��E%�fgl@�#H���x���>_Λ��p���os��l2� �E�{�D���sIN�>�h@��Z���GtLt�\rh�B�H)�9Z����#���}@�+�ϥ�L;z"ϔT�QGb���&��.g�v�0 ?�Q��@�y����ԞkA���A�������As(��=k�Ƥ���͢��ew����I�ݷ*?Y��՚_hN{�p* �<����`���#4"�R����b�#b��#ڶvj��