gnomon 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 991d919e3c0f3196568199c69f36ced61f8f9213
4
+ data.tar.gz: 10bf4317d05abb7f974f5ab70d16f80042a0188c
5
+ SHA512:
6
+ metadata.gz: 48bd31e3ff530439e32fdaaee93c9c9577584ec85e20d0645070cbf6794fd9a803a088bfad4c352153d039368b9cfae8eb68340cd9a490f4ef6be37fff0d2d92
7
+ data.tar.gz: 4ba79e9a4fa976dca8ed416ddaca6ff0a8646f9b441a23aa2f63a95c21e8536e2d5269b41b257831820b93acb78560c9ea61e8c5f24102eeaa1e2973d0127fb8
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .idea
2
+ Gemfile.lock
3
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Paul Duncan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # Gnomon
2
+
3
+ Simple event scheduling for Ruby
4
+
5
+ ## Presentation
6
+
7
+ This library provides an easy-to-use event scheduler for Ruby.
8
+ This allows easy development of asynchronous time-based triggers.
9
+ Both intervals and points are available for scheduling.
10
+
11
+ ## Installation
12
+
13
+ ### Gemfile
14
+ ```ruby
15
+ gem 'gnomon'
16
+ ```
17
+
18
+ ### Terminal
19
+ ```bash
20
+ gem install -V gnomon
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### Introduction
26
+
27
+ First, a *Gnomon* scheduler should be created:
28
+
29
+ ```ruby
30
+ require 'gnomon'
31
+ sched = Gnomon.new
32
+ ```
33
+
34
+ ### Managing the scheduler
35
+
36
+ The scheduler itself is a simple [Runify](https://rubygems.org/gems/runify) service which can be controlled via the typical methods (_startup_, _shutdown_, _restart_).
37
+
38
+ ```ruby
39
+ sched.startup # Start the Scheduler Service
40
+ sched.restart # Re-Start the Scheduler Service
41
+ sched.shutdown # Stop the Scheduler Service
42
+ ```
43
+
44
+ Events can then be scheduled by using either the _schedule_ generic method, or one of the three shortcut methods _schedule_at_, _schedule_in_, _schedule_every_.
45
+
46
+ All four methods take an _id_ argument, which can be used to later de-schedule an event (or a group of events).
47
+
48
+ Each method also accepts a _block_, which will be executed when the event triggers.
49
+ Finally, the _*args_ argument will be passed as-is to the _block_ upon trigger.
50
+
51
+ Any time an event triggers, a new thread is created around the event's _block_, to which is passed the event's _*args_.
52
+
53
+ ### Granularity
54
+
55
+ Internally, the scheduler checks the system clock at regular intervals, and compares the next event in the queue to determine whether it's time to trigger.
56
+ This means that the scheduler has an inherent "minimal time resolution" - events can not be triggered at smaller intervals than this minimum resolution.
57
+ So why not set this directly to an infinitesimal value? Simply because the scheduler would basically spend most of its time (and the host's CPU cycles) just checking the system clock.
58
+
59
+ By default, the granularity is set to *1 second*, which is fine for most usages.
60
+ For sub-second scheduling (or very wide scheduling), the granularity can be set to any value by passing it as argument to the constructor:
61
+
62
+ ```ruby
63
+ require 'gnomon'
64
+ sched_ms = Gnomon.new 0.001 # Create a Scheduler with milli-second granularity
65
+ sched_h = Gnomon.new 3600 # Create a Scheduler with hour granularity
66
+ ```
67
+
68
+ ### Scheduling events
69
+
70
+ #### *schedule* _id_, _mode_, _mode_options_, _timespec_, _*args_, _&block_
71
+
72
+ The _mode_ argument can be one of the following:
73
+ * *:at* -> Trigger the event _once_ at a given date/time (provided by the _timespec_ argument)
74
+ * *:in* -> Trigger the event _once_ after a certain amount of time has passed (provided by the _timespec_ argument)
75
+ * *:every* -> Trigger the event _periodically_ at regular intervals (provided by the _timespec_ argument)
76
+
77
+ Any event scheduled in *:at* or *:in* mode will trigger only once, and is _automatically de-scheduled_ after triggering.
78
+
79
+ The shortcut methods presented below do not perform anything other than call the _schedule_ method with the correct _mode_ argument set.
80
+
81
+ #### *schedule_at* _id_, _date_time_, _*args_, _&block_
82
+
83
+ The _schedule_at_ method schedules _&block_ to be run *once*, at exactly _date_time_, which can be any string representation of a date/time.
84
+
85
+ #### *schedule_in* _id_, _time_, _*args_, _&block_
86
+
87
+ The _schedule_in_ method schedules _&block_ to be run *once*, in exactly _time_ seconds.
88
+
89
+ #### *schedule_every* _id_, _interval_, _async_, _*args_, _&block_
90
+
91
+ The _schedule_every_ method schedules _&block_ to be run *periodically*, once every _interval_ seconds.
92
+ The _async_ argument determines when the next run is scheduled for each run:
93
+ * *async = true* -> The next trigger is scheduled *as soon as the event triggers* (before running the event's _block_).
94
+ * *async = false* -> The next trigger is scheduled *as soon as the event _block_ completes*.
95
+
96
+ ### De-scheduling events
97
+
98
+ Previously-scheduled events can be de-scheduled through the _deschedule_ method, by specifying the *id* used during initial scheduling of the event.
99
+ An optional _keep_running_ argument can be set to true to indicate that any "next scheduled trigger" should remain in the event queue.
100
+
101
+ ## Examples
102
+
103
+ ### Basic scheduling
104
+
105
+ ```ruby
106
+ #!/usr/bin/env ruby
107
+
108
+ require 'gnomon'
109
+
110
+ # Create Scheduler
111
+ sched = Gnomon.new
112
+
113
+ # Events can be scheduled before running the scheduler
114
+ sched.schedule_at(nil, "April 1 2000 10:00 AM") { puts 'April fool\'s day!' }
115
+
116
+ # Start Scheduler
117
+ sched.startup
118
+
119
+ # Events can be scheduled while the scheduler is running
120
+ sched.schedule_in(nil, 5.5) { puts 'Five and half seconds have passed :)' }
121
+ sched.schedule_every(nil, 0.1, false) { puts 'Refreshing something...' }
122
+
123
+ # Do something while scheduler is running...
124
+
125
+ # Shutdown Scheduler
126
+ sched.shutdown
127
+ ```
128
+
129
+ ### Self-de-scheduling event
130
+
131
+ ```ruby
132
+ #!/usr/bin/env ruby
133
+
134
+ require 'gnomon'
135
+
136
+ # Create Scheduler
137
+ sched = Gnomon.new
138
+
139
+ # Start Scheduler
140
+ sched.startup
141
+
142
+ # Run this every second, but five times only (have the event de-schedule itself after 5 triggers)
143
+ counter = 0
144
+ sched.schedule_every(:foo_task, 1, false) { puts "Run ##{counter}"; sched.deschedule :foo_task if counter >= 5 }
145
+
146
+ # Shutdown Scheduler
147
+ sched.shutdown
148
+ ```
149
+
150
+ ### Passing arguments
151
+
152
+ ```ruby
153
+ #!/usr/bin/env ruby
154
+
155
+ require 'gnomon'
156
+
157
+ # Create Scheduler
158
+ sched = Gnomon.new
159
+
160
+ # Start Scheduler
161
+ sched.startup
162
+
163
+ # Events can be scheduled while the scheduler is running
164
+ sched.schedule_in(nil, 5.5, 'Hello world!') { |s| puts "Five and half seconds have passed, time to say \"#{s}\"" }
165
+ sched.schedule_in(nil, 10, :users, [:foo, :bar]) { |category, entities| puts "Refreshing #{category}: #{entities.join ', '}" }
166
+
167
+ # Do something while scheduler is running...
168
+
169
+ # Shutdown Scheduler
170
+ sched.shutdown
171
+ ```
172
+
173
+ ## License
174
+
175
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+ require 'bundler/gem_tasks'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.pattern = 'test/**/test*.rb'
7
+ end
8
+
9
+ task :default => :test
data/gnomon.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gnomon/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'gnomon'
8
+ spec.version = Gnomon::VERSION
9
+ spec.authors = ['Eresse']
10
+ spec.email = ['eresse@eresse.net']
11
+
12
+ spec.summary = 'Simple event scheduling for Ruby'
13
+ spec.description = 'Provides a simple, easy-to-use, event scheduler'
14
+ spec.homepage = 'http://redmine.eresse.net/projects/gnomon'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_development_dependency 'bundler'
21
+ spec.add_development_dependency 'rake'
22
+ spec.add_runtime_dependency 'minitest'
23
+ spec.add_runtime_dependency 'runify'
24
+ end
data/lib/gnomon.rb ADDED
@@ -0,0 +1,148 @@
1
+ # Gnomon
2
+ # by Eresse <eresse@eresse.net>
3
+
4
+ # Internal Includes
5
+ require 'gnomon/version'
6
+
7
+ # External Includes
8
+ require 'date'
9
+ require 'thread'
10
+ require 'runify'
11
+
12
+ # Gnomon Class
13
+ class Gnomon
14
+
15
+ # Defaults
16
+ DEFAULT_GRANULARITY = 1
17
+
18
+ # Runify
19
+ include Runify
20
+
21
+ # Construct
22
+ def initialize granularity = DEFAULT_GRANULARITY
23
+
24
+ # Set Granularity
25
+ @granularity = granularity
26
+
27
+ # Create Event Queue
28
+ @equeue = []
29
+
30
+ # Create Event Schedule
31
+ @esched = []
32
+
33
+ # Create Lock
34
+ @lock = Mutex.new
35
+ end
36
+
37
+ # Schedule Event
38
+ def schedule id, mode, mode_options, timespec, *args, &block
39
+
40
+ # Synchronize
41
+ @lock.synchronize do
42
+
43
+ # Create Event
44
+ e = {
45
+ id: id,
46
+ ts: timespec,
47
+ mode: mode,
48
+ mopt: mode_options,
49
+ args: args,
50
+ blck: block
51
+ }
52
+
53
+ # Register Event Scheduling
54
+ @esched << e
55
+
56
+ # Enqueue Next Run
57
+ enqueue_next_run e
58
+ end
59
+ end
60
+
61
+ # Schedule Event at a given date/time
62
+ def schedule_at id, date_time, *args, &block
63
+ schedule id, :at, {}, date_time, *args, &block
64
+ end
65
+
66
+ # Schedule Event in a given number of seconds
67
+ def schedule_in id, time, *args, &block
68
+ schedule id, :in, {}, time, *args, &block
69
+ end
70
+
71
+ # Schedule Event at a given interval (seconds)
72
+ def schedule_every id, interval, async, *args, &block
73
+ schedule id, :every, { async: async }, interval, *args, &block
74
+ end
75
+
76
+ # De-schedule
77
+ def deschedule id, keep_running = false
78
+
79
+ # Synchronize
80
+ @lock.synchronize do
81
+
82
+ # De-schedule Event
83
+ @esched.delete_if { |e| e[:id] == id }
84
+
85
+ # De-queue any next run
86
+ @equeue.delete_if { |e| e[:event][:id] == id } unless keep_running
87
+ end
88
+ end
89
+
90
+ # Privates
91
+ private
92
+
93
+ # Enqueue Next Run
94
+ def enqueue_next_run event
95
+ next_run = next_run event[:mode], event[:ts]
96
+ pos = @equeue.index { |p| p[:next_run] >= next_run } || 0
97
+ @equeue.insert pos, { next_run: next_run, event: event }
98
+ end
99
+
100
+ # Determine Next Run
101
+ def next_run mode, ts
102
+ case mode
103
+ when :at
104
+ DateTime.parse(ts).to_time.to_f
105
+ when :every
106
+ when :in
107
+ Time.at(Time.now.to_f + ts.to_f).to_f
108
+ else
109
+ raise "Unknown scheduling mode [#{mode}]"
110
+ end
111
+ end
112
+
113
+ # Run
114
+ def run
115
+ update until @stop
116
+ end
117
+
118
+ # Update
119
+ def update
120
+
121
+ # Synchronize { Trigger Next Event in Queue }
122
+ @lock.synchronize { trigger @equeue.shift[:event] if @equeue.first && @equeue.first[:next_run] <= Time.now.to_f }
123
+
124
+ # Sleep
125
+ sleep @granularity
126
+ end
127
+
128
+ # Trigger Event
129
+ def trigger event
130
+
131
+ # Spawn Thread around Event's Block
132
+ Thread.new do
133
+
134
+ # De-schedule non-periodic events
135
+ @esched.delete event unless event[:mode] == :every
136
+ @equeue.delete_if { |e| e[:event] == event } unless event[:mode] == :every
137
+
138
+ # Enqueue next run BEFORE calling block for asynchronous periodic events
139
+ enqueue_next_run event if event[:mode] == :every && event[:mopt][:async]
140
+
141
+ # Pass Event Args to Event Block
142
+ event[:blck].call *(event[:args])
143
+
144
+ # Enqueue next run AFTER calling block for synchronous periodic events
145
+ enqueue_next_run event if event[:mode] == :every && !(event[:mopt][:async])
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,9 @@
1
+ # Gnomon
2
+ # by Eresse <eresse@eresse.net>
3
+
4
+ # Gnomon Class
5
+ class Gnomon
6
+
7
+ # Version
8
+ VERSION = '0.1.0'
9
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gnomon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Eresse
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: runify
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Provides a simple, easy-to-use, event scheduler
70
+ email:
71
+ - eresse@eresse.net
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - Gemfile
78
+ - LICENSE.txt
79
+ - README.md
80
+ - Rakefile
81
+ - gnomon.gemspec
82
+ - lib/gnomon.rb
83
+ - lib/gnomon/version.rb
84
+ homepage: http://redmine.eresse.net/projects/gnomon
85
+ licenses:
86
+ - MIT
87
+ metadata: {}
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 2.6.10
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: Simple event scheduling for Ruby
108
+ test_files: []