sonixlabs-eventmachine-java 1.0.0.rc.4-java
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.
- data/.gitignore +22 -0
- data/.yardopts +7 -0
- data/GNU +281 -0
- data/Gemfile +3 -0
- data/LICENSE +60 -0
- data/README.md +109 -0
- data/Rakefile +20 -0
- data/docs/DocumentationGuidesIndex.md +27 -0
- data/docs/GettingStarted.md +521 -0
- data/docs/old/ChangeLog +211 -0
- data/docs/old/DEFERRABLES +246 -0
- data/docs/old/EPOLL +141 -0
- data/docs/old/INSTALL +13 -0
- data/docs/old/KEYBOARD +42 -0
- data/docs/old/LEGAL +25 -0
- data/docs/old/LIGHTWEIGHT_CONCURRENCY +130 -0
- data/docs/old/PURE_RUBY +75 -0
- data/docs/old/RELEASE_NOTES +94 -0
- data/docs/old/SMTP +4 -0
- data/docs/old/SPAWNED_PROCESSES +148 -0
- data/docs/old/TODO +8 -0
- data/eventmachine.gemspec +34 -0
- data/examples/guides/getting_started/01_eventmachine_echo_server.rb +18 -0
- data/examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rb +22 -0
- data/examples/guides/getting_started/03_simple_chat_server.rb +149 -0
- data/examples/guides/getting_started/04_simple_chat_server_step_one.rb +27 -0
- data/examples/guides/getting_started/05_simple_chat_server_step_two.rb +43 -0
- data/examples/guides/getting_started/06_simple_chat_server_step_three.rb +98 -0
- data/examples/guides/getting_started/07_simple_chat_server_step_four.rb +121 -0
- data/examples/guides/getting_started/08_simple_chat_server_step_five.rb +141 -0
- data/examples/old/ex_channel.rb +43 -0
- data/examples/old/ex_queue.rb +2 -0
- data/examples/old/ex_tick_loop_array.rb +15 -0
- data/examples/old/ex_tick_loop_counter.rb +32 -0
- data/examples/old/helper.rb +2 -0
- data/ext/binder.cpp +124 -0
- data/ext/binder.h +46 -0
- data/ext/cmain.cpp +876 -0
- data/ext/ed.cpp +1973 -0
- data/ext/ed.h +422 -0
- data/ext/em.cpp +2353 -0
- data/ext/em.h +239 -0
- data/ext/eventmachine.h +127 -0
- data/ext/extconf.rb +176 -0
- data/ext/fastfilereader/extconf.rb +103 -0
- data/ext/fastfilereader/mapper.cpp +214 -0
- data/ext/fastfilereader/mapper.h +59 -0
- data/ext/fastfilereader/rubymain.cpp +127 -0
- data/ext/kb.cpp +79 -0
- data/ext/page.cpp +107 -0
- data/ext/page.h +51 -0
- data/ext/pipe.cpp +347 -0
- data/ext/project.h +156 -0
- data/ext/rubymain.cpp +1297 -0
- data/ext/ssl.cpp +468 -0
- data/ext/ssl.h +94 -0
- data/java/.classpath +8 -0
- data/java/.project +17 -0
- data/java/src/com/rubyeventmachine/EmReactor.java +588 -0
- data/java/src/com/rubyeventmachine/EmReactorException.java +40 -0
- data/java/src/com/rubyeventmachine/EventableChannel.java +70 -0
- data/java/src/com/rubyeventmachine/EventableDatagramChannel.java +195 -0
- data/java/src/com/rubyeventmachine/EventableSocketChannel.java +364 -0
- data/lib/em/buftok.rb +110 -0
- data/lib/em/callback.rb +58 -0
- data/lib/em/channel.rb +64 -0
- data/lib/em/completion.rb +304 -0
- data/lib/em/connection.rb +712 -0
- data/lib/em/deferrable.rb +210 -0
- data/lib/em/deferrable/pool.rb +2 -0
- data/lib/em/file_watch.rb +73 -0
- data/lib/em/future.rb +61 -0
- data/lib/em/iterator.rb +270 -0
- data/lib/em/messages.rb +66 -0
- data/lib/em/pool.rb +151 -0
- data/lib/em/process_watch.rb +45 -0
- data/lib/em/processes.rb +123 -0
- data/lib/em/protocols.rb +36 -0
- data/lib/em/protocols/header_and_content.rb +138 -0
- data/lib/em/protocols/httpclient.rb +279 -0
- data/lib/em/protocols/httpclient2.rb +600 -0
- data/lib/em/protocols/line_and_text.rb +125 -0
- data/lib/em/protocols/line_protocol.rb +29 -0
- data/lib/em/protocols/linetext2.rb +161 -0
- data/lib/em/protocols/memcache.rb +331 -0
- data/lib/em/protocols/object_protocol.rb +46 -0
- data/lib/em/protocols/postgres3.rb +246 -0
- data/lib/em/protocols/saslauth.rb +175 -0
- data/lib/em/protocols/smtpclient.rb +365 -0
- data/lib/em/protocols/smtpserver.rb +640 -0
- data/lib/em/protocols/socks4.rb +66 -0
- data/lib/em/protocols/stomp.rb +202 -0
- data/lib/em/protocols/tcptest.rb +54 -0
- data/lib/em/pure_ruby.rb +1017 -0
- data/lib/em/queue.rb +71 -0
- data/lib/em/resolver.rb +192 -0
- data/lib/em/spawnable.rb +84 -0
- data/lib/em/streamer.rb +118 -0
- data/lib/em/threaded_resource.rb +90 -0
- data/lib/em/tick_loop.rb +85 -0
- data/lib/em/timers.rb +61 -0
- data/lib/em/version.rb +3 -0
- data/lib/eventmachine.rb +1532 -0
- data/lib/jeventmachine.rb +284 -0
- data/lib/sonixlabs-eventmachine-java.rb +1 -0
- data/rakelib/cpp.rake_example +77 -0
- data/rakelib/package.rake +98 -0
- data/rakelib/test.rake +8 -0
- data/tests/client.crt +31 -0
- data/tests/client.key +51 -0
- data/tests/em_test_helper.rb +64 -0
- data/tests/test_attach.rb +126 -0
- data/tests/test_basic.rb +294 -0
- data/tests/test_channel.rb +62 -0
- data/tests/test_completion.rb +177 -0
- data/tests/test_connection_count.rb +33 -0
- data/tests/test_defer.rb +18 -0
- data/tests/test_deferrable.rb +35 -0
- data/tests/test_epoll.rb +130 -0
- data/tests/test_error_handler.rb +38 -0
- data/tests/test_exc.rb +28 -0
- data/tests/test_file_watch.rb +65 -0
- data/tests/test_futures.rb +170 -0
- data/tests/test_get_sock_opt.rb +37 -0
- data/tests/test_handler_check.rb +35 -0
- data/tests/test_hc.rb +155 -0
- data/tests/test_httpclient.rb +190 -0
- data/tests/test_httpclient2.rb +128 -0
- data/tests/test_idle_connection.rb +23 -0
- data/tests/test_inactivity_timeout.rb +54 -0
- data/tests/test_kb.rb +34 -0
- data/tests/test_ltp.rb +138 -0
- data/tests/test_ltp2.rb +288 -0
- data/tests/test_next_tick.rb +104 -0
- data/tests/test_object_protocol.rb +36 -0
- data/tests/test_pause.rb +78 -0
- data/tests/test_pending_connect_timeout.rb +52 -0
- data/tests/test_pool.rb +194 -0
- data/tests/test_process_watch.rb +48 -0
- data/tests/test_processes.rb +128 -0
- data/tests/test_proxy_connection.rb +180 -0
- data/tests/test_pure.rb +88 -0
- data/tests/test_queue.rb +50 -0
- data/tests/test_resolver.rb +55 -0
- data/tests/test_running.rb +14 -0
- data/tests/test_sasl.rb +47 -0
- data/tests/test_send_file.rb +217 -0
- data/tests/test_servers.rb +33 -0
- data/tests/test_set_sock_opt.rb +37 -0
- data/tests/test_shutdown_hooks.rb +23 -0
- data/tests/test_smtpclient.rb +55 -0
- data/tests/test_smtpserver.rb +57 -0
- data/tests/test_spawn.rb +293 -0
- data/tests/test_ssl_args.rb +78 -0
- data/tests/test_ssl_methods.rb +48 -0
- data/tests/test_ssl_verify.rb +82 -0
- data/tests/test_threaded_resource.rb +53 -0
- data/tests/test_tick_loop.rb +59 -0
- data/tests/test_timers.rb +123 -0
- data/tests/test_ud.rb +8 -0
- data/tests/test_unbind_reason.rb +48 -0
- metadata +301 -0
data/lib/em/buftok.rb
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# BufferedTokenizer takes a delimiter upon instantiation, or acts line-based
|
|
2
|
+
# by default. It allows input to be spoon-fed from some outside source which
|
|
3
|
+
# receives arbitrary length datagrams which may-or-may-not contain the token
|
|
4
|
+
# by which entities are delimited.
|
|
5
|
+
#
|
|
6
|
+
# By default, new BufferedTokenizers will operate on lines delimited by "\n" by default
|
|
7
|
+
# or allow you to specify any delimiter token you so choose, which will then
|
|
8
|
+
# be used by String#split to tokenize the input data
|
|
9
|
+
#
|
|
10
|
+
# @example Using BufferedTokernizer to parse lines out of incoming data
|
|
11
|
+
#
|
|
12
|
+
# module LineBufferedConnection
|
|
13
|
+
# def receive_data(data)
|
|
14
|
+
# (@buffer ||= BufferedTokenizer.new).extract(data).each do |line|
|
|
15
|
+
# receive_line(line)
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @author Tony Arcieri
|
|
21
|
+
# @author Martin Emde
|
|
22
|
+
class BufferedTokenizer
|
|
23
|
+
# @param [String] delimiter
|
|
24
|
+
# @param [Integer] size_limit
|
|
25
|
+
def initialize(delimiter = "\n", size_limit = nil)
|
|
26
|
+
@delimiter = delimiter
|
|
27
|
+
@size_limit = size_limit
|
|
28
|
+
|
|
29
|
+
# The input buffer is stored as an array. This is by far the most efficient
|
|
30
|
+
# approach given language constraints (in C a linked list would be a more
|
|
31
|
+
# appropriate data structure). Segments of input data are stored in a list
|
|
32
|
+
# which is only joined when a token is reached, substantially reducing the
|
|
33
|
+
# number of objects required for the operation.
|
|
34
|
+
@input = []
|
|
35
|
+
|
|
36
|
+
# Size of the input buffer
|
|
37
|
+
@input_size = 0
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Extract takes an arbitrary string of input data and returns an array of
|
|
41
|
+
# tokenized entities, provided there were any available to extract.
|
|
42
|
+
#
|
|
43
|
+
# @example
|
|
44
|
+
#
|
|
45
|
+
# tokenizer.extract(data).
|
|
46
|
+
# map { |entity| Decode(entity) }.each { ... }
|
|
47
|
+
#
|
|
48
|
+
# @param [String] data
|
|
49
|
+
def extract(data)
|
|
50
|
+
# Extract token-delimited entities from the input string with the split command.
|
|
51
|
+
# There's a bit of craftiness here with the -1 parameter. Normally split would
|
|
52
|
+
# behave no differently regardless of if the token lies at the very end of the
|
|
53
|
+
# input buffer or not (i.e. a literal edge case) Specifying -1 forces split to
|
|
54
|
+
# return "" in this case, meaning that the last entry in the list represents a
|
|
55
|
+
# new segment of data where the token has not been encountered
|
|
56
|
+
entities = data.split @delimiter, -1
|
|
57
|
+
|
|
58
|
+
# Check to see if the buffer has exceeded capacity, if we're imposing a limit
|
|
59
|
+
if @size_limit
|
|
60
|
+
raise 'input buffer full' if @input_size + entities.first.size > @size_limit
|
|
61
|
+
@input_size += entities.first.size
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Move the first entry in the resulting array into the input buffer. It represents
|
|
65
|
+
# the last segment of a token-delimited entity unless it's the only entry in the list.
|
|
66
|
+
@input << entities.shift
|
|
67
|
+
|
|
68
|
+
# If the resulting array from the split is empty, the token was not encountered
|
|
69
|
+
# (not even at the end of the buffer). Since we've encountered no token-delimited
|
|
70
|
+
# entities this go-around, return an empty array.
|
|
71
|
+
return [] if entities.empty?
|
|
72
|
+
|
|
73
|
+
# At this point, we've hit a token, or potentially multiple tokens. Now we can bring
|
|
74
|
+
# together all the data we've buffered from earlier calls without hitting a token,
|
|
75
|
+
# and add it to our list of discovered entities.
|
|
76
|
+
entities.unshift @input.join
|
|
77
|
+
|
|
78
|
+
# Now that we've hit a token, joined the input buffer and added it to the entities
|
|
79
|
+
# list, we can go ahead and clear the input buffer. All of the segments that were
|
|
80
|
+
# stored before the join can now be garbage collected.
|
|
81
|
+
@input.clear
|
|
82
|
+
|
|
83
|
+
# The last entity in the list is not token delimited, however, thanks to the -1
|
|
84
|
+
# passed to split. It represents the beginning of a new list of as-yet-untokenized
|
|
85
|
+
# data, so we add it to the start of the list.
|
|
86
|
+
@input << entities.pop
|
|
87
|
+
|
|
88
|
+
# Set the new input buffer size, provided we're keeping track
|
|
89
|
+
@input_size = @input.first.size if @size_limit
|
|
90
|
+
|
|
91
|
+
# Now we're left with the list of extracted token-delimited entities we wanted
|
|
92
|
+
# in the first place. Hooray!
|
|
93
|
+
entities
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Flush the contents of the input buffer, i.e. return the input buffer even though
|
|
97
|
+
# a token has not yet been encountered.
|
|
98
|
+
#
|
|
99
|
+
# @return [String]
|
|
100
|
+
def flush
|
|
101
|
+
buffer = @input.join
|
|
102
|
+
@input.clear
|
|
103
|
+
buffer
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @return [Boolean]
|
|
107
|
+
def empty?
|
|
108
|
+
@input.empty?
|
|
109
|
+
end
|
|
110
|
+
end
|
data/lib/em/callback.rb
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module EventMachine
|
|
2
|
+
# Utility method for coercing arguments to an object that responds to :call.
|
|
3
|
+
# Accepts an object and a method name to send to, or a block, or an object
|
|
4
|
+
# that responds to :call.
|
|
5
|
+
#
|
|
6
|
+
# @example EventMachine.Callback used with a block. Returns that block.
|
|
7
|
+
#
|
|
8
|
+
# cb = EventMachine.Callback do |msg|
|
|
9
|
+
# puts(msg)
|
|
10
|
+
# end
|
|
11
|
+
# # returned object is a callable
|
|
12
|
+
# cb.call('hello world')
|
|
13
|
+
#
|
|
14
|
+
#
|
|
15
|
+
# @example EventMachine.Callback used with an object (to be more specific, class object) and a method name, returns an object that responds to #call
|
|
16
|
+
#
|
|
17
|
+
# cb = EventMachine.Callback(Object, :puts)
|
|
18
|
+
# # returned object is a callable that delegates to Kernel#puts (in this case Object.puts)
|
|
19
|
+
# cb.call('hello world')
|
|
20
|
+
#
|
|
21
|
+
#
|
|
22
|
+
# @example EventMachine.Callback used with an object that responds to #call. Returns the argument.
|
|
23
|
+
#
|
|
24
|
+
# cb = EventMachine.Callback(proc{ |msg| puts(msg) })
|
|
25
|
+
# # returned object is a callable
|
|
26
|
+
# cb.call('hello world')
|
|
27
|
+
#
|
|
28
|
+
#
|
|
29
|
+
# @overload Callback(object, method)
|
|
30
|
+
# Wraps `method` invocation on `object` into an object that responds to #call that proxies all the arguments to that method
|
|
31
|
+
# @param [Object] Object to invoke method on
|
|
32
|
+
# @param [Symbol] Method name
|
|
33
|
+
# @return [<#call>] An object that responds to #call that takes any number of arguments and invokes method on object with those arguments
|
|
34
|
+
#
|
|
35
|
+
# @overload Callback(object)
|
|
36
|
+
# Returns callable object as is, without any coercion
|
|
37
|
+
# @param [<#call>] An object that responds to #call
|
|
38
|
+
# @return [<#call>] Its argument
|
|
39
|
+
#
|
|
40
|
+
# @overload Callback(&block)
|
|
41
|
+
# Returns block passed to it without any coercion
|
|
42
|
+
# @return [<#call>] Block passed to this method
|
|
43
|
+
#
|
|
44
|
+
# @raise [ArgumentError] When argument doesn't respond to #call, method name is missing or when invoked without arguments and block isn't given
|
|
45
|
+
#
|
|
46
|
+
# @return [<#call>]
|
|
47
|
+
def self.Callback(object = nil, method = nil, &blk)
|
|
48
|
+
if object && method
|
|
49
|
+
lambda { |*args| object.__send__ method, *args }
|
|
50
|
+
else
|
|
51
|
+
if object.respond_to? :call
|
|
52
|
+
object
|
|
53
|
+
else
|
|
54
|
+
blk || raise(ArgumentError)
|
|
55
|
+
end # if
|
|
56
|
+
end # if
|
|
57
|
+
end # self.Callback
|
|
58
|
+
end # EventMachine
|
data/lib/em/channel.rb
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module EventMachine
|
|
2
|
+
# Provides a simple thread-safe way to transfer data between (typically) long running
|
|
3
|
+
# tasks in {EventMachine.defer} and event loop thread.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
#
|
|
7
|
+
# channel = EventMachine::Channel.new
|
|
8
|
+
# sid = channel.subscribe { |msg| p [:got, msg] }
|
|
9
|
+
#
|
|
10
|
+
# channel.push('hello world')
|
|
11
|
+
# channel.unsubscribe(sid)
|
|
12
|
+
#
|
|
13
|
+
#
|
|
14
|
+
class Channel
|
|
15
|
+
def initialize
|
|
16
|
+
@subs = {}
|
|
17
|
+
@uid = 0
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Takes any arguments suitable for EM::Callback() and returns a subscriber
|
|
21
|
+
# id for use when unsubscribing.
|
|
22
|
+
#
|
|
23
|
+
# @return [Integer] Subscribe identifier
|
|
24
|
+
# @see #unsubscribe
|
|
25
|
+
def subscribe(*a, &b)
|
|
26
|
+
name = gen_id
|
|
27
|
+
EM.schedule { @subs[name] = EM::Callback(*a, &b) }
|
|
28
|
+
|
|
29
|
+
name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Removes subscriber from the list.
|
|
33
|
+
#
|
|
34
|
+
# @param [Integer] Subscriber identifier
|
|
35
|
+
# @see #subscribe
|
|
36
|
+
def unsubscribe(name)
|
|
37
|
+
EM.schedule { @subs.delete name }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Add items to the channel, which are pushed out to all subscribers.
|
|
41
|
+
def push(*items)
|
|
42
|
+
items = items.dup
|
|
43
|
+
EM.schedule { items.each { |i| @subs.values.each { |s| s.call i } } }
|
|
44
|
+
end
|
|
45
|
+
alias << push
|
|
46
|
+
|
|
47
|
+
# Fetches one message from the channel.
|
|
48
|
+
def pop(*a, &b)
|
|
49
|
+
EM.schedule {
|
|
50
|
+
name = subscribe do |*args|
|
|
51
|
+
unsubscribe(name)
|
|
52
|
+
EM::Callback(*a, &b).call(*args)
|
|
53
|
+
end
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# @private
|
|
60
|
+
def gen_id
|
|
61
|
+
@uid += 1
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# = EM::Completion
|
|
2
|
+
#
|
|
3
|
+
# A completion is a callback container for various states of completion. In
|
|
4
|
+
# it's most basic form it has a start state and a finish state.
|
|
5
|
+
#
|
|
6
|
+
# This implementation includes some hold-back from the EM::Deferrable
|
|
7
|
+
# interface in order to be compatible - but it has a much cleaner
|
|
8
|
+
# implementation.
|
|
9
|
+
#
|
|
10
|
+
# In general it is preferred that this implementation be used as a state
|
|
11
|
+
# callback container than EM::DefaultDeferrable or other classes including
|
|
12
|
+
# EM::Deferrable. This is because it is generally more sane to keep this level
|
|
13
|
+
# of state in a dedicated state-back container. This generally leads to more
|
|
14
|
+
# malleable interfaces and software designs, as well as eradicating nasty bugs
|
|
15
|
+
# that result from abstraction leakage.
|
|
16
|
+
#
|
|
17
|
+
# == Basic Usage
|
|
18
|
+
#
|
|
19
|
+
# As already mentioned, the basic usage of a Completion is simply for its two
|
|
20
|
+
# final states, :succeeded and :failed.
|
|
21
|
+
#
|
|
22
|
+
# An asynchronous operation will complete at some future point in time, and
|
|
23
|
+
# users often want to react to this event. API authors will want to expose
|
|
24
|
+
# some common interface to react to these events.
|
|
25
|
+
#
|
|
26
|
+
# In the following example, the user wants to know when a short lived
|
|
27
|
+
# connection has completed its exchange with the remote server. The simple
|
|
28
|
+
# protocol just waits for an ack to its message.
|
|
29
|
+
#
|
|
30
|
+
# class Protocol < EM::Connection
|
|
31
|
+
# include EM::P::LineText2
|
|
32
|
+
#
|
|
33
|
+
# def initialize(message, completion)
|
|
34
|
+
# @message, @completion = message, completion
|
|
35
|
+
# @completion.completion { close_connection }
|
|
36
|
+
# @completion.timeout(1, :timeout)
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# def post_init
|
|
40
|
+
# send_data(@message)
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# def receive_line(line)
|
|
44
|
+
# case line
|
|
45
|
+
# when /ACK/i
|
|
46
|
+
# @completion.succeed line
|
|
47
|
+
# when /ERR/i
|
|
48
|
+
# @completion.fail :error, line
|
|
49
|
+
# else
|
|
50
|
+
# @completion.fail :unknown, line
|
|
51
|
+
# end
|
|
52
|
+
# end
|
|
53
|
+
#
|
|
54
|
+
# def unbind
|
|
55
|
+
# @completion.fail :disconnected unless @completion.completed?
|
|
56
|
+
# end
|
|
57
|
+
# end
|
|
58
|
+
#
|
|
59
|
+
# class API
|
|
60
|
+
# attr_reader :host, :port
|
|
61
|
+
#
|
|
62
|
+
# def initialize(host = 'example.org', port = 8000)
|
|
63
|
+
# @host, @port = host, port
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
# def request(message)
|
|
67
|
+
# completion = EM::Deferrable::Completion.new
|
|
68
|
+
# EM.connect(host, port, Protocol, message, completion)
|
|
69
|
+
# completion
|
|
70
|
+
# end
|
|
71
|
+
# end
|
|
72
|
+
#
|
|
73
|
+
# api = API.new
|
|
74
|
+
# completion = api.request('stuff')
|
|
75
|
+
# completion.callback do |line|
|
|
76
|
+
# puts "API responded with: #{line}"
|
|
77
|
+
# end
|
|
78
|
+
# completion.errback do |type, line|
|
|
79
|
+
# case type
|
|
80
|
+
# when :error
|
|
81
|
+
# puts "API error: #{line}"
|
|
82
|
+
# when :unknown
|
|
83
|
+
# puts "API returned unknown response: #{line}"
|
|
84
|
+
# when :disconnected
|
|
85
|
+
# puts "API server disconnected prematurely"
|
|
86
|
+
# when :timeout
|
|
87
|
+
# puts "API server did not respond in a timely fashion"
|
|
88
|
+
# end
|
|
89
|
+
# end
|
|
90
|
+
#
|
|
91
|
+
# == Advanced Usage
|
|
92
|
+
#
|
|
93
|
+
# This completion implementation also supports more state callbacks and
|
|
94
|
+
# arbitrary states (unlike the original Deferrable API). This allows for basic
|
|
95
|
+
# stateful process encapsulation. One might use this to setup state callbacks
|
|
96
|
+
# for various states in an exchange like in the basic usage example, except
|
|
97
|
+
# where the applicaiton could be made to react to "connected" and
|
|
98
|
+
# "disconnected" states additionally.
|
|
99
|
+
#
|
|
100
|
+
# class Protocol < EM::Connection
|
|
101
|
+
# def initialize(completion)
|
|
102
|
+
# @response = []
|
|
103
|
+
# @completion = completion
|
|
104
|
+
# @completion.stateback(:disconnected) do
|
|
105
|
+
# @completion.succeed @response.join
|
|
106
|
+
# end
|
|
107
|
+
# end
|
|
108
|
+
#
|
|
109
|
+
# def connection_completed
|
|
110
|
+
# @host, @port = Socket.unpack_sockaddr_in get_peername
|
|
111
|
+
# @completion.change_state(:connected, @host, @port)
|
|
112
|
+
# send_data("GET http://example.org/ HTTP/1.0\r\n\r\n")
|
|
113
|
+
# end
|
|
114
|
+
#
|
|
115
|
+
# def receive_data(data)
|
|
116
|
+
# @response << data
|
|
117
|
+
# end
|
|
118
|
+
#
|
|
119
|
+
# def unbind
|
|
120
|
+
# @completion.change_state(:disconnected, @host, @port)
|
|
121
|
+
# end
|
|
122
|
+
# end
|
|
123
|
+
#
|
|
124
|
+
# completion = EM::Deferrable::Completion.new
|
|
125
|
+
# completion.stateback(:connected) do |host, port|
|
|
126
|
+
# puts "Connected to #{host}:#{port}"
|
|
127
|
+
# end
|
|
128
|
+
# completion.stateback(:disconnected) do |host, port|
|
|
129
|
+
# puts "Disconnected from #{host}:#{port}"
|
|
130
|
+
# end
|
|
131
|
+
# completion.callback do |response|
|
|
132
|
+
# puts response
|
|
133
|
+
# end
|
|
134
|
+
#
|
|
135
|
+
# EM.connect('example.org', 80, Protocol, completion)
|
|
136
|
+
#
|
|
137
|
+
# == Timeout
|
|
138
|
+
#
|
|
139
|
+
# The Completion also has a timeout. The timeout is global and is not aware of
|
|
140
|
+
# states apart from completion states. The timeout is only engaged if #timeout
|
|
141
|
+
# is called, and it will call fail if it is reached.
|
|
142
|
+
#
|
|
143
|
+
# == Completion states
|
|
144
|
+
#
|
|
145
|
+
# By default there are two completion states, :succeeded and :failed. These
|
|
146
|
+
# states can be modified by subclassing and overrding the #completion_states
|
|
147
|
+
# method. Completion states are special, in that callbacks for all completion
|
|
148
|
+
# states are explcitly cleared when a completion state is entered. This
|
|
149
|
+
# prevents errors that could arise from accidental unterminated timeouts, and
|
|
150
|
+
# other such user errors.
|
|
151
|
+
#
|
|
152
|
+
# == Other notes
|
|
153
|
+
#
|
|
154
|
+
# Several APIs have been carried over from EM::Deferrable for compatibility
|
|
155
|
+
# reasons during a transitionary period. Specifically cancel_errback and
|
|
156
|
+
# cancel_callback are implemented, but their usage is to be strongly
|
|
157
|
+
# discouraged. Due to the already complex nature of reaction systems, dynamic
|
|
158
|
+
# callback deletion only makes the problem much worse. It is always better to
|
|
159
|
+
# add correct conditionals to the callback code, or use more states, than to
|
|
160
|
+
# address such implementaiton issues with conditional callbacks.
|
|
161
|
+
|
|
162
|
+
module EventMachine
|
|
163
|
+
|
|
164
|
+
class Completion
|
|
165
|
+
# This is totally not used (re-implemented), it's here in case people check
|
|
166
|
+
# for kind_of?
|
|
167
|
+
include EventMachine::Deferrable
|
|
168
|
+
|
|
169
|
+
attr_reader :state, :value
|
|
170
|
+
|
|
171
|
+
def initialize
|
|
172
|
+
@state = :unknown
|
|
173
|
+
@callbacks = Hash.new { |h,k| h[k] = [] }
|
|
174
|
+
@value = []
|
|
175
|
+
@timeout_timer = nil
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Enter the :succeeded state, setting the result value if given.
|
|
179
|
+
def succeed(*args)
|
|
180
|
+
change_state(:succeeded, *args)
|
|
181
|
+
end
|
|
182
|
+
# The old EM method:
|
|
183
|
+
alias set_deferred_success succeed
|
|
184
|
+
|
|
185
|
+
# Enter the :failed state, setting the result value if given.
|
|
186
|
+
def fail(*args)
|
|
187
|
+
change_state(:failed, *args)
|
|
188
|
+
end
|
|
189
|
+
# The old EM method:
|
|
190
|
+
alias set_deferred_failure fail
|
|
191
|
+
|
|
192
|
+
# Statebacks are called when you enter (or are in) the named state.
|
|
193
|
+
def stateback(state, *a, &b)
|
|
194
|
+
# The following is quite unfortunate special casing for :completed
|
|
195
|
+
# statebacks, but it's a necessary evil for latent completion
|
|
196
|
+
# definitions.
|
|
197
|
+
|
|
198
|
+
if :completed == state || !completed? || @state == state
|
|
199
|
+
@callbacks[state] << EM::Callback(*a, &b)
|
|
200
|
+
end
|
|
201
|
+
execute_callbacks
|
|
202
|
+
self
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Callbacks are called when you enter (or are in) a :succeeded state.
|
|
206
|
+
def callback(*a, &b)
|
|
207
|
+
stateback(:succeeded, *a, &b)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Errbacks are called when you enter (or are in) a :failed state.
|
|
211
|
+
def errback(*a, &b)
|
|
212
|
+
stateback(:failed, *a, &b)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Completions are called when you enter (or are in) either a :failed or a
|
|
216
|
+
# :succeeded state. They are stored as a special (reserved) state called
|
|
217
|
+
# :completed.
|
|
218
|
+
def completion(*a, &b)
|
|
219
|
+
stateback(:completed, *a, &b)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Enter a new state, setting the result value if given. If the state is one
|
|
223
|
+
# of :succeeded or :failed, then :completed callbacks will also be called.
|
|
224
|
+
def change_state(state, *args)
|
|
225
|
+
@value = args
|
|
226
|
+
@state = state
|
|
227
|
+
|
|
228
|
+
EM.schedule { execute_callbacks }
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# The old EM method:
|
|
232
|
+
alias set_deferred_status change_state
|
|
233
|
+
|
|
234
|
+
# Indicates that we've reached some kind of completion state, by default
|
|
235
|
+
# this is :succeeded or :failed. Due to these semantics, the :completed
|
|
236
|
+
# state is reserved for internal use.
|
|
237
|
+
def completed?
|
|
238
|
+
completion_states.any? { |s| state == s }
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Completion states simply returns a list of completion states, by default
|
|
242
|
+
# this is :succeeded and :failed.
|
|
243
|
+
def completion_states
|
|
244
|
+
[:succeeded, :failed]
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Schedule a time which if passes before we enter a completion state, this
|
|
248
|
+
# deferrable will be failed with the given arguments.
|
|
249
|
+
def timeout(time, *args)
|
|
250
|
+
cancel_timeout
|
|
251
|
+
@timeout_timer = EM::Timer.new(time) do
|
|
252
|
+
fail(*args) unless completed?
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Disable the timeout
|
|
257
|
+
def cancel_timeout
|
|
258
|
+
if @timeout_timer
|
|
259
|
+
@timeout_timer.cancel
|
|
260
|
+
@timeout_timer = nil
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Remove an errback. N.B. Some errbacks cannot be deleted. Usage is NOT
|
|
265
|
+
# recommended, this is an anti-pattern.
|
|
266
|
+
def cancel_errback(*a, &b)
|
|
267
|
+
@callbacks[:failed].delete(EM::Callback(*a, &b))
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Remove a callback. N.B. Some callbacks cannot be deleted. Usage is NOT
|
|
271
|
+
# recommended, this is an anti-pattern.
|
|
272
|
+
def cancel_callback(*a, &b)
|
|
273
|
+
@callbacks[:succeeded].delete(EM::Callback(*a, &b))
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
private
|
|
277
|
+
# Execute all callbacks for the current state. If in a completed state, then
|
|
278
|
+
# call any statebacks associated with the completed state.
|
|
279
|
+
def execute_callbacks
|
|
280
|
+
execute_state_callbacks(state)
|
|
281
|
+
if completed?
|
|
282
|
+
execute_state_callbacks(:completed)
|
|
283
|
+
clear_dead_callbacks
|
|
284
|
+
cancel_timeout
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Iterate all callbacks for a given state, and remove then call them.
|
|
289
|
+
def execute_state_callbacks(state)
|
|
290
|
+
while callback = @callbacks[state].shift
|
|
291
|
+
callback.call(*value)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# If we enter a completion state, clear other completion states after all
|
|
296
|
+
# callback chains are completed. This means that operation specific
|
|
297
|
+
# callbacks can't be dual-called, which is most common user error.
|
|
298
|
+
def clear_dead_callbacks
|
|
299
|
+
completion_states.each do |state|
|
|
300
|
+
@callbacks[state].clear
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|