cron-table 0.2

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
+ SHA256:
3
+ metadata.gz: ae408479bf5161e8a60f21fd10758b7bc9790f2e47eb13517127f78ec09d9174
4
+ data.tar.gz: 70787c17ee8e536bb73c8226162765e5c7bc6a3b888cc6c3686df83f676004cc
5
+ SHA512:
6
+ metadata.gz: 82b82aa4118fefa72c3896bb3850e281b8987205ecee857a5266c6674c81613ada788f3090b696e15a15761641cc2f471e7c96bafe4d9547fe5d722d8d58e497
7
+ data.tar.gz: fe33d54efd7eb2036125202dada55394624a81c17d461d512cda2693cedd4c9f943228dbae0b783ac959b474fec9bfc0298d449df9d7e2d66d99581ac0c86e86
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2023 TODO: Write your name
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # CronTable
2
+ Basic cron-like system to schedule jobs
3
+
4
+ ## Setup
5
+ 1. Add `cron-table` gem
6
+ 2. Run `rails app:cron_table:install:migrations`
7
+ 3. Run `rails db:migrate` to apply migrations
8
+
9
+ ## Usage
10
+ 1. Add `include CronTable::Schedule` to the job
11
+ 2. Define cron schedule using `crontable(every: <interval>)`
12
+ 3. Use block if cron requires params, eg `crontable(every: 1.day) { perform_later(Time.now) }`
13
+
14
+ ## License
15
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1,6 @@
1
+ module Cron
2
+ module Table
3
+ class ApplicationController < ActionController::Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module CronTable
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module CronTable
2
+ class Item < ApplicationRecord
3
+ self.table_name = "cron_table"
4
+ end
5
+ end
@@ -0,0 +1,67 @@
1
+ class CronTable::Server
2
+ SLEEP = 1.second..1.day
3
+ SLEEP_IDLE = 1.hour
4
+
5
+ def initialize
6
+ end
7
+
8
+ def sync!
9
+ crons = CronTable.all.clone
10
+ deleted = []
11
+ CronTable::Item.transaction do
12
+ CronTable::Item.lock.all.each do |cron|
13
+ if definition = crons.delete(cron.key)
14
+ cron.update(next_run_at: definition.next_run_at(Time.now)) if cron.next_run_at.nil?
15
+ elsif cron.next_run_at.present?
16
+ deleted << cron.key
17
+ end
18
+ end
19
+ CronTable::Item.where(key: deleted).update(next_run_at: nil)
20
+ crons.each do |key, definition|
21
+ CronTable::Item.create(key: key, next_run_at: definition.next_run_at(Time.now))
22
+ end
23
+ end
24
+ end
25
+
26
+ def run!
27
+ @exit = false
28
+ next_run_at = CronTable::Item.minimum(:next_run_at) || SLEEP_IDLE.from_now
29
+ interruptible_sleep(next_run_at.to_f - Time.now.to_f)
30
+
31
+ CronTable::Item.lock.where(next_run_at: ..Time.now).each do |cron|
32
+ process(cron)
33
+ end
34
+ end
35
+
36
+ def exit!
37
+ @exit = true
38
+ interrupt!
39
+ end
40
+
41
+ def exit?
42
+ @exit.present?
43
+ end
44
+
45
+ private
46
+
47
+ def process(cron)
48
+ Rails.application.reloader.wrap do
49
+ definition = CronTable.all.fetch(cron.key)
50
+ definition.call
51
+
52
+ cron.update(last_run_at: Time.now, next_run_at: definition.next_run_at(Time.now))
53
+ end
54
+ rescue => e
55
+ cron.update(next_run_at: nil)
56
+ Rails.error.report(e, handled: true, severity: :error, context: { cron: cron.id })
57
+ end
58
+
59
+ def interruptible_sleep(seconds)
60
+ @sleep, @interrupt = IO.pipe
61
+ IO.select([@sleep], nil, nil, seconds.to_i.clamp(SLEEP))
62
+ end
63
+
64
+ def interrupt!
65
+ @interrupt&.close
66
+ end
67
+ end
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Cron table</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+ </head>
8
+ <body>
9
+
10
+ <%= yield %>
11
+
12
+ </body>
13
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ CronTable::Engine.routes.draw do
2
+ end
@@ -0,0 +1,11 @@
1
+ class CreateCronTable < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :cron_table do |t|
4
+ t.string :key, null: false, unique: true
5
+
6
+ t.datetime :next_run_at, index: true
7
+ t.datetime :last_run_at
8
+ t.datetime :created_at, null: false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module CronTable
2
+ class Definition < Struct.new(:key, :every, :block, keyword_init: true)
3
+ delegate :call, to: :block
4
+
5
+ def next_run_at(now)
6
+ now + every
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module CronTable
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace CronTable
4
+
5
+ server do
6
+ Thread.new do
7
+ cron = CronTable::Server.new
8
+ cron.sync!
9
+ cron.run until true
10
+ end if CronTable.attach_to_server
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ require "active_support/all"
2
+
3
+ module CronTable
4
+ module Schedule
5
+ class DuplicateKeyError < ArgumentError
6
+ def initialize(key)
7
+ @key = key
8
+ end
9
+
10
+ def message = "There can be only one crontable entry with key `#{@key}`. Use `crontable(key: '<other unique name>')` to select a different key"
11
+ end
12
+
13
+ class MissingBlockError < ArgumentError
14
+ def message = "Provide a block to `crontable(..) { }` with code to execute"
15
+ end
16
+
17
+ extend ActiveSupport::Concern
18
+
19
+ included do |_base|
20
+ singleton_class.prepend(PrependedClassMethods)
21
+ end
22
+
23
+ module PrependedClassMethods
24
+ def crontable(every:, key: self.name, &block)
25
+ raise DuplicateKeyError.new(key) if CronTable.all.key? key
26
+
27
+ block ||= -> { self.perform_later } if self.respond_to?(:perform_later)
28
+ raise MissingBlockError if block.nil?
29
+
30
+ CronTable.all[key] = Definition.new(key:, every:, block:)
31
+
32
+ self
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module CronTable
2
+ VERSION = "0.2"
3
+ end
data/lib/cron-table.rb ADDED
@@ -0,0 +1,23 @@
1
+ require "cron-table/version"
2
+ require "cron-table/engine"
3
+ require "cron-table/definition"
4
+ require "cron-table/schedule"
5
+
6
+ module CronTable
7
+ mattr_accessor :attach_to_server, default: Rails.env.production?
8
+
9
+ mattr_accessor :preload_dirs, default: ["app/jobs"]
10
+
11
+ @@all = nil
12
+ def self.all
13
+ if @@all.nil?
14
+ @@all = {}
15
+
16
+ CronTable.preload_dirs.each do |dir|
17
+ Rails.autoloaders.main.eager_load_dir(Rails.root.join(dir))
18
+ end if CronTable.preload_dirs
19
+ end
20
+
21
+ @@all
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ namespace :cron_table do
2
+ desc "Run cron_table scheduler"
3
+ task run: :environment do
4
+ cron = CronTable::Server.new
5
+ cron.sync!
6
+
7
+ Signal.trap("INT") { cron.exit! }
8
+ Signal.trap("TERM") { cron.exit! }
9
+
10
+ cron.run! until cron.exit?
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cron-table
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.2'
5
+ platform: ruby
6
+ authors:
7
+ - twratajczak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-07-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mocha
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rufo
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.16'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.16'
55
+ description: Simple system to schedule recurring jobs for Rails
56
+ email:
57
+ - twratajczak@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - MIT-LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - app/controllers/cron/table/application_controller.rb
66
+ - app/models/cron_table/application_record.rb
67
+ - app/models/cron_table/item.rb
68
+ - app/services/cron_table/server.rb
69
+ - app/views/layouts/cron-table/application.html.erb
70
+ - config/routes.rb
71
+ - db/migrate/20230723000000_create_cron_table.rb
72
+ - lib/cron-table.rb
73
+ - lib/cron-table/definition.rb
74
+ - lib/cron-table/engine.rb
75
+ - lib/cron-table/schedule.rb
76
+ - lib/cron-table/version.rb
77
+ - lib/tasks/cron_table_tasks.rake
78
+ homepage: https://github.com/cron-table/cron-table
79
+ licenses:
80
+ - MIT
81
+ metadata:
82
+ homepage_uri: https://github.com/cron-table/cron-table
83
+ source_code_uri: https://github.com/cron-table/cron-table
84
+ changelog_uri: https://github.com/cron-table/cron-table/blob/main/CHANGELOG.md
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubygems_version: 3.3.26
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Basic cron-like scheduling system for Rails
104
+ test_files: []