Skip to content

Commit

Permalink
initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
nickelser committed May 5, 2015
1 parent 804f594 commit abe7f90
Show file tree
Hide file tree
Showing 11 changed files with 390 additions and 239 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 0.1.0

- First release.
22 changes: 22 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Copyright (c) 2015 Nick Elser

MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
54 changes: 32 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,49 @@
# Zhong

Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/zhong`. To experiment with that code, run `bin/console` for an interactive prompt.
Useful, reliable distributed cron.

TODO: Delete this and the text above, and describe your gem
# Installation

## Installation

Add this line to your application's Gemfile:
Add this line to your application’s Gemfile:

```ruby
gem 'zhong'
```

And then execute:

$ bundle

Or install it yourself as:

$ gem install zhong

## Usage

TODO: Write usage instructions here
```ruby
r = Redis.new

Zhong.schedule(redis: r) do
category "stuff" do
every(5.seconds, "foo") { puts "foo" }
every(1.week, "baz", at: "mon 22:45") { puts "baz" }
end

category "clutter" do
every(1.second, "compute", if: -> (t) { rand < 0.5 }) { puts "something happened" }
end
end
```

## Development
## TODO
- better logging
- error handling
- tests
- examples
- callbacks
- generic handler

After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
## History

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
View the [changelog](https://github.com/nickelser/zhong/blob/master/CHANGELOG.md).

## Contributing

1. Fork it ( https://github.com/[my-github-username]/zhong/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request
Everyone is encouraged to help improve this project. Here are a few ways you can help:

- [Report bugs](https://github.com/nickelser/zhong/issues)
- Fix bugs and [submit pull requests](https://github.com/nickelser/zhong/pulls)
- Write, clarify, or fix documentation
- Suggest or add new features
7 changes: 7 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
require "bundler/gem_tasks"
require "rake/testtask"

task default: :test
Rake::TestTask.new do |t|
t.libs << "test"
t.pattern = "test/**/*_test.rb"
end
218 changes: 11 additions & 207 deletions lib/zhong.rb
Original file line number Diff line number Diff line change
@@ -1,218 +1,22 @@
require "zhong/version"
require "monitor"
require "logger"
require "redis"
require "suo"
require "active_support/time"

module Zhong
class Job
attr_reader :description, :name, :category

def initialize(manager:, name:, every:, description: nil, category: nil, &block)
@every = every
@description = description
@category = category
@block = block
@redis = manager.config[:redis]
@logger = manager.config[:logger]
@name = name
@lock = Suo::Client::Redis.new(lock_key, client: @redis)
@timeout = 5

refresh_last_ran
end

def run?(time = Time.now)
!@last_ran || next_run_at < time
end

def run(time = Time.now)
return unless run?(time)

if running?
@logger.info "already running: #{@name}"
return
end

ran_set = @lock.lock do
refresh_last_ran

break unless run?(time)

if disabled?
@logger.info "disabled: #{@name}"
break
end

@logger.info "running: #{@name}"

@thread = Thread.new { @block.call } if @block

ran!(time)
end

@logger.info "unable to acquire exclusive run lock: #{@name}" unless ran_set
end

def stop
return unless running?
Thread.new { @logger.error "killing #{@name} due to stop" } # thread necessary due to trap context
@thread.join(@timeout)
@thread.kill
end

def running?
@thread && @thread.alive?
end

def next_run_at
@last_ran ? (@last_ran + @every) : (Time.now - 0.001)
end

def refresh_last_ran
last_ran_val = @redis.get(run_time_key)
@last_ran = last_ran_val ? Time.at(last_ran_val.to_i) : nil
end

def disabled?
!!@redis.get(disabled_key)
end

private

def ran!(time)
@last_ran = time
@redis.set(run_time_key, @last_ran.to_i)
end

def run_time_key
"zhong:last_ran:#{@name}"
end

def disabled_key
"zhong:disabled:#{@name}"
end

def lock_key
"zhong:lock:#{@name}"
end
end

class Manager
attr_reader :config, :redis

def initialize(config = {})
@jobs = []
@config = {timeout: 0.5, tz: "UTC"}.merge(config)
@logger = @config[:logger] ||= default_logger
@redis = @config[:redis] ||= Redis.new
end

def start
%w(QUIT INT TERM).each do |sig|
Signal.trap(sig) { stop }
end

@logger.info "starting"

loop do
tick

break if @stop
end
end

def stop
Thread.new { @logger.error "stopping" } # thread necessary due to trap context
@stop = true
@jobs.each(&:stop)
Thread.new { @logger.info "stopped" }
end

def add(job)
@jobs << job
end

def tick
now = redis_time

@jobs.each { |job| job.run(now) }

sleep(interval)
end

def interval
1.0 - Time.now.subsec + 0.001
end
require "zhong/version"

def redis_time
s, ms = @redis.time # returns [seconds since epoch, microseconds]
Time.at(s + ms / (10**6))
end
require "zhong/at"
require "zhong/every"

def default_logger
Logger.new(STDOUT).tap do |logger|
logger.formatter = -> (_, datetime, _, msg) { "#{datetime}: #{msg}\n" }
end
end
end
require "zhong/job"
require "zhong/scheduler"

module Zhong
class << self
def included(klass)
klass.send "include", Methods
klass.extend Methods
end

def manager
@manager ||= Manager.new
end

def manager=(manager)
@manager = manager
end
end

module Methods
def configure(&block)
self.manager.configure(&block)
end

# def handler(&block)
# self.manager.handler(&block)
# end

# def error_handler(&block)
# self.manager.error_handler(&block)
# end

def on(event, options={}, &block)
self.manager.on(event, options, &block)
end

def every(period, job, options={}, &block)
self.manager.every(period, job, options, &block)
end

def run
self.manager.run
def schedule(**opts, &block)
@scheduler = Scheduler.new(opts)
@scheduler.instance_eval(&block)
@scheduler.start
end
end

extend Methods
end


r = Redis.new

x = Zhong::Manager.new(redis: r)

j = Zhong::Job.new(manager: x, name: "j1", every: 10) { puts "FUCK THIS SHIT YOLOOOOO" }
j2 = Zhong::Job.new(manager: x, name: "j2", every: 15) { puts "FUCK UuuuuuuuUUUUUU" }
j3 = Zhong::Job.new(manager: x, name: "j3", every: 10) { puts "FUCK THIS SHIT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!" }
j4 = Zhong::Job.new(manager: x, name: "j4", every: 5) { sleep 8; puts "RAN FUCK SHIT" }

x.add(j)
x.add(j2)
x.add(j3)
x.add(j4)
x.start

0 comments on commit abe7f90

Please sign in to comment.