wj_eventmachine 1.3.0.dev.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 (180) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +179 -0
  3. data/GNU +281 -0
  4. data/LICENSE +60 -0
  5. data/README.md +110 -0
  6. data/docs/DocumentationGuidesIndex.md +27 -0
  7. data/docs/GettingStarted.md +520 -0
  8. data/docs/old/ChangeLog +211 -0
  9. data/docs/old/DEFERRABLES +246 -0
  10. data/docs/old/EPOLL +141 -0
  11. data/docs/old/INSTALL +13 -0
  12. data/docs/old/KEYBOARD +42 -0
  13. data/docs/old/LEGAL +25 -0
  14. data/docs/old/LIGHTWEIGHT_CONCURRENCY +130 -0
  15. data/docs/old/PURE_RUBY +75 -0
  16. data/docs/old/RELEASE_NOTES +94 -0
  17. data/docs/old/SMTP +4 -0
  18. data/docs/old/SPAWNED_PROCESSES +148 -0
  19. data/docs/old/TODO +8 -0
  20. data/examples/guides/getting_started/01_eventmachine_echo_server.rb +18 -0
  21. data/examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rb +22 -0
  22. data/examples/guides/getting_started/03_simple_chat_server.rb +149 -0
  23. data/examples/guides/getting_started/04_simple_chat_server_step_one.rb +27 -0
  24. data/examples/guides/getting_started/05_simple_chat_server_step_two.rb +43 -0
  25. data/examples/guides/getting_started/06_simple_chat_server_step_three.rb +98 -0
  26. data/examples/guides/getting_started/07_simple_chat_server_step_four.rb +121 -0
  27. data/examples/guides/getting_started/08_simple_chat_server_step_five.rb +141 -0
  28. data/examples/old/ex_channel.rb +43 -0
  29. data/examples/old/ex_queue.rb +2 -0
  30. data/examples/old/ex_tick_loop_array.rb +15 -0
  31. data/examples/old/ex_tick_loop_counter.rb +32 -0
  32. data/examples/old/helper.rb +2 -0
  33. data/ext/binder.cpp +124 -0
  34. data/ext/binder.h +52 -0
  35. data/ext/cmain.cpp +1046 -0
  36. data/ext/ed.cpp +2238 -0
  37. data/ext/ed.h +460 -0
  38. data/ext/em.cpp +2378 -0
  39. data/ext/em.h +266 -0
  40. data/ext/eventmachine.h +152 -0
  41. data/ext/extconf.rb +285 -0
  42. data/ext/fastfilereader/extconf.rb +120 -0
  43. data/ext/fastfilereader/mapper.cpp +214 -0
  44. data/ext/fastfilereader/mapper.h +59 -0
  45. data/ext/fastfilereader/rubymain.cpp +126 -0
  46. data/ext/kb.cpp +79 -0
  47. data/ext/page.cpp +107 -0
  48. data/ext/page.h +51 -0
  49. data/ext/pipe.cpp +354 -0
  50. data/ext/project.h +174 -0
  51. data/ext/rubymain.cpp +1610 -0
  52. data/ext/ssl.cpp +627 -0
  53. data/ext/ssl.h +103 -0
  54. data/ext/wait_for_single_fd.h +36 -0
  55. data/java/.classpath +8 -0
  56. data/java/.project +17 -0
  57. data/java/src/com/rubyeventmachine/EmReactor.java +625 -0
  58. data/java/src/com/rubyeventmachine/EmReactorException.java +40 -0
  59. data/java/src/com/rubyeventmachine/EmReactorInterface.java +70 -0
  60. data/java/src/com/rubyeventmachine/EventableChannel.java +72 -0
  61. data/java/src/com/rubyeventmachine/EventableDatagramChannel.java +201 -0
  62. data/java/src/com/rubyeventmachine/EventableSocketChannel.java +415 -0
  63. data/java/src/com/rubyeventmachine/NullEmReactor.java +157 -0
  64. data/java/src/com/rubyeventmachine/NullEventableChannel.java +81 -0
  65. data/lib/em/buftok.rb +59 -0
  66. data/lib/em/callback.rb +58 -0
  67. data/lib/em/channel.rb +69 -0
  68. data/lib/em/completion.rb +307 -0
  69. data/lib/em/connection.rb +776 -0
  70. data/lib/em/deferrable.rb +210 -0
  71. data/lib/em/deferrable/pool.rb +2 -0
  72. data/lib/em/file_watch.rb +73 -0
  73. data/lib/em/future.rb +61 -0
  74. data/lib/em/io_streamer.rb +68 -0
  75. data/lib/em/iterator.rb +252 -0
  76. data/lib/em/messages.rb +66 -0
  77. data/lib/em/pool.rb +151 -0
  78. data/lib/em/process_watch.rb +45 -0
  79. data/lib/em/processes.rb +123 -0
  80. data/lib/em/protocols.rb +37 -0
  81. data/lib/em/protocols/header_and_content.rb +138 -0
  82. data/lib/em/protocols/httpclient.rb +303 -0
  83. data/lib/em/protocols/httpclient2.rb +602 -0
  84. data/lib/em/protocols/line_and_text.rb +125 -0
  85. data/lib/em/protocols/line_protocol.rb +33 -0
  86. data/lib/em/protocols/linetext2.rb +179 -0
  87. data/lib/em/protocols/memcache.rb +331 -0
  88. data/lib/em/protocols/object_protocol.rb +46 -0
  89. data/lib/em/protocols/postgres3.rb +246 -0
  90. data/lib/em/protocols/saslauth.rb +175 -0
  91. data/lib/em/protocols/smtpclient.rb +394 -0
  92. data/lib/em/protocols/smtpserver.rb +666 -0
  93. data/lib/em/protocols/socks4.rb +66 -0
  94. data/lib/em/protocols/stomp.rb +205 -0
  95. data/lib/em/protocols/tcptest.rb +54 -0
  96. data/lib/em/pure_ruby.rb +1299 -0
  97. data/lib/em/queue.rb +80 -0
  98. data/lib/em/resolver.rb +232 -0
  99. data/lib/em/spawnable.rb +84 -0
  100. data/lib/em/streamer.rb +118 -0
  101. data/lib/em/threaded_resource.rb +90 -0
  102. data/lib/em/tick_loop.rb +85 -0
  103. data/lib/em/timers.rb +61 -0
  104. data/lib/em/version.rb +3 -0
  105. data/lib/eventmachine.rb +1602 -0
  106. data/lib/jeventmachine.rb +318 -0
  107. data/rakelib/package.rake +120 -0
  108. data/rakelib/test.rake +6 -0
  109. data/rakelib/test_pure.rake +11 -0
  110. data/tests/client.crt +31 -0
  111. data/tests/client.key +51 -0
  112. data/tests/dhparam.pem +13 -0
  113. data/tests/em_ssl_handlers.rb +153 -0
  114. data/tests/em_test_helper.rb +198 -0
  115. data/tests/jruby/test_jeventmachine.rb +38 -0
  116. data/tests/test_attach.rb +199 -0
  117. data/tests/test_basic.rb +321 -0
  118. data/tests/test_channel.rb +75 -0
  119. data/tests/test_completion.rb +178 -0
  120. data/tests/test_connection_count.rb +83 -0
  121. data/tests/test_connection_write.rb +35 -0
  122. data/tests/test_defer.rb +35 -0
  123. data/tests/test_deferrable.rb +35 -0
  124. data/tests/test_epoll.rb +141 -0
  125. data/tests/test_error_handler.rb +38 -0
  126. data/tests/test_exc.rb +37 -0
  127. data/tests/test_file_watch.rb +86 -0
  128. data/tests/test_fork.rb +75 -0
  129. data/tests/test_futures.rb +170 -0
  130. data/tests/test_handler_check.rb +35 -0
  131. data/tests/test_hc.rb +155 -0
  132. data/tests/test_httpclient.rb +238 -0
  133. data/tests/test_httpclient2.rb +132 -0
  134. data/tests/test_idle_connection.rb +31 -0
  135. data/tests/test_inactivity_timeout.rb +102 -0
  136. data/tests/test_io_streamer.rb +47 -0
  137. data/tests/test_ipv4.rb +96 -0
  138. data/tests/test_ipv6.rb +107 -0
  139. data/tests/test_iterator.rb +122 -0
  140. data/tests/test_kb.rb +28 -0
  141. data/tests/test_keepalive.rb +113 -0
  142. data/tests/test_line_protocol.rb +33 -0
  143. data/tests/test_ltp.rb +155 -0
  144. data/tests/test_ltp2.rb +332 -0
  145. data/tests/test_many_fds.rb +21 -0
  146. data/tests/test_next_tick.rb +104 -0
  147. data/tests/test_object_protocol.rb +36 -0
  148. data/tests/test_pause.rb +109 -0
  149. data/tests/test_pending_connect_timeout.rb +52 -0
  150. data/tests/test_pool.rb +196 -0
  151. data/tests/test_process_watch.rb +50 -0
  152. data/tests/test_processes.rb +128 -0
  153. data/tests/test_proxy_connection.rb +180 -0
  154. data/tests/test_pure.rb +156 -0
  155. data/tests/test_queue.rb +64 -0
  156. data/tests/test_resolver.rb +129 -0
  157. data/tests/test_running.rb +14 -0
  158. data/tests/test_sasl.rb +46 -0
  159. data/tests/test_send_file.rb +217 -0
  160. data/tests/test_servers.rb +32 -0
  161. data/tests/test_shutdown_hooks.rb +23 -0
  162. data/tests/test_smtpclient.rb +75 -0
  163. data/tests/test_smtpserver.rb +90 -0
  164. data/tests/test_sock_opt.rb +53 -0
  165. data/tests/test_spawn.rb +290 -0
  166. data/tests/test_ssl_args.rb +41 -0
  167. data/tests/test_ssl_dhparam.rb +57 -0
  168. data/tests/test_ssl_ecdh_curve.rb +57 -0
  169. data/tests/test_ssl_extensions.rb +24 -0
  170. data/tests/test_ssl_methods.rb +31 -0
  171. data/tests/test_ssl_protocols.rb +190 -0
  172. data/tests/test_ssl_verify.rb +52 -0
  173. data/tests/test_stomp.rb +38 -0
  174. data/tests/test_system.rb +46 -0
  175. data/tests/test_threaded_resource.rb +68 -0
  176. data/tests/test_tick_loop.rb +58 -0
  177. data/tests/test_timers.rb +150 -0
  178. data/tests/test_ud.rb +8 -0
  179. data/tests/test_unbind_reason.rb +40 -0
  180. metadata +384 -0
@@ -0,0 +1,157 @@
1
+ package com.rubyeventmachine;
2
+
3
+ import java.io.IOException;
4
+ import java.net.InetSocketAddress;
5
+ import java.net.SocketAddress;
6
+ import java.nio.ByteBuffer;
7
+ import java.nio.channels.SocketChannel;
8
+ import java.security.KeyManagementException;
9
+ import java.security.NoSuchAlgorithmException;
10
+
11
+ public class NullEmReactor implements EmReactorInterface
12
+ {
13
+ public void eventCallback(long sig, int eventType, ByteBuffer data, long data2)
14
+ {
15
+
16
+ }
17
+
18
+ public void eventCallback(long sig, int eventType, ByteBuffer data)
19
+ {
20
+
21
+ }
22
+
23
+ public void run()
24
+ {
25
+
26
+ }
27
+
28
+ public void stop()
29
+ {
30
+
31
+ }
32
+
33
+ public long installOneshotTimer(long milliseconds)
34
+ {
35
+ return 0;
36
+ }
37
+
38
+ public long startTcpServer(SocketAddress sa) throws EmReactorException
39
+ {
40
+ return 0;
41
+ }
42
+
43
+ public long startTcpServer(String address, int port) throws EmReactorException
44
+ {
45
+ return 0;
46
+ }
47
+
48
+ public void stopTcpServer(long signature) throws IOException
49
+ {
50
+
51
+ }
52
+
53
+ public long openUdpSocket(InetSocketAddress address) throws IOException
54
+ {
55
+ return 0;
56
+ }
57
+
58
+ public long openUdpSocket(String address, int port) throws IOException
59
+ {
60
+ return 0;
61
+ }
62
+
63
+ public void sendData(long sig, ByteBuffer bb) throws IOException
64
+ {
65
+
66
+ }
67
+
68
+ public void sendData(long sig, byte[] data) throws IOException
69
+ {
70
+
71
+ }
72
+
73
+ public void setCommInactivityTimeout(long sig, long mills)
74
+ {
75
+
76
+ }
77
+
78
+ public void sendDatagram(long sig, byte[] data, int length, String recipAddress, int recipPort)
79
+ {
80
+
81
+ }
82
+
83
+ public void sendDatagram(long sig, ByteBuffer bb, String recipAddress, int recipPort)
84
+ {
85
+
86
+ }
87
+
88
+ public long connectTcpServer(String address, int port)
89
+ {
90
+ return 0;
91
+ }
92
+
93
+ public long connectTcpServer(String bindAddr, int bindPort, String address, int port)
94
+ {
95
+ return 0;
96
+ }
97
+
98
+ public void closeConnection(long sig, boolean afterWriting)
99
+ {
100
+
101
+ }
102
+
103
+ public void signalLoopbreak()
104
+ {
105
+
106
+ }
107
+
108
+ public void startTls(long sig) throws NoSuchAlgorithmException, KeyManagementException
109
+ {
110
+
111
+ }
112
+
113
+ public void setTimerQuantum(int mills)
114
+ {
115
+
116
+ }
117
+
118
+ public Object[] getPeerName(long sig)
119
+ {
120
+ return new Object[0];
121
+ }
122
+
123
+ public long attachChannel(SocketChannel sc, boolean watch_mode)
124
+ {
125
+ return 0;
126
+ }
127
+
128
+ public SocketChannel detachChannel(long sig)
129
+ {
130
+ return null;
131
+ }
132
+
133
+ public void setNotifyReadable(long sig, boolean mode)
134
+ {
135
+
136
+ }
137
+
138
+ public void setNotifyWritable(long sig, boolean mode)
139
+ {
140
+
141
+ }
142
+
143
+ public boolean isNotifyReadable(long sig)
144
+ {
145
+ return false;
146
+ }
147
+
148
+ public boolean isNotifyWritable(long sig)
149
+ {
150
+ return false;
151
+ }
152
+
153
+ public int getConnectionCount()
154
+ {
155
+ return 0;
156
+ }
157
+ }
@@ -0,0 +1,81 @@
1
+ package com.rubyeventmachine;
2
+
3
+ import java.io.IOException;
4
+ import java.nio.ByteBuffer;
5
+ import java.nio.channels.ClosedChannelException;
6
+
7
+ public class NullEventableChannel implements EventableChannel
8
+ {
9
+ public void scheduleOutboundData(ByteBuffer bb)
10
+ {
11
+ }
12
+
13
+ public void scheduleOutboundDatagram(ByteBuffer bb, String recipAddress, int recipPort)
14
+ {
15
+ }
16
+
17
+ public boolean scheduleClose(boolean afterWriting)
18
+ {
19
+ return false;
20
+ }
21
+
22
+ public void startTls()
23
+ {
24
+ }
25
+
26
+ public long getBinding()
27
+ {
28
+ return 0;
29
+ }
30
+
31
+ public void readInboundData(ByteBuffer dst) throws IOException
32
+ {
33
+ }
34
+
35
+ public void register() throws ClosedChannelException
36
+ {
37
+ }
38
+
39
+ public void close()
40
+ {
41
+ }
42
+
43
+ public boolean writeOutboundData() throws IOException
44
+ {
45
+ return false;
46
+ }
47
+
48
+ public void setCommInactivityTimeout(long seconds)
49
+ {
50
+ }
51
+
52
+ public Object[] getPeerName()
53
+ {
54
+ return new Object[0];
55
+ }
56
+
57
+ public Object[] getSockName()
58
+ {
59
+ return new Object[0];
60
+ }
61
+
62
+ public boolean isWatchOnly()
63
+ {
64
+ return false;
65
+ }
66
+
67
+ public boolean isNotifyReadable()
68
+ {
69
+ return false;
70
+ }
71
+
72
+ public boolean isNotifyWritable()
73
+ {
74
+ return false;
75
+ }
76
+
77
+ public long getOutboundDataSize ()
78
+ {
79
+ return 0;
80
+ }
81
+ }
@@ -0,0 +1,59 @@
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. In this respect it's ideally paired with
5
+ # something like EventMachine (http://rubyeventmachine.com/).
6
+ class BufferedTokenizer
7
+ # New BufferedTokenizers will operate on lines delimited by a delimiter,
8
+ # which is by default the global input delimiter $/ ("\n").
9
+ #
10
+ # The input buffer is stored as an array. This is by far the most efficient
11
+ # approach given language constraints (in C a linked list would be a more
12
+ # appropriate data structure). Segments of input data are stored in a list
13
+ # which is only joined when a token is reached, substantially reducing the
14
+ # number of objects required for the operation.
15
+ def initialize(delimiter = $/)
16
+ @delimiter = delimiter
17
+ @input = []
18
+ @tail = ''
19
+ @trim = @delimiter.length - 1
20
+ end
21
+
22
+ # Extract takes an arbitrary string of input data and returns an array of
23
+ # tokenized entities, provided there were any available to extract. This
24
+ # makes for easy processing of datagrams using a pattern like:
25
+ #
26
+ # tokenizer.extract(data).map { |entity| Decode(entity) }.each do ...
27
+ #
28
+ # Using -1 makes split to return "" if the token is at the end of
29
+ # the string, meaning the last element is the start of the next chunk.
30
+ def extract(data)
31
+ if @trim > 0
32
+ tail_end = @tail.slice!(-@trim, @trim) # returns nil if string is too short
33
+ data = tail_end + data if tail_end
34
+ end
35
+
36
+ @input << @tail
37
+ entities = data.split(@delimiter, -1)
38
+ @tail = entities.shift
39
+
40
+ unless entities.empty?
41
+ @input << @tail
42
+ entities.unshift @input.join
43
+ @input.clear
44
+ @tail = entities.pop
45
+ end
46
+
47
+ entities
48
+ end
49
+
50
+ # Flush the contents of the input buffer, i.e. return the input buffer even though
51
+ # a token has not yet been encountered
52
+ def flush
53
+ @input << @tail
54
+ buffer = @input.join
55
+ @input.clear
56
+ @tail = "" # @tail.clear is slightly faster, but not supported on 1.8.7
57
+ buffer
58
+ end
59
+ end
@@ -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
@@ -0,0 +1,69 @@
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
+ # Return the number of current subscribers.
21
+ def num_subscribers
22
+ return @subs.size
23
+ end
24
+
25
+ # Takes any arguments suitable for EM::Callback() and returns a subscriber
26
+ # id for use when unsubscribing.
27
+ #
28
+ # @return [Integer] Subscribe identifier
29
+ # @see #unsubscribe
30
+ def subscribe(*a, &b)
31
+ name = gen_id
32
+ EM.schedule { @subs[name] = EM::Callback(*a, &b) }
33
+
34
+ name
35
+ end
36
+
37
+ # Removes subscriber from the list.
38
+ #
39
+ # @param [Integer] Subscriber identifier
40
+ # @see #subscribe
41
+ def unsubscribe(name)
42
+ EM.schedule { @subs.delete name }
43
+ end
44
+
45
+ # Add items to the channel, which are pushed out to all subscribers.
46
+ def push(*items)
47
+ items = items.dup
48
+ EM.schedule { items.each { |i| @subs.values.each { |s| s.call i } } }
49
+ end
50
+ alias << push
51
+
52
+ # Fetches one message from the channel.
53
+ def pop(*a, &b)
54
+ EM.schedule {
55
+ name = subscribe do |*args|
56
+ unsubscribe(name)
57
+ EM::Callback(*a, &b).call(*args)
58
+ end
59
+ }
60
+ end
61
+
62
+ private
63
+
64
+ # @private
65
+ def gen_id
66
+ @uid += 1
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,307 @@
1
+ module EventMachine
2
+
3
+ ##
4
+ # An EM::Completion instance is a callback container for various states of
5
+ # completion. In its most basic form it has a start state and a finish state.
6
+ #
7
+ # This implementation includes some hold-back from the EM::Deferrable
8
+ # interface in order to be compatible - but it has a much cleaner
9
+ # implementation.
10
+ #
11
+ # In general it is preferred that this implementation be used as a state
12
+ # callback container than EM::DefaultDeferrable or other classes including
13
+ # EM::Deferrable. This is because it is generally more sane to keep this level
14
+ # of state in a dedicated state-back container. This generally leads to more
15
+ # malleable interfaces and software designs, as well as eradicating nasty bugs
16
+ # that result from abstraction leakage.
17
+ #
18
+ # == Basic Usage
19
+ #
20
+ # As already mentioned, the basic usage of a Completion is simply for its two
21
+ # final states, :succeeded and :failed.
22
+ #
23
+ # An asynchronous operation will complete at some future point in time, and
24
+ # users often want to react to this event. API authors will want to expose
25
+ # some common interface to react to these events.
26
+ #
27
+ # In the following example, the user wants to know when a short lived
28
+ # connection has completed its exchange with the remote server. The simple
29
+ # protocol just waits for an ack to its message.
30
+ #
31
+ # ```ruby
32
+ # class Protocol < EM::Connection
33
+ # include EM::P::LineText2
34
+ #
35
+ # def initialize(message, completion)
36
+ # @message, @completion = message, completion
37
+ # @completion.completion { close_connection }
38
+ # @completion.timeout(1, :timeout)
39
+ # end
40
+ #
41
+ # def post_init
42
+ # send_data(@message)
43
+ # end
44
+ #
45
+ # def receive_line(line)
46
+ # case line
47
+ # when /ACK/i
48
+ # @completion.succeed line
49
+ # when /ERR/i
50
+ # @completion.fail :error, line
51
+ # else
52
+ # @completion.fail :unknown, line
53
+ # end
54
+ # end
55
+ #
56
+ # def unbind
57
+ # @completion.fail :disconnected unless @completion.completed?
58
+ # end
59
+ # end
60
+ #
61
+ # class API
62
+ # attr_reader :host, :port
63
+ #
64
+ # def initialize(host = 'example.org', port = 8000)
65
+ # @host, @port = host, port
66
+ # end
67
+ #
68
+ # def request(message)
69
+ # completion = EM::Deferrable::Completion.new
70
+ # EM.connect(host, port, Protocol, message, completion)
71
+ # completion
72
+ # end
73
+ # end
74
+ #
75
+ # api = API.new
76
+ # completion = api.request('stuff')
77
+ # completion.callback do |line|
78
+ # puts "API responded with: #{line}"
79
+ # end
80
+ # completion.errback do |type, line|
81
+ # case type
82
+ # when :error
83
+ # puts "API error: #{line}"
84
+ # when :unknown
85
+ # puts "API returned unknown response: #{line}"
86
+ # when :disconnected
87
+ # puts "API server disconnected prematurely"
88
+ # when :timeout
89
+ # puts "API server did not respond in a timely fashion"
90
+ # end
91
+ # end
92
+ # ```
93
+ #
94
+ # == Advanced Usage
95
+ #
96
+ # This completion implementation also supports more state callbacks and
97
+ # arbitrary states (unlike the original Deferrable API). This allows for basic
98
+ # stateful process encapsulation. One might use this to setup state callbacks
99
+ # for various states in an exchange like in the basic usage example, except
100
+ # where the applicaiton could be made to react to "connected" and
101
+ # "disconnected" states additionally.
102
+ #
103
+ # ```ruby
104
+ # class Protocol < EM::Connection
105
+ # def initialize(completion)
106
+ # @response = []
107
+ # @completion = completion
108
+ # @completion.stateback(:disconnected) do
109
+ # @completion.succeed @response.join
110
+ # end
111
+ # end
112
+ #
113
+ # def connection_completed
114
+ # @host, @port = Socket.unpack_sockaddr_in get_peername
115
+ # @completion.change_state(:connected, @host, @port)
116
+ # send_data("GET http://example.org/ HTTP/1.0\r\n\r\n")
117
+ # end
118
+ #
119
+ # def receive_data(data)
120
+ # @response << data
121
+ # end
122
+ #
123
+ # def unbind
124
+ # @completion.change_state(:disconnected, @host, @port)
125
+ # end
126
+ # end
127
+ #
128
+ # completion = EM::Deferrable::Completion.new
129
+ # completion.stateback(:connected) do |host, port|
130
+ # puts "Connected to #{host}:#{port}"
131
+ # end
132
+ # completion.stateback(:disconnected) do |host, port|
133
+ # puts "Disconnected from #{host}:#{port}"
134
+ # end
135
+ # completion.callback do |response|
136
+ # puts response
137
+ # end
138
+ #
139
+ # EM.connect('example.org', 80, Protocol, completion)
140
+ # ```
141
+ #
142
+ # == Timeout
143
+ #
144
+ # The Completion also has a timeout. The timeout is global and is not aware of
145
+ # states apart from completion states. The timeout is only engaged if #timeout
146
+ # is called, and it will call fail if it is reached.
147
+ #
148
+ # == Completion states
149
+ #
150
+ # By default there are two completion states, :succeeded and :failed. These
151
+ # states can be modified by subclassing and overrding the #completion_states
152
+ # method. Completion states are special, in that callbacks for all completion
153
+ # states are explcitly cleared when a completion state is entered. This
154
+ # prevents errors that could arise from accidental unterminated timeouts, and
155
+ # other such user errors.
156
+ #
157
+ # == Other notes
158
+ #
159
+ # Several APIs have been carried over from EM::Deferrable for compatibility
160
+ # reasons during a transitionary period. Specifically cancel_errback and
161
+ # cancel_callback are implemented, but their usage is to be strongly
162
+ # discouraged. Due to the already complex nature of reaction systems, dynamic
163
+ # callback deletion only makes the problem much worse. It is always better to
164
+ # add correct conditionals to the callback code, or use more states, than to
165
+ # address such implementaiton issues with conditional callbacks.
166
+
167
+ class Completion
168
+ # This is totally not used (re-implemented), it's here in case people check
169
+ # for kind_of?
170
+ include EventMachine::Deferrable
171
+
172
+ attr_reader :state, :value
173
+
174
+ def initialize
175
+ @state = :unknown
176
+ @callbacks = Hash.new { |h,k| h[k] = [] }
177
+ @value = []
178
+ @timeout_timer = nil
179
+ end
180
+
181
+ # Enter the :succeeded state, setting the result value if given.
182
+ def succeed(*args)
183
+ change_state(:succeeded, *args)
184
+ end
185
+ # The old EM method:
186
+ alias set_deferred_success succeed
187
+
188
+ # Enter the :failed state, setting the result value if given.
189
+ def fail(*args)
190
+ change_state(:failed, *args)
191
+ end
192
+ # The old EM method:
193
+ alias set_deferred_failure fail
194
+
195
+ # Statebacks are called when you enter (or are in) the named state.
196
+ def stateback(state, *a, &b)
197
+ # The following is quite unfortunate special casing for :completed
198
+ # statebacks, but it's a necessary evil for latent completion
199
+ # definitions.
200
+
201
+ if :completed == state || !completed? || @state == state
202
+ @callbacks[state] << EM::Callback(*a, &b)
203
+ end
204
+ execute_callbacks
205
+ self
206
+ end
207
+
208
+ # Callbacks are called when you enter (or are in) a :succeeded state.
209
+ def callback(*a, &b)
210
+ stateback(:succeeded, *a, &b)
211
+ end
212
+
213
+ # Errbacks are called when you enter (or are in) a :failed state.
214
+ def errback(*a, &b)
215
+ stateback(:failed, *a, &b)
216
+ end
217
+
218
+ # Completions are called when you enter (or are in) either a :failed or a
219
+ # :succeeded state. They are stored as a special (reserved) state called
220
+ # :completed.
221
+ def completion(*a, &b)
222
+ stateback(:completed, *a, &b)
223
+ end
224
+
225
+ # Enter a new state, setting the result value if given. If the state is one
226
+ # of :succeeded or :failed, then :completed callbacks will also be called.
227
+ def change_state(state, *args)
228
+ @value = args
229
+ @state = state
230
+
231
+ EM.schedule { execute_callbacks }
232
+ end
233
+
234
+ # The old EM method:
235
+ alias set_deferred_status change_state
236
+
237
+ # Indicates that we've reached some kind of completion state, by default
238
+ # this is :succeeded or :failed. Due to these semantics, the :completed
239
+ # state is reserved for internal use.
240
+ def completed?
241
+ completion_states.any? { |s| state == s }
242
+ end
243
+
244
+ # Completion states simply returns a list of completion states, by default
245
+ # this is :succeeded and :failed.
246
+ def completion_states
247
+ [:succeeded, :failed]
248
+ end
249
+
250
+ # Schedule a time which if passes before we enter a completion state, this
251
+ # deferrable will be failed with the given arguments.
252
+ def timeout(time, *args)
253
+ cancel_timeout
254
+ @timeout_timer = EM::Timer.new(time) do
255
+ fail(*args) unless completed?
256
+ end
257
+ end
258
+
259
+ # Disable the timeout
260
+ def cancel_timeout
261
+ if @timeout_timer
262
+ @timeout_timer.cancel
263
+ @timeout_timer = nil
264
+ end
265
+ end
266
+
267
+ # Remove an errback. N.B. Some errbacks cannot be deleted. Usage is NOT
268
+ # recommended, this is an anti-pattern.
269
+ def cancel_errback(*a, &b)
270
+ @callbacks[:failed].delete(EM::Callback(*a, &b))
271
+ end
272
+
273
+ # Remove a callback. N.B. Some callbacks cannot be deleted. Usage is NOT
274
+ # recommended, this is an anti-pattern.
275
+ def cancel_callback(*a, &b)
276
+ @callbacks[:succeeded].delete(EM::Callback(*a, &b))
277
+ end
278
+
279
+ private
280
+ # Execute all callbacks for the current state. If in a completed state, then
281
+ # call any statebacks associated with the completed state.
282
+ def execute_callbacks
283
+ execute_state_callbacks(state)
284
+ if completed?
285
+ execute_state_callbacks(:completed)
286
+ clear_dead_callbacks
287
+ cancel_timeout
288
+ end
289
+ end
290
+
291
+ # Iterate all callbacks for a given state, and remove then call them.
292
+ def execute_state_callbacks(state)
293
+ while callback = @callbacks[state].shift
294
+ callback.call(*value)
295
+ end
296
+ end
297
+
298
+ # If we enter a completion state, clear other completion states after all
299
+ # callback chains are completed. This means that operation specific
300
+ # callbacks can't be dual-called, which is most common user error.
301
+ def clear_dead_callbacks
302
+ completion_states.each do |state|
303
+ @callbacks[state].clear
304
+ end
305
+ end
306
+ end
307
+ end