plezi 0.7.7 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +3 -3
  4. data/lib/plezi.rb +35 -36
  5. data/lib/plezi/common/cache.rb +94 -0
  6. data/lib/plezi/{base → common}/dsl.rb +87 -68
  7. data/lib/plezi/{base → common}/logging.rb +15 -15
  8. data/lib/plezi/eventmachine/connection.rb +192 -0
  9. data/lib/plezi/eventmachine/em.rb +95 -0
  10. data/lib/plezi/eventmachine/io.rb +265 -0
  11. data/lib/plezi/eventmachine/protocol.rb +54 -0
  12. data/lib/plezi/eventmachine/queue.rb +51 -0
  13. data/lib/plezi/eventmachine/ssl_connection.rb +138 -0
  14. data/lib/plezi/eventmachine/timers.rb +117 -0
  15. data/lib/plezi/eventmachine/workers.rb +35 -0
  16. data/lib/plezi/handlers/http_host.rb +4 -4
  17. data/lib/plezi/handlers/magic_helpers.rb +1 -21
  18. data/lib/plezi/handlers/route.rb +3 -3
  19. data/lib/plezi/server/{helpers/http.rb → http.rb} +1 -57
  20. data/lib/plezi/server/{protocols/http_protocol.rb → http_protocol.rb} +18 -35
  21. data/lib/plezi/server/{protocols/http_request.rb → http_request.rb} +1 -1
  22. data/lib/plezi/server/{protocols/http_response.rb → http_response.rb} +45 -22
  23. data/lib/plezi/server/{helpers/mime_types.rb → mime_types.rb} +0 -0
  24. data/lib/plezi/server/{protocols/websocket.rb → websocket.rb} +34 -37
  25. data/lib/plezi/server/{protocols/ws_response.rb → ws_response.rb} +37 -19
  26. data/lib/plezi/version.rb +1 -1
  27. data/plezi.gemspec +3 -3
  28. data/resources/config.ru +9 -11
  29. metadata +27 -30
  30. data/lib/plezi/base/cache.rb +0 -89
  31. data/lib/plezi/base/connections.rb +0 -33
  32. data/lib/plezi/base/engine.rb +0 -77
  33. data/lib/plezi/base/events.rb +0 -93
  34. data/lib/plezi/base/io_reactor.rb +0 -62
  35. data/lib/plezi/base/rack_app.rb +0 -89
  36. data/lib/plezi/base/services.rb +0 -57
  37. data/lib/plezi/base/timers.rb +0 -71
  38. data/lib/plezi/server/README.md +0 -33
  39. data/lib/plezi/server/services/basic_service.rb +0 -224
  40. data/lib/plezi/server/services/no_service.rb +0 -196
  41. data/lib/plezi/server/services/ssl_service.rb +0 -193
@@ -33,28 +33,28 @@ module Plezi
33
33
  end
34
34
 
35
35
  # logs info
36
- def log line
37
- @logger.info line
38
- @copy_to_stdout.info line if @copy_to_stdout
36
+ def log data
37
+ @logger.info data
38
+ @copy_to_stdout.info data if @copy_to_stdout
39
39
  end
40
40
  # logs info
41
- def info line
42
- @logger.info line
43
- @copy_to_stdout.info line if @copy_to_stdout
41
+ def info data
42
+ @logger.info data
43
+ @copy_to_stdout.info data if @copy_to_stdout
44
44
  end
45
45
  # logs warning
46
- def warn line
47
- @logger.warn line
48
- @copy_to_stdout.warn line if @copy_to_stdout
46
+ def warn data
47
+ @logger.warn data
48
+ @copy_to_stdout.warn data if @copy_to_stdout
49
49
  end
50
50
  # logs errors
51
- def error line
52
- @logger.error line
53
- @copy_to_stdout.error line if @copy_to_stdout
51
+ def error data
52
+ @logger.error data
53
+ @copy_to_stdout.error data if @copy_to_stdout
54
54
  end
55
55
  # logs a fatal error
56
- def fatal line
57
- @logger.fatal line
58
- @copy_to_stdout.fatal line if @copy_to_stdout
56
+ def fatal data
57
+ @logger.fatal data
58
+ @copy_to_stdout.fatal data if @copy_to_stdout
59
59
  end
60
60
  end
@@ -0,0 +1,192 @@
1
+ # encoding: UTF-8
2
+
3
+
4
+
5
+ module Plezi
6
+
7
+ module EventMachine
8
+
9
+ class Connection
10
+ attr_reader :socket, :params, :active_time, :out_que, :locker
11
+ attr_accessor :protocol, :handler, :timeout
12
+
13
+ # initializes the connection and it's settings.
14
+ def initialize socket, params
15
+ @socket, @params, @handler = socket, params, params[:handler]
16
+ # socket.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, "\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" #== [10 sec 0 usec].pack '1_2'
17
+ @out_que, @locker = [], Mutex.new
18
+ @protocol = params[:protocol].is_a?(Class) ? (params[:protocol].new self, params) : params[:protocol]
19
+ @protocol.on_connect if @protocol.is_a?(Protocol)
20
+ touch
21
+ @timeout ||= 5
22
+ end
23
+
24
+ # used by the EM when the connection should handle data.
25
+ def call
26
+ # don't let competing threads do the same job.
27
+ return false if @locker.locked?
28
+ begin
29
+ @locker.synchronize do
30
+ return disconnect if disconnected?
31
+ protocol.on_message
32
+ end
33
+
34
+ rescue Exception => e
35
+ PL.error e
36
+ return disconnect
37
+ end
38
+ end
39
+
40
+ def clear?
41
+ return false unless timedout? || disconnected?
42
+ disconnect
43
+ true
44
+ end
45
+
46
+ # checks if a connection timed out
47
+ def timedout?
48
+ Time.now - @active_time > @timeout.to_i
49
+ end
50
+
51
+ # resets the timer for the connection timeout
52
+ def touch
53
+ @active_time = Time.now
54
+ end
55
+
56
+ # returns an IO-like object used for reading/writing (unlike the original IO object, this can be an SSL layer or any other wrapper object).
57
+ def io
58
+ @socket
59
+ end
60
+
61
+ # sends data immidiately - forcing the data to be sent, flushing any pending messages in the que
62
+ def send data = nil
63
+ return false unless @out_que.any? || data
64
+ @locker.synchronize do
65
+ unless @out_que.empty?
66
+ @out_que.each { |d| _send d rescue disconnect }
67
+ @out_que.clear
68
+ end
69
+ (_send data rescue disconnect) if data
70
+ end
71
+ end
72
+
73
+ # the non-blocking proc used for send_nonblock
74
+ SEND_COMPLETE_PROC = Proc.new {|c| c.send }
75
+
76
+ # sends data without waiting - data might be sent in a different order then intended.
77
+ def send_nonblock data
78
+ touch
79
+ @locker.synchronize {@out_que << data}
80
+ EventMachine.queue [self], SEND_COMPLETE_PROC
81
+ end
82
+
83
+ # adds data to the out buffer - but doesn't send the data until a send event is called.
84
+ def << data
85
+ touch
86
+ @locker.synchronize {@out_que << data}
87
+ end
88
+
89
+ # makes sure any data in the que is send and calls `flush` on the socket, to make sure the buffer is sent.
90
+ def flush
91
+ send
92
+ io.flush
93
+ end
94
+
95
+ # the proc used to remove the connection from the IO stack.
96
+ REMOVE_CONNECTION_PROC = Proc.new {|old_io| EventMachine.remove_io old_io }
97
+ # called once a socket is disconnected or needs to be disconnected.
98
+ def on_disconnect
99
+ EventMachine.queue [@socket], REMOVE_CONNECTION_PROC
100
+ @locker.synchronize do
101
+ @out_que.each { |d| _send d rescue true}
102
+ @out_que.clear
103
+ io.flush rescue true
104
+ io.close rescue true
105
+ end
106
+ EventMachine.queue [], protocol.method(:on_disconnect) if protocol && !protocol.is_a?(Class)
107
+ end
108
+
109
+ # status markers
110
+
111
+ # closes the connection
112
+ def close
113
+ @locker.synchronize do
114
+ io.flush rescue true
115
+ io.close rescue true
116
+ end
117
+ end
118
+ # returns true if the service is disconnected
119
+ def disconnected?
120
+ (@socket.closed? || socket.stat.mode == 0140222) rescue true # if mode is read only, it's the same as closed.
121
+ end
122
+ # the async disconnect proc
123
+ FIRE_DISCONNECT_PROC = Proc.new {|handler| handler.on_disconnect }
124
+ # disconects the service.
125
+ def disconnect
126
+ @out_que.clear
127
+ EventMachine.queue [self], FIRE_DISCONNECT_PROC
128
+ end
129
+ # returns true if the socket has content to be read.
130
+ def has_incoming_data?
131
+ (@socket.stat.size > 0) rescue false
132
+ end
133
+
134
+
135
+ # identification markers
136
+
137
+ #returns the service type - set to normal
138
+ def service_type
139
+ 'normal'
140
+ end
141
+ #returns true if the service is encrypted using the OpenSSL library.
142
+ def ssl?
143
+ false
144
+ end
145
+
146
+ #################
147
+ # overide the followind methods for any child class.
148
+
149
+ # this is a public method and it should be used by child classes to implement each
150
+ # read(_nonblock) action. accepts one argument ::size for an optional buffer size to be read.
151
+ def read size = 1048576
152
+ begin
153
+ data = @socket.recv_nonblock( size )
154
+ return nil if data.to_s.empty?
155
+ touch
156
+ data
157
+ rescue Exception => e
158
+
159
+ end
160
+ end
161
+ # # this is a public method and it should be used by child classes to implement each
162
+ # # read(_nonblock) action. accepts one argument ::size for an optional buffer size to be read.
163
+ # def read_line
164
+ # data = @line_data ||= ''
165
+ # begin
166
+ # data << @socket.recv_nonblock( 1 ).to_s until data[-1] == "\n"
167
+ # @line_data = ''
168
+ # return data
169
+ # rescue => e
170
+ # return false
171
+ # ensure
172
+ # touch
173
+ # end
174
+ # end
175
+
176
+ protected
177
+
178
+ # this is a protected method, it should be used by child classes to implement each
179
+ # send action.
180
+ def _send data
181
+ @active_time += 7200
182
+ len = data.bytesize
183
+ act = @socket.send data, 0
184
+ while len > act
185
+ act += @socket.send data.byteslice(act..-1) , 0
186
+ end
187
+ touch
188
+ end
189
+
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,95 @@
1
+ module Plezi
2
+
3
+ # This module holds the events, timers and IO logic and workflow (asynchronous workflow).
4
+ #
5
+ # Timed events are approximated and their exact time of execution is dependant on the workload and continues uptime of the process (timed events AREN'T persistant)
6
+ module EventMachine
7
+
8
+ module_function
9
+
10
+ # sets the amount of worker threads cycling the event machine and starts (or re-starts) the event machine.
11
+ def start count=12
12
+ @workers ||= []
13
+ @workers_lock ||= Mutex.new
14
+ stop unless @workers.count <= count
15
+ (count - @workers.count).times {@workers << Worker.new}
16
+ end
17
+
18
+ # runs through all existing events and one idle cycle
19
+ def run wait = 0.1
20
+ begin
21
+ fire_timers
22
+ do_job while do_job
23
+ # replace with io
24
+ review_io || sleep(wait)
25
+ rescue => e
26
+ Plezi.error e
27
+ end
28
+ end
29
+
30
+ def stop_and_wait
31
+ @workers_lock.synchronize { @workers.each {|w| w.stop }; @workers.each {|w| w.join }; @workers.clear; Worker.reset_wait}
32
+ end
33
+
34
+ def stop
35
+ @workers_lock.synchronize { @workers.each {|w| w.stop } ; @workers.clear; Worker.reset_wait}
36
+ end
37
+
38
+ def running?
39
+ @workers && @workers.any?
40
+ end
41
+ def count_living_workers
42
+ (@workers_lock.synchronize { @workers.select {|w| w.alive? } } ).count
43
+ end
44
+ def workers_status
45
+ @workers_lock.synchronize { @workers.map {|w| w.status } }
46
+ end
47
+
48
+ SHUTDOWN_CALLBACKS = []
49
+ # runs the shutdown queue
50
+ def shutdown
51
+ stop_and_wait rescue false
52
+ old_timeout = io_timeout
53
+ io_timeout = 0.001
54
+ run
55
+ io_timeout = old_timeout
56
+ do_job while do_job
57
+ stop_connections
58
+ do_job while do_job
59
+ QUEUE_LOCKER.synchronize do
60
+ SHUTDOWN_CALLBACKS.each { |s_job| s_job[0].call(*s_job[1]) }
61
+ SHUTDOWN_CALLBACKS.clear
62
+ end
63
+ true
64
+ end
65
+ # Adds a callback to be called once the services were shut down. see: callback for more info.
66
+ def on_shutdown object=nil, method=nil, *args, &block
67
+ if block && !object && !method
68
+ QUEUE_LOCKER.synchronize {SHUTDOWN_CALLBACKS << [block, args]}
69
+ elsif block
70
+ QUEUE_LOCKER.synchronize {SHUTDOWN_CALLBACKS << [(Proc.new {|*a| block.call(object.method(method).call(*a))} ), args]}
71
+ elsif object && method
72
+ QUEUE_LOCKER.synchronize {SHUTDOWN_CALLBACKS << [object.method(method), args]}
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ module_function
79
+
80
+ # Plezi event cycle settings: gets how many worker threads Plezi will initially run.
81
+ def max_threads
82
+ @max_threads ||= 16
83
+ end
84
+ # Plezi event cycle settings: sets how many worker threads Plezi will initially run.
85
+ def max_threads= value
86
+ raise "Plezi will hang and do nothing if there isn't at least one (1) working thread. Cannot set Plezi.max_threads = #{value}" if value.to_i <= 0
87
+ @max_threads = value.to_i
88
+ start @max_threads if EventMachine.running?
89
+ end
90
+
91
+ # Adds a callback to be called once the services were shut down. see: callback for more info.
92
+ def on_shutdown object=nil, method=nil, *args, &block
93
+ EventMachine.on_shutdown object, method, *args, &block
94
+ end
95
+ end
@@ -0,0 +1,265 @@
1
+ module Plezi
2
+ module EventMachine
3
+
4
+ # a basic IO listening socket wrapper.
5
+ class BasicIO
6
+
7
+ # attribute readers
8
+ attr_reader :io, :params
9
+ # initialize the listener with the actual socket and the properties.
10
+ def initialize io, params
11
+ @io, @params = io, params
12
+ end
13
+ # returns true if the socket is closed and the object can be cleared.
14
+ def clear?
15
+ @io.closed?
16
+ end
17
+ # accepts a new connection, adding it to the IO stack, so that it will be reviewed.
18
+ def call
19
+ begin
20
+ socket = io.accept_nonblock
21
+ handler = (@params[:ssl] ? SSLConnection : Connection).new socket, @params
22
+ EventMachine.add_io socket, handler
23
+ rescue Errno::EWOULDBLOCK => e
24
+
25
+ rescue OpenSSL::SSL::SSLError => e
26
+ Plezi.info "Due to an SSL issue, the connection was refused (maybe untrusted certificate)?"
27
+
28
+ rescue => e
29
+ Plezi.error e
30
+ end
31
+ end
32
+ end
33
+ module_function
34
+
35
+ # the IO stack.
36
+ EM_IO = {}
37
+ # the IO Mutex
38
+ IO_LOCKER = Mutex.new
39
+
40
+ # Adds an io object to the Plezi event machine.
41
+ #
42
+ # accepts:
43
+ # io:: an actual IO object (i.e. socket) that can be passed on to IO.select.
44
+ # job:: an object that answers to #call. job#call will be called whenever the IO object is flagged by IO.select.
45
+ #
46
+ def add_io io, job
47
+ IO_LOCKER.synchronize { EM_IO[io] = job }
48
+ end
49
+
50
+ # removes an IO from the event machine.
51
+ def remove_io io
52
+ IO_LOCKER.synchronize { (EM_IO.delete io).close rescue true }
53
+ end
54
+
55
+ # deletes any connections that are closed or timed out.
56
+ def clear_connections
57
+ IO_LOCKER.synchronize { EM_IO.delete_if { |io, c| c.clear? } }
58
+ end
59
+
60
+ # forces the event machine to forget all the existing connections (they will not be reviewed any longer).
61
+ def forget_connections
62
+ IO_LOCKER.synchronize { EM_IO.clear }
63
+ end
64
+
65
+ # stops all the connections without stopping the lisntener IO's.
66
+ def stop_connections
67
+ IO_LOCKER.synchronize { EM_IO.each { |io, c| Plezi.run_async { c.disconnect rescue true } } }
68
+ end
69
+
70
+ # set the default idle waiting time.
71
+ @io_timeout = 0.1
72
+
73
+ # Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
74
+ #
75
+ # No timing methods will be called during this interval.
76
+ #
77
+ # Gets the current idle setting. The default setting is 0.1 seconds.
78
+ def io_timeout
79
+ @io_timeout
80
+ end
81
+ # Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
82
+ #
83
+ # No timing methods will be called during this interval (#run_after / #run_every).
84
+ #
85
+ # It's rare, but it's one of the reasons for the timeout: some connections might wait for the timeout befor being established.
86
+ #
87
+ # set the current idle setting
88
+ def io_timeout= value
89
+ @io_timeout = value
90
+ end
91
+
92
+ # the proc for async IO removal, in case of IO exceptions raised by unexpectedly closed sockets.
93
+ IO_CLEAR_ASYNC_PROC = Proc.new {|c| c.protocol.on_disconnect if (c.protocol rescue false) }
94
+
95
+ # hangs for IO data or io_timeout
96
+ def review_io
97
+ return false if IO_LOCKER.locked?
98
+ IO_LOCKER.synchronize do
99
+ return false unless queue_empty? && EM_IO.any?
100
+ io_array = EM_IO.keys
101
+ begin
102
+ io_r = ::IO.select(io_array, nil, io_array, @io_timeout)
103
+ return false unless io_r
104
+ io_r[0].each {|io| queue [], EM_IO[io] }
105
+ io_r[2].each { |io| EM_IO.delete io }
106
+ rescue Errno::EWOULDBLOCK => e
107
+
108
+ rescue => e
109
+ EM_IO.keys.each {|io| EventMachine.queue [EM_IO.delete(io)], IO_CLEAR_ASYNC_PROC if io.closed?}
110
+ raise e
111
+ end
112
+ true
113
+ end
114
+ end
115
+
116
+ end
117
+
118
+ module DSL
119
+ module_function
120
+
121
+ # public API to add a service to the framework.
122
+ # accepts a Hash object with any of the following options (Hash keys):
123
+ # port:: port number. defaults to 3000 or the port specified when the script was called.
124
+ # host:: the host name. defaults to any host not explicitly defined (a catch-all).
125
+ # alias:: a String or an Array of Strings which represent alternative host names (i.e. `alias: ["admin.google.com", "admin.gmail.com"]`).
126
+ # root:: the public root folder. if this is defined, static files will be served from the location.
127
+ # assets:: the assets root folder. defaults to nil (no assets support). if the path is defined, assets will be served from `/assets/...` (or the public_asset path defined) before any static files. assets will not be served if the file in the /public/assets folder if up to date (a rendering attempt will be made for systems that allow file writing).
128
+ # assets_public:: the assets public uri location (uri format, NOT a file path). defaults to `/assets`. assets will be saved (or rendered) to the assets public folder and served as static files.
129
+ # assets_callback:: a method that accepts one parameters: `request` and renders any custom assets. the method should return `false` unless it has created a response object (`response = Plezi::HTTPResponse.new(request)`) and sent a response to the client using `response.finish`.
130
+ # save_assets:: saves the rendered assets to the filesystem, under the public folder. defaults to false.
131
+ # templates:: the templates root folder. defaults to nil (no template support). templates can be rendered by a Controller class, using the `render` method.
132
+ # ssl:: if true, an SSL service will be attempted. if no certificate is defined, an attempt will be made to create a self signed certificate.
133
+ # ssl_key:: the public key for the SSL service.
134
+ # ssl_cert:: the certificate for the SSL service.
135
+ #
136
+ # some further options, which are unstable and might be removed in future versions, are:
137
+ # protocol:: the protocol objects (usually a class, but any object answering `#call` will do).
138
+ # handler:: an optional handling object, to be called upon by the protocol (i.e. #on_message, #on_connect, etc'). this option is used to allow easy protocol switching, such as from HTTP to Websockets.
139
+ #
140
+ # Duringn normal Plezi behavior, the optional `handler` object will be returned if `listen` is called more than once for the same port.
141
+ #
142
+ # assets:
143
+ #
144
+ # assets support will render `.sass`, `.scss` and `.coffee` and save them as local files (`.css`, `.css`, and `.js` respectively)
145
+ # before sending them as static files.
146
+ #
147
+ # templates:
148
+ #
149
+ # ERB, Slim and Haml are natively supported.
150
+ #
151
+ def listen parameters = {}
152
+ # update default values
153
+ parameters = {assets_public: '/assets'}.update(parameters)
154
+
155
+ # set port if undefined
156
+ if !parameters[:port] && defined? ARGV
157
+ if ARGV.find_index('-p')
158
+ port_index = ARGV.find_index('-p') + 1
159
+ parameters[:port] ||= ARGV[port_index].to_i
160
+ ARGV[port_index] = (parameters[:port] + 1).to_s
161
+ else
162
+ ARGV << '-p'
163
+ ARGV << '3001'
164
+ parameters[:port] ||= 3000
165
+ end
166
+ end
167
+
168
+ #keeps information of past ports.
169
+ @listeners ||= {}
170
+ @listeners_locker = Mutex.new
171
+
172
+ # check if the port is used twice.
173
+ if @listeners[parameters[:port]]
174
+ puts "WARNING: port aleady in use! returning existing service and attemptin to add host (maybe multiple hosts? use `host` instead)." unless parameters[:host]
175
+ @active_router = @listeners[parameters[:port]].params[:handler] || @listeners[parameters[:port]].params[:protocol]
176
+ @active_router.add_host parameters[:host], parameters if @active_router.is_a?(HTTPRouter)
177
+ return @active_router
178
+ end
179
+
180
+ # make sure the protocol exists.
181
+ unless parameters[:protocol]
182
+ parameters[:protocol] = HTTPProtocol
183
+ parameters[:handler] ||= HTTPRouter.new
184
+ end
185
+
186
+ # create the EventMachine IO object.
187
+ io = defined?(PLEZI_ON_RACK) ? parameters : EventMachine::BasicIO.new( TCPServer.new(parameters[:port]), parameters)
188
+ EventMachine.add_io io.io, io unless defined? PLEZI_ON_RACK
189
+ @listeners_locker.synchronize { @listeners[parameters[:port]] = io }
190
+ # set the active router to the handler or the protocol.
191
+ @active_router = (parameters[:handler] || parameters[:protocol])
192
+ @active_router.add_host(parameters[:host], parameters) if @active_router.is_a?(HTTPRouter)
193
+
194
+ Plezi.run_async { Plezi.info "Started listening on port #{parameters[:port]}." } unless defined?(PLEZI_ON_RACK)
195
+
196
+ # return the current handler or the protocol..
197
+ @active_router
198
+ end
199
+
200
+ # Plezi Engine, DO NOT CALL. creates the thread pool and starts cycling through the events.
201
+ def start_services
202
+ if @listeners && @listeners.any?
203
+ # prepare threads
204
+ exit_flag = false
205
+ threads = []
206
+ Plezi.run_every(5, &EventMachine.method(:clear_connections))
207
+ Plezi.run_every(3_600) {GC.start; Plezi.info "Refreshing worker threads."; EventMachine.stop; EventMachine.start Plezi.max_threads}
208
+ # run_every( 1 , Proc.new() { Plezi.info "#{IO_CONNECTION_DIC.length} active connections ( #{ IO_CONNECTION_DIC.select{|k,v| v.protocol.is_a?(WSProtocol)} .length } websockets)." })
209
+ # run_every 10 , -> {Plezi.info "Cache report: #{CACHE_STORE.length} objects cached." }
210
+ puts "Services running Plezi version #{Plezi::VERSION}. Press ^C to stop"
211
+ EventMachine.start Plezi.max_threads
212
+
213
+ # set signal tarps
214
+ trap('INT'){ exit_flag = true; raise "close Plezi" }
215
+ trap('TERM'){ exit_flag = true; raise "close Plezi" }
216
+ # sleep until trap raises exception (cycling might cause the main thread to ignor signals and lose attention)
217
+ sleep rescue true
218
+ # avoid refreshing the working threads and stop all timed events.
219
+ EventMachine.clear_timers
220
+ # start shutdown.
221
+ exit_flag = true
222
+ # set new tarps
223
+ trap('INT'){ puts 'Forced exit.'; Kernel.exit } #rescue true}
224
+ trap('TERM'){ puts 'Forced exit.'; Kernel.exit } #rescue true }
225
+ puts 'Started shutdown process. Press ^C to force quit.'
226
+ # shut down listening sockets
227
+ stop_services
228
+ # cycle down threads
229
+ Plezi.info "Finishing up and running shutdown tasks."
230
+ end
231
+ EventMachine.shutdown
232
+ Plezi.info "Plezi shutdown as requested."
233
+ puts "Since we're resting, why not practice Tai Chi?"
234
+ # return exit code?
235
+ 0
236
+ end
237
+ # Closes and removes listening IO's registered by Plezi using #listen
238
+ def stop_services
239
+ Plezi.info 'Stopping services'
240
+ @listeners_locker.synchronize { @listeners.each {|port, io| EventMachine.remove_io(io.io); Plezi.info "Stoped listening on port #{port}" } ; @listeners.clear } if @listeners
241
+ true
242
+ end
243
+ end
244
+
245
+ module_function
246
+
247
+ # Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
248
+ #
249
+ # No timing methods will be called during this interval.
250
+ #
251
+ # Gets the current idle setting. The default setting is 0.1 seconds.
252
+ def idle_sleep
253
+ EventMachine.io_timeout
254
+ end
255
+ # Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
256
+ #
257
+ # No timing methods will be called during this interval (#run_after / #run_every).
258
+ #
259
+ # It's rare, but it's one of the reasons for the timeout: some connections might wait for the timeout befor being established.
260
+ #
261
+ # set the current idle setting
262
+ def idle_sleep= value
263
+ EventMachine.io_timeout = value
264
+ end
265
+ end