pastry 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README.rdoc +29 -0
  2. data/bin/pastry +28 -0
  3. data/lib/pastry.rb +111 -0
  4. metadata +78 -0
@@ -0,0 +1,29 @@
1
+ = Thin Pastry flakes
2
+
3
+ This gem is just scratching an itch, that I could not run a master and fork several thin workers
4
+ listening on the same parent socket.
5
+
6
+ Why fork thin ? So I can make use of the multiple cores more effectively.
7
+
8
+ == Warning
9
+
10
+ It's fairly pre-alpha, so use at your own peril
11
+
12
+ == Usage
13
+
14
+ pastry -R myapp.ru -n 2 -P /tmp/myapp.pid -l /tmp/myapp.log -a 127.0.0.1 -p 3000 -d
15
+
16
+ kill -TERM `cat /tmp/myapp.pid`
17
+
18
+ == Dependencies
19
+
20
+ * requires patched eventmachine in http://github.com/deepfryed/eventmachine
21
+
22
+ == TODO
23
+
24
+ * graceful stop / restart
25
+ * increase or decrease worker count
26
+
27
+ == License
28
+
29
+ {Creative Commons Attribution - CC BY}[http://creativecommons.org/licenses/by/3.0]
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pastry'
4
+ require 'optparse'
5
+
6
+ options = {}
7
+ parser = OptionParser.new do |opts|
8
+ opts.banner = "pastry [options]"
9
+
10
+ opts.on('-n', '--servers number', 'worker count') {|value| options[:workers] = value }
11
+ opts.on('-E', '--environment name', 'rack environment') {|value| options[:env] = value }
12
+ opts.on('-R', '--rackup file', 'rackup file') {|value| options[:rackup] = value }
13
+ opts.on('-a', '--address name', 'bind ip/host') {|value| options[:host] = value }
14
+ opts.on('-p', '--port name', 'bind port') {|value| options[:port] = value }
15
+ opts.on('-s', '--socket file', 'unix socket') {|value| options[:unix] = value }
16
+ opts.on('-d', '--[no-]daemon', 'daemonize') {|value| options[:daemonize] = value }
17
+ opts.on('-l', '--logfile file', 'logfile') {|value| options[:logfile] = value }
18
+ opts.on('-P', '--pidfile file', 'pidfile') {|value| options[:pidfile] = value }
19
+ end
20
+
21
+ parser.parse!
22
+
23
+ app = Rack::Builder.parse_file(options.delete(:rackup) || 'config.ru').first
24
+ size = options.delete(:workers) || 2
25
+ env = options.delete(:env) || 'development'
26
+
27
+ ENV['RACK_ENV'] = env
28
+ Pastry.new(size, app, options).start
@@ -0,0 +1,111 @@
1
+ require 'fileutils'
2
+ require 'logger'
3
+ require 'socket'
4
+ require 'thin'
5
+
6
+ class Pastry
7
+ attr_reader :pool, :unix, :host, :port, :pidfile, :logfile, :daemon
8
+
9
+ def initialize pool, app, options = {}
10
+ @pool = pool
11
+ @app = app
12
+ @host = options.fetch :host, '127.0.0.1'
13
+ @port = options.fetch :port, 3000
14
+ @unix = options.fetch :socket, nil
15
+ @queue = options.fetch :queue, 1024
16
+ @logfile = options.fetch :logfile, nil
17
+ @daemon = options.fetch :daemonize, false
18
+ @pidfile = options.fetch :pidfile, '/tmp/pastry.pid'
19
+ # TODO: validation
20
+ end
21
+
22
+ def start
23
+ ensure_not_running!
24
+ Process.daemon if daemon
25
+ start!
26
+ end
27
+
28
+ def ensure_not_running!
29
+ if File.exists?(pidfile) && pid = File.read(pidfile).to_i
30
+ running = Process.kill(0, pid) rescue nil
31
+ raise "already running with pid #{pid}" if running
32
+ FileUtils.rm_f(pidfile)
33
+ end
34
+ end
35
+
36
+ def create_pidfile
37
+ File.open(pidfile, 'w') {|fh| fh.write(Process.pid)}
38
+ end
39
+
40
+ def motd
41
+ "starting #{@app} pastry with #{pool} flakes listening on #{unix ? 'socket %s ' % unix : 'port %d' % port}"
42
+ end
43
+
44
+ def start!
45
+ create_pidfile
46
+ server = unix ? UnixServer.new(unix) : TCPServer.new(host, port)
47
+
48
+ server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) unless unix
49
+ server.fcntl(Fcntl::F_SETFL, server.fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
50
+
51
+ server.listen(@queue)
52
+ server.extend(PastryServer)
53
+
54
+ @running = true
55
+ server.app = @app
56
+ logger = Logger.new(logfile || daemon ? '/tmp/pastry.log' : $stdout, 0)
57
+ pids = pool.times.map { run(server) }
58
+
59
+ logger.info motd
60
+ Signal.trap('CHLD') do
61
+ unless @running
62
+ died = pids.reject {|pid| Process.kill(0, pid) rescue nil}
63
+ pids -= died
64
+ died.each do |pid|
65
+ logger.info "process #{pid} died, starting a new one"
66
+ pids << run(server)
67
+ end
68
+ end
69
+ end
70
+
71
+ at_exit { FileUtils.rm_f(pidfile) }
72
+
73
+ %w(INT TERM HUP).each do |signal|
74
+ Signal.trap(signal) do
75
+ @running = false
76
+ logger.info "caught #{signal}, closing time for the bakery -- no more pastries!"
77
+ pids.each {|pid| Process.kill(signal, pid) }
78
+ exit
79
+ end
80
+ end
81
+
82
+ Process.waitall rescue nil
83
+ end
84
+
85
+ def run server
86
+ fork { EM.run { Backend.new.start(server) } }
87
+ end
88
+
89
+ module PastryServer
90
+ attr_accessor :app
91
+ end
92
+
93
+ class Backend < Thin::Backends::Base
94
+ def start server
95
+ @stopping = false
96
+ @running = true
97
+ @server = server
98
+
99
+ config
100
+ trap_signals!
101
+
102
+ EM.attach_server_socket(server, Thin::Connection, &method(:initialize_connection))
103
+ end
104
+
105
+ def trap_signals!
106
+ %w(INT TERM HUP CHLD).each do |signal|
107
+ Signal.trap(signal) { exit }
108
+ end
109
+ end
110
+ end # Backend
111
+ end # Pastry
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pastry
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Bharanee Rathna
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-11-26 00:00:00 +11:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: thin
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ description: thin runner that forks and supports binding to single socket
34
+ email: deepfryed@gmail.com
35
+ executables:
36
+ - pastry
37
+ extensions: []
38
+
39
+ extra_rdoc_files:
40
+ - README.rdoc
41
+ files:
42
+ - lib/pastry.rb
43
+ - README.rdoc
44
+ - bin/pastry
45
+ has_rdoc: true
46
+ homepage: http://github.com/deepfryed/pastry
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.3.7
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: thin runner that supports forking
77
+ test_files: []
78
+