wj_eventmachine 1.3.0.dev.1

Sign up to get free protection for your applications and to get access to all the features.
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