daemonic 0.0.2 → 0.1.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
2
  SHA1:
3
- metadata.gz: c90af55fdad70a6c4a99fc782c9fa34fb7ed6939
4
- data.tar.gz: 08b9b435ce0d8e4c5b87978b66f804a55b388dab
3
+ metadata.gz: b1446f7014c74a118db956ede363c6ba0dc31dc6
4
+ data.tar.gz: e519190127e484ab4ca0722928eaac7feadc9e6f
5
5
  SHA512:
6
- metadata.gz: d6a23fc8d43c33e1346dce80900130ea7c703d80b05e6028cbb8cb7d0c8c44315f3aad50d1a20f17bcc849412b77376db282582e6b9a2fb16c4d38cfdf1fc444
7
- data.tar.gz: e38c14642dc4d6c5b618610b62bfd30e3b315571dff5cf2cbf36ff7e040be1b3735522c85c6e8f841c20ca0986485ddc4a90ebd6a043d69592d93a6654120f74
6
+ metadata.gz: 584c0e7c4fb1cb899743762b553ddcf4bb76169029f41c4db8b89ab456bcb23f18fa8d7688e2d6ea26b45b9ca18de9caf9c14cd1b371365ad4144f7551795ddc
7
+ data.tar.gz: 76cfeaa9cdb7067afd482b96fe3de73a8e72d9baeb5ba5f775878c09c89fb962d466199b69d294b82b5512c54e6fa6fc5f9d50c89bf5e3800c0a086d0779d126
data/.gitignore CHANGED
@@ -15,4 +15,8 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
- log/*.log
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.2
5
+ - rbx-2
6
+ - ruby-head
7
+ - jruby
8
+ cache: bundler
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 TODO: Write your name
1
+ Copyright (c) 2014 TODO: Write your name
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,127 +1,157 @@
1
1
  # Daemonic
2
2
 
3
- Daemonic is a tool that let's you turn your long running processes into daemons.
3
+ Daemonic makes multi-threaded daemons easy. All you need to do is provide the
4
+ code that actually does the thing you need to do and you're done.
4
5
 
5
- At a glance:
6
+ Because Daemonic is designed to perform multi-threaded work, you need to
7
+ organize your code to fit a "provider-consumer" pattern.
6
8
 
7
- * Designed to be external to and independent of your app.
8
- * Possibility to spawn multiple instances of your app.
9
- * Smart restart behavior, comparable to Unicorn.
10
- * Automatically restarts crashed instances of your app.
9
+ Your worker needs to be an object that responds to the `produce` and `consume`
10
+ methods. Behind the scenes, Daemonic creates a thread pool and delegate work to
11
+ each of the threads.
11
12
 
12
- ### What can Daemonic do for me?
13
+ ## Example
13
14
 
14
- Say you have written your own daemon. It processes messages of a queue for
15
- example. You can start it by running `ruby work.rb`. Now the only thing you
16
- need to do is turn it into a proper daemon. This means daemonizing your
17
- process, managing restarts, adding concurrency, managing a pid file, etc.
15
+ Let's build a daemon that downloads and parses RSS feeds parses them. You can
16
+ find this example in the examples directory of the project.
18
17
 
19
- This is a lot of work, quite error prone and frankly, very tedious. That's
20
- where Daemonic comes in. All you need to do, to start your message processor is:
18
+ ``` ruby
19
+ #!/usr/bin/env ruby
20
+
21
+ require "nokogiri"
22
+ require "open-uri"
23
+ require "daemonic"
24
+
25
+ class FeedWorker
26
+
27
+ # some feeds as an example
28
+ URLS = %w(
29
+ https://blog.yourkarma.com/rss
30
+ http://gigaom.com/feed
31
+ ) * 5
32
+
33
+ # The produce method determines what to work on next.
34
+ # Put the work onto the queue that is passed in.
35
+ def produce(queue)
36
+ URLS.each do |url|
37
+ queue << url
38
+ end
39
+ end
40
+
41
+ # The consume method does the actual hard work of downloading the feed and parsing it.
42
+ def consume(message)
43
+ puts "Downloading #{message}"
44
+ items = Nokogiri::XML(open(message)).css("item")
45
+ puts "Processing #{items.size} articles from #{message}"
46
+ end
21
47
 
22
- ```
23
- $ daemonic --command "ruby work.rb" --pid work.pid --workers 5 --daemonize
48
+ end
49
+
50
+ feed_worker = FeedWorker.new
51
+
52
+ Daemonic.run(feed_worker)
24
53
  ```
25
54
 
26
- And voila: you now have 5 instances of your worker, each in it's own process.
27
- This works just like Unicorn, but with any kind of process, not just Rack apps.
28
- You can restart all workers by sending the `USR2` signal, just like in Unicorn
29
- too.
55
+ Make the file executable:
30
56
 
31
57
  ```
32
- $ kill -USR2 `cat work.pid`
58
+ $ chmod u+x rss
33
59
  ```
34
60
 
35
- This will restart each process individually, meaning you never lose capacity.
61
+ Then start the daemon:
62
+
63
+ ```
64
+ $ ./rss start --concurrency 10 --daemonize --pid tmp/worker.pid
65
+ ```
36
66
 
37
- There are some caveats though. For one, daemonic checks if the process is
38
- running, but it is very naive. It doesn't really know if the app is ready, just
39
- if it is running or not.
67
+ And you can stop it:
40
68
 
41
- Secondly, if your app needs a shared resource, spawning multiple workers will
42
- not work. This is the case with web servers that need to bind to a port.
69
+ ```
70
+ $ ./rss stop --pid tmp/worker.pid
71
+ ```
43
72
 
44
- Also, your own worker needs to respond to the `TERM` and `HUP` signals.
45
- Daemonic expects the app to shut down after sending the `TERM` signal. It's up
46
- to you to finish up what you are doing.
73
+ Stopping might take a while, because it gently shuts down all the threads,
74
+ letting them finish their work first.
47
75
 
48
76
  ## Installation
49
77
 
50
- You don't need to add daemonic to your Gemfile. You can simply install it and
51
- use it straight away.
78
+ Add this line to your application's Gemfile:
52
79
 
80
+ ``` ruby
81
+ gem 'daemonic'
53
82
  ```
54
- gem install daemonic
55
- ```
56
-
57
- ## Usage
58
83
 
59
- Daemonic accepts command line options and can also read from a configuration
60
- file. The configuration file contains the same options as you would pass to the
61
- command line. Here is an example:
84
+ And then execute:
62
85
 
63
86
  ```
64
- --command "ruby work.rb"
65
- --workers 5
66
- --pid tmp/worker.pid
67
- --name my-worker
68
- --log log/development.log
69
- --daemonize
87
+ $ bundle
70
88
  ```
71
89
 
72
- Then you can run Daemonic with the `config` option:
90
+ Or install it yourself as:
73
91
 
74
92
  ```
75
- $ daemonic --config my-worker.conf
93
+ $ gem install daemonic
76
94
  ```
77
95
 
78
- There are a bunch more options. See `daemonic --help` for the full list.
96
+ ## Usage
79
97
 
80
- ### Signals
98
+ When you get down to it, you only need to do these things to create a
99
+ multi-threaded daemon:
81
100
 
82
- These are the signals daemonic responds to:
101
+ * Create an executable.
102
+ * Require daemonic.
103
+ * Require your own worker.
104
+ * End the executable with `Daemonic.run(my_worker)`.
83
105
 
84
- * `TERM`/`INT` shuts down all workers and then the master
85
- * `HUP` will be forwarded to each worker and the master will reload the config file
86
- * `USR2` will restart each worker, but not the master
87
- * `TTIN` increase the number of workers by one
88
- * `TTOU` decrease the number of workers by one
106
+ You can get help, by running the script you created:
89
107
 
90
- ### Creating a worker
108
+ ```
109
+ $ ./my_worker
110
+ ```
91
111
 
92
- Here's an example of a basic daemonic worker:
112
+ And get help for each command, by passing `--help` to the command:
93
113
 
94
- ``` ruby
95
- exiting = false
114
+ ```
115
+ $ ./my_worker start --help
116
+ ```
96
117
 
97
- trap("TERM") { exiting = true }
98
- trap("INT") { exiting = true }
99
- trap("HUP") {
100
- # reload settings
101
- }
118
+ ## How does it work?
102
119
 
103
- puts "Booting worker number #{ENV["DAEMON_WORKER_NUMBER"]}"
120
+ When starting a
121
+ [SizedQueue](http://ruby-doc.org/stdlib-2.0.0/libdoc/thread/rdoc/SizedQueue.html
122
+ ) will be created. A bunch of threads (specified by the `--concurrency` option)
123
+ will be spawned to listen to that queue. Each message they pick up will be sent
124
+ to the worker object you provided to be consumed.
104
125
 
105
- SomePollingService.poll do
106
- # do your work here
107
- break if exiting
108
- end
126
+ A separate thread will be started that calls your `produce` method continuously.
127
+ Because the SizedQueue has a limit, it will block when the queue is full, until
128
+ some of the consumers are done.
109
129
 
110
- puts "Shutting down worker number #{ENV["DAEMON_WORKER_NUMBER"]}"
111
- ```
130
+ This approach works great for queueing systems. The produce method finds the
131
+ next item, the consume method does the actual work.
132
+
133
+ ## Gotchas
134
+
135
+ Because Daemonic is multi-threaded, your code needs to be thread-safe.
112
136
 
113
- Most importantly:
137
+ Also, MRI has a Global Interpreter Lock (GIL). This means that under MRI you
138
+ cannot do proper multithreading. This might be a problem for you if most of
139
+ the work you are trying to do is CPU bound. If most work is IO bound (like
140
+ downloading stuff from the internet), this shouldn't be a problem. When one
141
+ consumer is busy doing IO, the other consumers can run. Therefore Daemonic works
142
+ great when your daemon is doing mostly IO.
114
143
 
115
- * Trap INT and TERM signals to cleanly stop your process
144
+ Daemonic ignores all errors. This means that Daemonic will keep on running, but
145
+ you need to make sure you still get notified of those errors.
146
+
147
+ When using the restart command, you need to provide all the options you provide
148
+ as if starting the application. Restarting only makes sense for daemonized
149
+ processes.
116
150
 
117
151
  ## Contributing
118
152
 
119
- 1. Fork it
153
+ 1. Fork it ( https://github.com/yourkarma/daemonic/fork )
120
154
  2. Create your feature branch (`git checkout -b my-new-feature`)
121
155
  3. Commit your changes (`git commit -am 'Add some feature'`)
122
156
  4. Push to the branch (`git push origin my-new-feature`)
123
- 5. Create new Pull Request
124
-
125
- ## TODO
126
-
127
- * Ability to create init script like behavior.
157
+ 5. Create a new Pull Request
data/Rakefile CHANGED
@@ -1,9 +1,16 @@
1
1
  require "bundler/gem_tasks"
2
2
 
3
- require 'rake/testtask'
3
+ begin
4
+ require 'cucumber'
5
+ require 'cucumber/rake/task'
4
6
 
5
- Rake::TestTask.new do |t|
6
- t.pattern = "test/*_test.rb"
7
+ Cucumber::Rake::Task.new(:features)
8
+
9
+ rescue LoadError
10
+ task :features do
11
+ puts "Cucumber not installed."
12
+ exit 1
13
+ end
7
14
  end
8
15
 
9
- task :default => :test
16
+ task :default => :features
data/daemonic.gemspec CHANGED
@@ -8,16 +8,19 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Daemonic::VERSION
9
9
  spec.authors = ["iain"]
10
10
  spec.email = ["iain@iain.nl"]
11
- spec.description = %q{Manages daemonizing your workers with basic monitoring and restart behavior.}
12
- spec.summary = %q{Manages daemonizing your workers with basic monitoring and restart behavior.}
11
+ spec.summary = %q{Daemonic makes multi-threaded daemons easy.}
12
+ spec.description = %q{Daemonic makes multi-threaded daemons easy.}
13
13
  spec.homepage = "https://github.com/yourkarma/daemonic"
14
14
  spec.license = "MIT"
15
15
 
16
- spec.files = `git ls-files`.split($/)
16
+ spec.files = `git ls-files -z`.split("\x0")
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.3"
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rspec", ">= 3.0.0"
22
23
  spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "cucumber"
25
+ spec.add_development_dependency "aruba"
23
26
  end
@@ -0,0 +1,37 @@
1
+ #!/bin/sh
2
+
3
+ # This is an example of an init.d script to wrap around your worker.
4
+
5
+ set -e # exit the script if any statement returns a non-true return value
6
+ set -u # check whether all variables are initialised
7
+
8
+ # The variables for this script
9
+ PATH=$PATH:/usr/local/bin
10
+ TIMEOUT=15
11
+ APP_ROOT=/some/path
12
+ SCRIPT=./bin/my_worker
13
+ PID=worker.pid
14
+ USER=worker
15
+ LOG=worker.log
16
+
17
+ # make sure the path exists
18
+ cd $APP_ROOT || exit 1
19
+
20
+ case $1 in
21
+ start)
22
+ su --login $USER --shell /bin/sh --command "cd $APP_ROOT; $SCRIPT start --pid $PID --log $LOG --daemonize"
23
+ ;;
24
+ restart)
25
+ su --login $USER --shell /bin/sh --command "cd $APP_ROOT; $SCRIPT restart --pid $PID --log $LOG --stop-timeout $TIMEOUT"
26
+ ;;
27
+ stop)
28
+ su --login $USER --shell /bin/sh --command "cd $APP_ROOT; $SCRIPT stop --pid $PID --stop-timeout $TIMEOUT"
29
+ ;;
30
+ status)
31
+ su --login $USER --shell /bin/sh --command "cd $APP_ROOT; $SCRIPT status --pid $PID"
32
+ ;;
33
+ *)
34
+ echo >&2 "Usage: $0 <start|stop|restart|status>"
35
+ exit 1
36
+ ;;
37
+ esac
data/examples/rss ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # To run this file, clone the repo, go to the examples directory and run:
4
+ #
5
+ # ./rss start -c 10
6
+
7
+ # This is only for the example to load the gem file inside the project
8
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
9
+
10
+ require "nokogiri"
11
+ require "open-uri"
12
+ require "daemonic"
13
+
14
+ class FeedWorker
15
+
16
+ # some feeds as an example
17
+ URLS = %w(
18
+ https://blog.yourkarma.com/rss
19
+ http://gigaom.com/feed
20
+ ) * 5
21
+
22
+ # The produce method determines what to work on next.
23
+ # Put the work onto the queue that is passed in.
24
+ def produce(queue)
25
+ URLS.each do |url|
26
+ queue << url
27
+ end
28
+ end
29
+
30
+ # The consume method does the actual hard work of downloading the feed and parsing it.
31
+ def consume(message)
32
+ puts "Downloading #{message}"
33
+ items = Nokogiri::XML(open(message)).css("item")
34
+ puts "Processing #{items.size} articles from #{message}"
35
+ end
36
+
37
+ end
38
+
39
+ feed_worker = FeedWorker.new
40
+
41
+ Daemonic.run(feed_worker)
@@ -0,0 +1 @@
1
+ require "aruba/cucumber"
@@ -0,0 +1,43 @@
1
+ Feature: Worker
2
+
3
+ Scenario: Starting and stopping
4
+
5
+ Given a file named "worker" with mode "744" and with:
6
+ """
7
+ #!/usr/bin/env ruby
8
+ $LOAD_PATH << File.expand_path("../../../lib", __FILE__)
9
+ require "daemonic"
10
+
11
+ class MyWorker
12
+
13
+ def produce(queue)
14
+ sleep 0.1
15
+ queue << "tick"
16
+ end
17
+
18
+ def consume(message)
19
+ puts message
20
+ sleep 0.1
21
+ end
22
+
23
+ end
24
+
25
+ worker = MyWorker.new
26
+
27
+ Daemonic.run(worker)
28
+ """
29
+
30
+ When I run `./worker start --daemonize --pid tmp/worker.pid`
31
+ Then the exit status should be 0
32
+
33
+ When I run `./worker status --pid tmp/worker.pid`
34
+ Then the exit status should be 0
35
+
36
+ When I run `./worker restart --pid tmp/worker.pid`
37
+ Then the exit status should be 0
38
+
39
+ When I run `./worker stop --pid tmp/worker.pid`
40
+ Then the exit status should be 0
41
+
42
+ When I run `./worker status --pid tmp/worker.pid`
43
+ Then the exit status should be 2