symphony-metronome 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 37cbbf6323e8b23dc0c63e6c0a7e6e6c5162931e
4
- data.tar.gz: c55bc0d55c479f0598cf0f191888ff193f05710b
2
+ SHA256:
3
+ metadata.gz: 7924d613c9657d90bf4214bb9408d1f23bb9ecb677fdad75e47517fb8f30846d
4
+ data.tar.gz: e1ee7ba0db1f90c3d0e71ab05209493590d00f00d560b743938f986e869a872c
5
5
  SHA512:
6
- metadata.gz: 63abf234a35e8cca4a92a734220cb62eedf2d91f5bf88842225ab0d1a5b7ce154b62205155271417a3246747407e8bd06b17cfd1bd8c17ffc6d93974bb182965
7
- data.tar.gz: c68f12fe582f81931262cc902cb316254b9f30d021bc5f924c7eba8d5575b4a0875ef9d7069efda10c6f735b8b9434b5462c8889a7bebc66bbe8f3dec70208b4
6
+ metadata.gz: a4146c565cf102bf1adf6979a4f57ffd5c2f8baedc8f6570da3654b0d65fd5183ae53778ee17d489de735c2f235875951d03347a84fe6143cf7fae86656ce95f
7
+ data.tar.gz: d9e6dd409f5206a1d98c8899626c2921465b013c2c160c84ffc43add998c3e32a672f5e75dbaa2f0c68f082dc6329f8b6cbaa9ac1def09847add88f7ca7549a2
checksums.yaml.gz.sig CHANGED
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.1'
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 CHANGED
Binary file