plezi 0.7.7 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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