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 Railz::FrameworkSpawner need this kind of functionality. SpawnManager maintains a collection of Railz::FrameworkSpawner and Railz::ApplicationSpawner objects, while Railz::FrameworkSpawner maintains a collection of Railz::ApplicationSpawner objects.

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeAbstractServerCollection

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

#next_cleaning_timeObject (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

#check_idle_servers!Object

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.



198
199
200
201
202
203
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 198

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

#cleanupObject

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.



246
247
248
249
250
251
252
253
254
255
256
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 246

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

#clearObject

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

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



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

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

#delete(key) ⇒ Object

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)


162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 162

def delete(key)
	raise ArgumentError, "cleanup() has already been called." if @done
	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

#eachObject

Iterate over all AbstractServer objects.

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



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

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

#each_pairObject

Iterate over all keys and associated AbstractServer objects.

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

Raises:

  • (ArgumentError)


217
218
219
220
221
222
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 217

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

#empty?Boolean

Checks whether the collection is empty.

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

Returns:

  • (Boolean)


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

def empty?
	return @collection.empty?
end

#has_key?(key) ⇒ Boolean

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

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

Returns:

  • (Boolean)


145
146
147
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 145

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

#lookup_or_add(key) ⇒ Object

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)


119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 119

def lookup_or_add(key)
	raise ArgumentError, "cleanup() has already been called." if @done
	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

#register_activity(server) ⇒ Object

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.



185
186
187
188
189
190
191
192
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 185

def register_activity(server)
	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

#synchronizeObject

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
# File 'lib/phusion_passenger/abstract_server_collection.rb', line 78

def synchronize
	@lock.synchronize do
		yield
		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
	end
end