Class: PhusionPassenger::AbstractServerCollection

Inherits:
Object
  • Object
show all
Includes:
Utils
Defined in:
lib/phusion_passenger/abstract_server_collection.rb

Overview

This class maintains a collection of AbstractServer objects. One can add new AbstractServer objects, or look up existing ones via a key. AbstractServerCollection also automatically takes care of cleaning up AbstractServers that have been idle for too long.

This class exists because both SpawnManager and ClassicRails::FrameworkSpawner need this kind of functionality. SpawnManager maintains a collection of ClassicRails::FrameworkSpawner and ClassicRails::ApplicationSpawner objects, while ClassicRails::FrameworkSpawner maintains a collection of ClassicRails::ApplicationSpawner objects.

This class is thread-safe as long as the specified thread-safety rules are followed.

Constant Summary

Constant Summary

Constants included from Utils

Utils::FileSystemWatcher, Utils::NULL

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Methods included from Utils

#split_by_null_into_hash

Constructor Details

- (AbstractServerCollection) initialize

Returns a new instance of AbstractServerCollection



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 44

def initialize
	@collection = {}
	@lock = Mutex.new
	@cleanup_lock = Mutex.new
	@cond = ConditionVariable.new
	@done = false
	
	# The next time the cleaner thread should check for idle servers.
	# The value may be nil, in which case the value will be calculated
	# at the end of the #synchronized block.
	#
	# Invariant:
	#    if value is not nil:
	#       There exists an s in @collection with s.next_cleaning_time == value.
	#       for all s in @collection:
	#          if eligable_for_cleanup?(s):
	#             s.next_cleaning_time <= value
	@next_cleaning_time = Time.now + 60 * 60
	@next_cleaning_time_changed = false
	
	@cleaner_thread = Thread.new do
		begin
			@lock.synchronize do
				cleaner_thread_main
			end
		rescue Exception => e
			print_exception(self.class.to_s, e)
		end
	end
end

Instance Attribute Details

- (Object) next_cleaning_time (readonly)

Returns the value of attribute next_cleaning_time



40
41
42
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 40

def next_cleaning_time
  @next_cleaning_time
end

Instance Method Details

- (Object) check_idle_servers!

Tell the cleaner thread to check the collection as soon as possible, instead of sleeping until the next scheduled cleaning time.

Precondition: this method must NOT be called within a #synchronize block.



208
209
210
211
212
213
214
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 208

def check_idle_servers!
	must_not_be_in_synchronize_block
	@lock.synchronize do
		@next_cleaning_time = Time.now - 60 * 60
		@cond.signal
	end
end

- (Object) cleanup

Cleanup all resources used by this AbstractServerCollection. All AbstractServers from the collection will be deleted. Each AbstractServer will be stopped, if necessary. The background thread which removes idle AbstractServers will be stopped.

After calling this method, this AbstractServerCollection object will become unusable.

Precondition: this method must NOT be called within a #synchronize block.



260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 260

def cleanup
	must_not_be_in_synchronize_block
	@cleanup_lock.synchronize do
		return if @done
		@lock.synchronize do
			@done = true
			@cond.signal
		end
		@cleaner_thread.join
		synchronize do
			clear
		end
	end
end

- (Object) clear

Delete all AbstractServers from the collection. Each AbstractServer will be stopped, if necessary.

Precondition: this method must be called within a #synchronize block.



241
242
243
244
245
246
247
248
249
250
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 241

def clear
	must_be_in_synchronize_block
	@collection.each_value do |server|
		if server.started?
			server.stop
		end
	end
	@collection.clear
	@next_cleaning_time = nil
end

- (Object) delete(key)

Deletes from the collection the AbstractServer that's associated with the given key. If no such AbstractServer exists, nothing will happen.

If the AbstractServer is started, then it will be stopped before deletion.

Precondition: this method must be called within a #synchronize block.

Raises:

  • (ArgumentError)


170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 170

def delete(key)
	raise ArgumentError, "cleanup() has already been called." if @done
	must_be_in_synchronize_block
	server = @collection[key]
	if server
		if server.started?
			server.stop
		end
		@collection.delete(key)
		if server.next_cleaning_time == @next_cleaning_time
			@next_cleaning_time = nil
		end
	end
end

- (Object) each

Iterate over all AbstractServer objects.

Precondition: this method must be called within a #synchronize block.



219
220
221
222
223
224
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 219

def each
	must_be_in_synchronize_block
	each_pair do |key, server|
		yield server
	end
end

- (Object) each_pair

Iterate over all keys and associated AbstractServer objects.

Precondition: this method must be called within a #synchronize block.

Raises:

  • (ArgumentError)


229
230
231
232
233
234
235
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 229

def each_pair
	raise ArgumentError, "cleanup() has already been called." if @done
	must_be_in_synchronize_block
	@collection.each_pair do |key, server|
		yield(key, server)
	end
end

- (Boolean) empty?

Checks whether the collection is empty.

Precondition: this method must be called within a #synchronize block.

Returns:

  • (Boolean)


159
160
161
162
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 159

def empty?
	must_be_in_synchronize_block
	return @collection.empty?
end

- (Boolean) has_key?(key)

Checks whether there's an AbstractServer object associated with the given key.

Precondition: this method must be called within a #synchronize block.

Returns:

  • (Boolean)


151
152
153
154
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 151

def has_key?(key)
	must_be_in_synchronize_block
	return @collection.has_key?(key)
end

- (Object) lookup_or_add(key)

Lookup and returns an AbstractServer with the given key.

If there is no AbstractSerer associated with the given key, then the given block will be called. That block must return an AbstractServer object. Then, that object will be stored in the collection, and returned.

The block must set the 'max_idle_time' attribute on the AbstractServer. AbstractServerCollection's idle cleaning interval will be adapted to accomodate with this. Changing the value outside this block is not guaranteed to have any effect on the idle cleaning interval. A max_idle_time value of nil or 0 means the AbstractServer will never be idle cleaned.

If the block raises an exception, then the collection will not be modified, and the exception will be propagated.

Precondition: this method must be called within a #synchronize block.

Raises:

  • (ArgumentError)


124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 124

def lookup_or_add(key)
	raise ArgumentError, "cleanup() has already been called." if @done
	must_be_in_synchronize_block
	server = @collection[key]
	if server
		register_activity(server)
		return server
	else
		server = yield
		if !server.respond_to?(:start)
			raise TypeError, "The block didn't return a valid AbstractServer object."
		end
		if eligable_for_cleanup?(server)
			server.next_cleaning_time = Time.now + server.max_idle_time
			if @next_cleaning_time && server.next_cleaning_time < @next_cleaning_time
				@next_cleaning_time = server.next_cleaning_time
				@next_cleaning_time_changed = true
			end
		end
		@collection[key] = server
		return server
	end
end

- (Object) register_activity(server)

Notify this AbstractServerCollection that server has performed an activity. This AbstractServerCollection will update the idle information associated with server accordingly.

lookup_or_add already automatically updates idle information, so you only need to call this method if the time at which the server has performed an activity is not close to the time at which lookup_or_add had been called.

Precondition: this method must be called within a #synchronize block.



194
195
196
197
198
199
200
201
202
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 194

def register_activity(server)
	must_be_in_synchronize_block
	if eligable_for_cleanup?(server)
		if server.next_cleaning_time == @next_cleaning_time
			@next_cleaning_time = nil
		end
		server.next_cleaning_time = Time.now + server.max_idle_time
	end
end

- (Object) synchronize

Acquire the lock for this AbstractServerCollection object, and run the code within the block. The entire block will be a single atomic operation.



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 78

def synchronize
	@lock.synchronize do
		@in_synchronize_block = true
		begin
			yield
		ensure
			if @next_cleaning_time.nil?
				@collection.each_value do |server|
					if @next_cleaning_time.nil? ||
					   (eligable_for_cleanup?(server) &&
					    server.next_cleaning_time < @next_cleaning_time
					   )
						@next_cleaning_time = server.next_cleaning_time
					end
				end
				if @next_cleaning_time.nil?
					# There are no servers in the collection with an idle timeout.
					@next_cleaning_time = Time.now + 60 * 60
				end
				@next_cleaning_time_changed = true
			end
			if @next_cleaning_time_changed
				@next_cleaning_time_changed = false
				@cond.signal
			end
			@in_synchronize_block = false
		end
	end
end