thin 1.2.3-x86-mswin32

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of thin might be problematic. Click here for more details.

Files changed (137) hide show
  1. data/CHANGELOG +263 -0
  2. data/COPYING +18 -0
  3. data/README +69 -0
  4. data/Rakefile +36 -0
  5. data/benchmark/abc +51 -0
  6. data/benchmark/benchmarker.rb +80 -0
  7. data/benchmark/runner +82 -0
  8. data/bin/thin +6 -0
  9. data/example/adapter.rb +32 -0
  10. data/example/async_app.ru +126 -0
  11. data/example/async_chat.ru +247 -0
  12. data/example/async_tailer.ru +100 -0
  13. data/example/config.ru +22 -0
  14. data/example/monit_sockets +20 -0
  15. data/example/monit_unixsock +20 -0
  16. data/example/myapp.rb +1 -0
  17. data/example/ramaze.ru +12 -0
  18. data/example/thin.god +80 -0
  19. data/example/thin_solaris_smf.erb +36 -0
  20. data/example/thin_solaris_smf.readme.txt +150 -0
  21. data/example/vlad.rake +64 -0
  22. data/ext/thin_parser/common.rl +55 -0
  23. data/ext/thin_parser/ext_help.h +14 -0
  24. data/ext/thin_parser/extconf.rb +6 -0
  25. data/ext/thin_parser/parser.c +452 -0
  26. data/ext/thin_parser/parser.h +49 -0
  27. data/ext/thin_parser/parser.rl +157 -0
  28. data/ext/thin_parser/thin.c +433 -0
  29. data/lib/rack/adapter/loader.rb +79 -0
  30. data/lib/rack/adapter/rails.rb +181 -0
  31. data/lib/thin.rb +46 -0
  32. data/lib/thin/backends/base.rb +141 -0
  33. data/lib/thin/backends/swiftiply_client.rb +56 -0
  34. data/lib/thin/backends/tcp_server.rb +29 -0
  35. data/lib/thin/backends/unix_server.rb +51 -0
  36. data/lib/thin/command.rb +53 -0
  37. data/lib/thin/connection.rb +222 -0
  38. data/lib/thin/controllers/cluster.rb +127 -0
  39. data/lib/thin/controllers/controller.rb +183 -0
  40. data/lib/thin/controllers/service.rb +75 -0
  41. data/lib/thin/controllers/service.sh.erb +39 -0
  42. data/lib/thin/daemonizing.rb +174 -0
  43. data/lib/thin/headers.rb +39 -0
  44. data/lib/thin/logging.rb +54 -0
  45. data/lib/thin/request.rb +153 -0
  46. data/lib/thin/response.rb +101 -0
  47. data/lib/thin/runner.rb +209 -0
  48. data/lib/thin/server.rb +247 -0
  49. data/lib/thin/stats.html.erb +216 -0
  50. data/lib/thin/stats.rb +52 -0
  51. data/lib/thin/statuses.rb +43 -0
  52. data/lib/thin/version.rb +32 -0
  53. data/lib/thin_parser.so +0 -0
  54. data/spec/backends/swiftiply_client_spec.rb +66 -0
  55. data/spec/backends/tcp_server_spec.rb +33 -0
  56. data/spec/backends/unix_server_spec.rb +37 -0
  57. data/spec/command_spec.rb +25 -0
  58. data/spec/configs/cluster.yml +9 -0
  59. data/spec/configs/single.yml +9 -0
  60. data/spec/connection_spec.rb +106 -0
  61. data/spec/controllers/cluster_spec.rb +235 -0
  62. data/spec/controllers/controller_spec.rb +129 -0
  63. data/spec/controllers/service_spec.rb +50 -0
  64. data/spec/daemonizing_spec.rb +192 -0
  65. data/spec/headers_spec.rb +40 -0
  66. data/spec/logging_spec.rb +46 -0
  67. data/spec/perf/request_perf_spec.rb +50 -0
  68. data/spec/perf/response_perf_spec.rb +19 -0
  69. data/spec/perf/server_perf_spec.rb +39 -0
  70. data/spec/rack/loader_spec.rb +29 -0
  71. data/spec/rack/rails_adapter_spec.rb +106 -0
  72. data/spec/rails_app/app/controllers/application.rb +10 -0
  73. data/spec/rails_app/app/controllers/simple_controller.rb +19 -0
  74. data/spec/rails_app/app/helpers/application_helper.rb +3 -0
  75. data/spec/rails_app/app/views/simple/index.html.erb +15 -0
  76. data/spec/rails_app/config/boot.rb +109 -0
  77. data/spec/rails_app/config/environment.rb +64 -0
  78. data/spec/rails_app/config/environments/development.rb +18 -0
  79. data/spec/rails_app/config/environments/production.rb +19 -0
  80. data/spec/rails_app/config/environments/test.rb +22 -0
  81. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  82. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  83. data/spec/rails_app/config/routes.rb +35 -0
  84. data/spec/rails_app/public/404.html +30 -0
  85. data/spec/rails_app/public/422.html +30 -0
  86. data/spec/rails_app/public/500.html +30 -0
  87. data/spec/rails_app/public/dispatch.cgi +10 -0
  88. data/spec/rails_app/public/dispatch.fcgi +24 -0
  89. data/spec/rails_app/public/dispatch.rb +10 -0
  90. data/spec/rails_app/public/favicon.ico +0 -0
  91. data/spec/rails_app/public/images/rails.png +0 -0
  92. data/spec/rails_app/public/index.html +277 -0
  93. data/spec/rails_app/public/javascripts/application.js +2 -0
  94. data/spec/rails_app/public/javascripts/controls.js +963 -0
  95. data/spec/rails_app/public/javascripts/dragdrop.js +972 -0
  96. data/spec/rails_app/public/javascripts/effects.js +1120 -0
  97. data/spec/rails_app/public/javascripts/prototype.js +4225 -0
  98. data/spec/rails_app/public/robots.txt +5 -0
  99. data/spec/rails_app/script/about +3 -0
  100. data/spec/rails_app/script/console +3 -0
  101. data/spec/rails_app/script/destroy +3 -0
  102. data/spec/rails_app/script/generate +3 -0
  103. data/spec/rails_app/script/performance/benchmarker +3 -0
  104. data/spec/rails_app/script/performance/profiler +3 -0
  105. data/spec/rails_app/script/performance/request +3 -0
  106. data/spec/rails_app/script/plugin +3 -0
  107. data/spec/rails_app/script/process/inspector +3 -0
  108. data/spec/rails_app/script/process/reaper +3 -0
  109. data/spec/rails_app/script/process/spawner +3 -0
  110. data/spec/rails_app/script/runner +3 -0
  111. data/spec/rails_app/script/server +3 -0
  112. data/spec/request/mongrel_spec.rb +39 -0
  113. data/spec/request/parser_spec.rb +215 -0
  114. data/spec/request/persistent_spec.rb +35 -0
  115. data/spec/request/processing_spec.rb +45 -0
  116. data/spec/response_spec.rb +91 -0
  117. data/spec/runner_spec.rb +168 -0
  118. data/spec/server/builder_spec.rb +44 -0
  119. data/spec/server/pipelining_spec.rb +110 -0
  120. data/spec/server/robustness_spec.rb +34 -0
  121. data/spec/server/stopping_spec.rb +55 -0
  122. data/spec/server/swiftiply.yml +6 -0
  123. data/spec/server/swiftiply_spec.rb +32 -0
  124. data/spec/server/tcp_spec.rb +57 -0
  125. data/spec/server/threaded_spec.rb +27 -0
  126. data/spec/server/unix_socket_spec.rb +26 -0
  127. data/spec/server_spec.rb +96 -0
  128. data/spec/spec_helper.rb +219 -0
  129. data/tasks/announce.rake +22 -0
  130. data/tasks/deploy.rake +13 -0
  131. data/tasks/email.erb +30 -0
  132. data/tasks/gem.rake +74 -0
  133. data/tasks/rdoc.rake +25 -0
  134. data/tasks/site.rake +15 -0
  135. data/tasks/spec.rake +49 -0
  136. data/tasks/stats.rake +28 -0
  137. metadata +246 -0
@@ -0,0 +1,56 @@
1
+ module Thin
2
+ module Backends
3
+ # Backend to act as a Swiftiply client (http://swiftiply.swiftcore.org).
4
+ class SwiftiplyClient < Base
5
+ attr_accessor :key
6
+
7
+ attr_accessor :host, :port
8
+
9
+ def initialize(host, port, options={})
10
+ @host = host
11
+ @port = port.to_i
12
+ @key = options[:swiftiply].to_s
13
+ super()
14
+ end
15
+
16
+ # Connect the server
17
+ def connect
18
+ EventMachine.connect(@host, @port, SwiftiplyConnection, &method(:initialize_connection))
19
+ end
20
+
21
+ # Stops the server
22
+ def disconnect
23
+ EventMachine.stop
24
+ end
25
+
26
+ def to_s
27
+ "#{@host}:#{@port} swiftiply"
28
+ end
29
+ end
30
+ end
31
+
32
+ class SwiftiplyConnection < Connection
33
+ def connection_completed
34
+ send_data swiftiply_handshake(@backend.key)
35
+ end
36
+
37
+ def persistent?
38
+ true
39
+ end
40
+
41
+ def unbind
42
+ super
43
+ EventMachine.add_timer(rand(2)) { reconnect(@backend.host, @backend.port) } if @backend.running?
44
+ end
45
+
46
+ protected
47
+ def swiftiply_handshake(key)
48
+ 'swiftclient' << host_ip.collect { |x| sprintf('%02x', x.to_i)}.join << sprintf('%04x', @backend.port) << sprintf('%02x', key.length) << key
49
+ end
50
+
51
+ # For some reason Swiftiply request the current host
52
+ def host_ip
53
+ Socket.gethostbyname(@backend.host)[3].unpack('CCCC') rescue [0,0,0,0]
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,29 @@
1
+ module Thin
2
+ module Backends
3
+ # Backend to act as a TCP socket server.
4
+ class TcpServer < Base
5
+ # Address and port on which the server is listening for connections.
6
+ attr_accessor :host, :port
7
+
8
+ def initialize(host, port)
9
+ @host = host
10
+ @port = port
11
+ super()
12
+ end
13
+
14
+ # Connect the server
15
+ def connect
16
+ @signature = EventMachine.start_server(@host, @port, Connection, &method(:initialize_connection))
17
+ end
18
+
19
+ # Stops the server
20
+ def disconnect
21
+ EventMachine.stop_server(@signature)
22
+ end
23
+
24
+ def to_s
25
+ "#{@host}:#{@port}"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,51 @@
1
+ module Thin
2
+ module Backends
3
+ # Backend to act as a UNIX domain socket server.
4
+ class UnixServer < Base
5
+ # UNIX domain socket on which the server is listening for connections.
6
+ attr_accessor :socket
7
+
8
+ def initialize(socket)
9
+ raise PlatformNotSupported, 'UNIX domain sockets not available on Windows' if Thin.win?
10
+ @socket = socket
11
+ super()
12
+ end
13
+
14
+ # Connect the server
15
+ def connect
16
+ at_exit { remove_socket_file } # In case it crashes
17
+ EventMachine.start_unix_domain_server(@socket, UnixConnection, &method(:initialize_connection))
18
+ # HACK EventMachine.start_unix_domain_server doesn't return the connection signature
19
+ # so we have to go in the internal stuff to find it.
20
+ @signature = EventMachine.instance_eval{@acceptors.keys.first}
21
+ end
22
+
23
+ # Stops the server
24
+ def disconnect
25
+ EventMachine.stop_server(@signature)
26
+ end
27
+
28
+ # Free up resources used by the backend.
29
+ def close
30
+ remove_socket_file
31
+ end
32
+
33
+ def to_s
34
+ @socket
35
+ end
36
+
37
+ protected
38
+ def remove_socket_file
39
+ File.delete(@socket) if @socket && File.exist?(@socket)
40
+ end
41
+ end
42
+ end
43
+
44
+ # Connection through a UNIX domain socket.
45
+ class UnixConnection < Connection
46
+ protected
47
+ def socket_address
48
+ '127.0.0.1' # Unix domain sockets can only be local
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,53 @@
1
+ require 'open3'
2
+
3
+ module Thin
4
+ # Run a command through the +thin+ command-line script.
5
+ class Command
6
+ include Logging
7
+
8
+ class << self
9
+ # Path to the +thin+ script used to control the servers.
10
+ # Leave this to default to use the one in the path.
11
+ attr_accessor :script
12
+ end
13
+
14
+ def initialize(name, options={})
15
+ @name = name
16
+ @options = options
17
+ end
18
+
19
+ def self.run(*args)
20
+ new(*args).run
21
+ end
22
+
23
+ # Send the command to the +thin+ script
24
+ def run
25
+ shell_cmd = shellify
26
+ trace shell_cmd
27
+ trap('INT') {} # Ignore INT signal to pass CTRL+C to subprocess
28
+ Open3.popen3(shell_cmd) do |stdin, stdout, stderr|
29
+ log stdout.gets until stdout.eof?
30
+ log stderr.gets until stderr.eof?
31
+ end
32
+ end
33
+
34
+ # Turn into a runnable shell command
35
+ def shellify
36
+ shellified_options = @options.inject([]) do |args, (name, value)|
37
+ option_name = name.to_s.tr("_", "-")
38
+ case value
39
+ when NilClass,
40
+ TrueClass then args << "--#{option_name}"
41
+ when FalseClass
42
+ when Array then value.each { |v| args << "--#{option_name}=#{v.inspect}" }
43
+ else args << "--#{option_name}=#{value.inspect}"
44
+ end
45
+ args
46
+ end
47
+
48
+ raise ArgumentError, "Path to thin script can't be found, set Command.script" unless self.class.script
49
+
50
+ "#{self.class.script} #{@name} #{shellified_options.compact.join(' ')}"
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,222 @@
1
+ require 'socket'
2
+
3
+ module Thin
4
+ # Connection between the server and client.
5
+ # This class is instanciated by EventMachine on each new connection
6
+ # that is opened.
7
+ class Connection < EventMachine::Connection
8
+ CONTENT_LENGTH = 'Content-Length'.freeze
9
+ TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
10
+ CHUNKED_REGEXP = /\bchunked\b/i.freeze
11
+
12
+ include Logging
13
+
14
+ # This is a template async response. N.B. Can't use string for body on 1.9
15
+ AsyncResponse = [-1, {}, []].freeze
16
+
17
+ # Rack application (adapter) served by this connection.
18
+ attr_accessor :app
19
+
20
+ # Backend to the server
21
+ attr_accessor :backend
22
+
23
+ # Current request served by the connection
24
+ attr_accessor :request
25
+
26
+ # Next response sent through the connection
27
+ attr_accessor :response
28
+
29
+ # Calling the application in a threaded allowing
30
+ # concurrent processing of requests.
31
+ attr_writer :threaded
32
+
33
+ # Get the connection ready to process a request.
34
+ def post_init
35
+ @request = Request.new
36
+ @response = Response.new
37
+ end
38
+
39
+ # Called when data is received from the client.
40
+ def receive_data(data)
41
+ trace { data }
42
+ process if @request.parse(data)
43
+ rescue InvalidRequest => e
44
+ log "!! Invalid request"
45
+ log_error e
46
+ close_connection
47
+ end
48
+
49
+ # Called when all data was received and the request
50
+ # is ready to be processed.
51
+ def process
52
+ if threaded?
53
+ @request.threaded = true
54
+ EventMachine.defer(method(:pre_process), method(:post_process))
55
+ else
56
+ @request.threaded = false
57
+ post_process(pre_process)
58
+ end
59
+ end
60
+
61
+ def pre_process
62
+ # Add client info to the request env
63
+ @request.remote_address = remote_address
64
+
65
+ # Connection may be closed unless the App#call response was a [-1, ...]
66
+ # It should be noted that connection objects will linger until this
67
+ # callback is no longer referenced, so be tidy!
68
+ @request.async_callback = method(:post_process)
69
+
70
+ # When we're under a non-async framework like rails, we can still spawn
71
+ # off async responses using the callback info, so there's little point
72
+ # in removing this.
73
+ response = AsyncResponse
74
+ catch(:async) do
75
+ # Process the request calling the Rack adapter
76
+ response = @app.call(@request.env)
77
+ end
78
+ response
79
+ rescue Exception
80
+ handle_error
81
+ terminate_request
82
+ nil # Signal to post_process that the request could not be processed
83
+ end
84
+
85
+ def post_process(result)
86
+ return unless result
87
+ result = result.to_a
88
+
89
+ # Status code -1 indicates that we're going to respond later (async).
90
+ return if result.first == AsyncResponse.first
91
+
92
+ # Set the Content-Length header if possible
93
+ set_content_length(result) if need_content_length?(result)
94
+
95
+ @response.status, @response.headers, @response.body = *result
96
+
97
+ log "!! Rack application returned nil body. Probably you wanted it to be an empty string?" if @response.body.nil?
98
+
99
+ # Make the response persistent if requested by the client
100
+ @response.persistent! if @request.persistent?
101
+
102
+ # Send the response
103
+ @response.each do |chunk|
104
+ trace { chunk }
105
+ send_data chunk
106
+ end
107
+
108
+ rescue Exception
109
+ handle_error
110
+ ensure
111
+ # If the body is being deferred, then terminate afterward.
112
+ if @response.body.respond_to?(:callback) && @response.body.respond_to?(:errback)
113
+ @response.body.callback { terminate_request }
114
+ @response.body.errback { terminate_request }
115
+ else
116
+ # Don't terminate the response if we're going async.
117
+ terminate_request unless result && result.first == AsyncResponse.first
118
+ end
119
+ end
120
+
121
+ # Logs catched exception and closes the connection.
122
+ def handle_error
123
+ log "!! Unexpected error while processing request: #{$!.message}"
124
+ log_error
125
+ close_connection rescue nil
126
+ end
127
+
128
+ def close_request_response
129
+ @request.async_close.succeed if @request.async_close
130
+ @request.close rescue nil
131
+ @response.close rescue nil
132
+ end
133
+
134
+ # Does request and response cleanup (closes open IO streams and
135
+ # deletes created temporary files).
136
+ # Re-initializes response and request if client supports persistent
137
+ # connection.
138
+ def terminate_request
139
+ unless persistent?
140
+ close_connection_after_writing rescue nil
141
+ close_request_response
142
+ else
143
+ close_request_response
144
+ # Prepare the connection for another request if the client
145
+ # supports HTTP pipelining (persistent connection).
146
+ post_init
147
+ end
148
+ end
149
+
150
+ # Called when the connection is unbinded from the socket
151
+ # and can no longer be used to process requests.
152
+ def unbind
153
+ @request.async_close.succeed if @request.async_close
154
+ @response.body.fail if @response.body.respond_to?(:fail)
155
+ @backend.connection_finished(self)
156
+ end
157
+
158
+ # Allows this connection to be persistent.
159
+ def can_persist!
160
+ @can_persist = true
161
+ end
162
+
163
+ # Return +true+ if this connection is allowed to stay open and be persistent.
164
+ def can_persist?
165
+ @can_persist
166
+ end
167
+
168
+ # Return +true+ if the connection must be left open
169
+ # and ready to be reused for another request.
170
+ def persistent?
171
+ @can_persist && @response.persistent?
172
+ end
173
+
174
+ # +true+ if <tt>app.call</tt> will be called inside a thread.
175
+ # You can set all requests as threaded setting <tt>Connection#threaded=true</tt>
176
+ # or on a per-request case returning +true+ in <tt>app.deferred?</tt>.
177
+ def threaded?
178
+ @threaded || (@app.respond_to?(:deferred?) && @app.deferred?(@request.env))
179
+ end
180
+
181
+ # IP Address of the remote client.
182
+ def remote_address
183
+ socket_address
184
+ rescue Exception
185
+ log_error
186
+ nil
187
+ end
188
+
189
+ protected
190
+
191
+ # Returns IP address of peer as a string.
192
+ def socket_address
193
+ Socket.unpack_sockaddr_in(get_peername)[1]
194
+ end
195
+
196
+ private
197
+ def need_content_length?(result)
198
+ status, headers, body = result
199
+ return false if status == -1
200
+ return false if headers.has_key?(CONTENT_LENGTH)
201
+ return false if (100..199).include?(status) || status == 204 || status == 304
202
+ return false if headers.has_key?(TRANSFER_ENCODING) && headers[TRANSFER_ENCODING] =~ CHUNKED_REGEXP
203
+ return false unless body.kind_of?(String) || body.kind_of?(Array)
204
+ true
205
+ end
206
+
207
+ def set_content_length(result)
208
+ headers, body = result[1..2]
209
+ case body
210
+ when String
211
+ # See http://redmine.ruby-lang.org/issues/show/203
212
+ headers[CONTENT_LENGTH] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
213
+ when Array
214
+ bytes = 0
215
+ body.each do |p|
216
+ bytes += p.respond_to?(:bytesize) ? p.bytesize : p.size
217
+ end
218
+ headers[CONTENT_LENGTH] = bytes.to_s
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,127 @@
1
+ module Thin
2
+ module Controllers
3
+ # Control a set of servers.
4
+ # * Generate start and stop commands and run them.
5
+ # * Inject the port or socket number in the pid and log filenames.
6
+ # Servers are started throught the +thin+ command-line script.
7
+ class Cluster < Controller
8
+ # Cluster only options that should not be passed in the command sent
9
+ # to the indiviual servers.
10
+ CLUSTER_OPTIONS = [:servers, :only]
11
+
12
+ # Create a new cluster of servers launched using +options+.
13
+ def initialize(options)
14
+ super
15
+ # Cluster can only contain daemonized servers
16
+ @options.merge!(:daemonize => true)
17
+ end
18
+
19
+ def first_port; @options[:port] end
20
+ def address; @options[:address] end
21
+ def socket; @options[:socket] end
22
+ def pid_file; @options[:pid] end
23
+ def log_file; @options[:log] end
24
+ def size; @options[:servers] end
25
+ def only; @options[:only] end
26
+
27
+ def swiftiply?
28
+ @options.has_key?(:swiftiply)
29
+ end
30
+
31
+ # Start the servers
32
+ def start
33
+ with_each_server { |n| start_server n }
34
+ end
35
+
36
+ # Start a single server
37
+ def start_server(number)
38
+ log "Starting server on #{server_id(number)} ... "
39
+
40
+ run :start, number
41
+ end
42
+
43
+ # Stop the servers
44
+ def stop
45
+ with_each_server { |n| stop_server n }
46
+ end
47
+
48
+ # Stop a single server
49
+ def stop_server(number)
50
+ log "Stopping server on #{server_id(number)} ... "
51
+
52
+ run :stop, number
53
+ end
54
+
55
+ # Stop and start the servers.
56
+ def restart
57
+ stop
58
+ sleep 0.1 # Let's breath a bit shall we ?
59
+ start
60
+ end
61
+
62
+ def server_id(number)
63
+ if socket
64
+ socket_for(number)
65
+ elsif swiftiply?
66
+ [address, first_port, number].join(':')
67
+ else
68
+ [address, number].join(':')
69
+ end
70
+ end
71
+
72
+ def log_file_for(number)
73
+ include_server_number log_file, number
74
+ end
75
+
76
+ def pid_file_for(number)
77
+ include_server_number pid_file, number
78
+ end
79
+
80
+ def socket_for(number)
81
+ include_server_number socket, number
82
+ end
83
+
84
+ def pid_for(number)
85
+ File.read(pid_file_for(number)).chomp.to_i
86
+ end
87
+
88
+ private
89
+ # Send the command to the +thin+ script
90
+ def run(cmd, number)
91
+ cmd_options = @options.reject { |option, value| CLUSTER_OPTIONS.include?(option) }
92
+ cmd_options.merge!(:pid => pid_file_for(number), :log => log_file_for(number))
93
+ if socket
94
+ cmd_options.merge!(:socket => socket_for(number))
95
+ elsif swiftiply?
96
+ cmd_options.merge!(:port => first_port)
97
+ else
98
+ cmd_options.merge!(:port => number)
99
+ end
100
+ Command.run(cmd, cmd_options)
101
+ end
102
+
103
+ def with_each_server
104
+ if only
105
+ if first_port && only < 80
106
+ # interpret +only+ as a sequence number
107
+ yield first_port + only
108
+ else
109
+ # interpret +only+ as an absolute port number
110
+ yield only
111
+ end
112
+ elsif socket || swiftiply?
113
+ size.times { |n| yield n }
114
+ else
115
+ size.times { |n| yield first_port + n }
116
+ end
117
+ end
118
+
119
+ # Add the server port or number in the filename
120
+ # so each instance get its own file
121
+ def include_server_number(path, number)
122
+ ext = File.extname(path)
123
+ path.gsub(/#{ext}$/, ".#{number}#{ext}")
124
+ end
125
+ end
126
+ end
127
+ end