eventmachine-le 1.1.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. data/.gitignore +21 -0
  2. data/.yardopts +7 -0
  3. data/GNU +281 -0
  4. data/LICENSE +60 -0
  5. data/README.md +80 -0
  6. data/Rakefile +19 -0
  7. data/eventmachine-le.gemspec +42 -0
  8. data/ext/binder.cpp +124 -0
  9. data/ext/binder.h +46 -0
  10. data/ext/cmain.cpp +841 -0
  11. data/ext/ed.cpp +1995 -0
  12. data/ext/ed.h +424 -0
  13. data/ext/em.cpp +2377 -0
  14. data/ext/em.h +243 -0
  15. data/ext/eventmachine.h +126 -0
  16. data/ext/extconf.rb +166 -0
  17. data/ext/fastfilereader/extconf.rb +94 -0
  18. data/ext/fastfilereader/mapper.cpp +214 -0
  19. data/ext/fastfilereader/mapper.h +59 -0
  20. data/ext/fastfilereader/rubymain.cpp +127 -0
  21. data/ext/kb.cpp +79 -0
  22. data/ext/page.cpp +107 -0
  23. data/ext/page.h +51 -0
  24. data/ext/pipe.cpp +347 -0
  25. data/ext/project.h +155 -0
  26. data/ext/rubymain.cpp +1269 -0
  27. data/ext/ssl.cpp +468 -0
  28. data/ext/ssl.h +94 -0
  29. data/lib/em/buftok.rb +110 -0
  30. data/lib/em/callback.rb +58 -0
  31. data/lib/em/channel.rb +64 -0
  32. data/lib/em/completion.rb +304 -0
  33. data/lib/em/connection.rb +728 -0
  34. data/lib/em/deferrable.rb +210 -0
  35. data/lib/em/deferrable/pool.rb +2 -0
  36. data/lib/em/file_watch.rb +73 -0
  37. data/lib/em/future.rb +61 -0
  38. data/lib/em/iterator.rb +313 -0
  39. data/lib/em/messages.rb +66 -0
  40. data/lib/em/pool.rb +151 -0
  41. data/lib/em/process_watch.rb +45 -0
  42. data/lib/em/processes.rb +123 -0
  43. data/lib/em/protocols.rb +37 -0
  44. data/lib/em/protocols/header_and_content.rb +138 -0
  45. data/lib/em/protocols/httpclient.rb +279 -0
  46. data/lib/em/protocols/httpclient2.rb +600 -0
  47. data/lib/em/protocols/line_and_text.rb +125 -0
  48. data/lib/em/protocols/line_protocol.rb +29 -0
  49. data/lib/em/protocols/linetext2.rb +161 -0
  50. data/lib/em/protocols/memcache.rb +331 -0
  51. data/lib/em/protocols/object_protocol.rb +46 -0
  52. data/lib/em/protocols/postgres3.rb +246 -0
  53. data/lib/em/protocols/saslauth.rb +175 -0
  54. data/lib/em/protocols/smtpclient.rb +365 -0
  55. data/lib/em/protocols/smtpserver.rb +663 -0
  56. data/lib/em/protocols/socks4.rb +66 -0
  57. data/lib/em/protocols/stomp.rb +202 -0
  58. data/lib/em/protocols/tcptest.rb +54 -0
  59. data/lib/em/queue.rb +71 -0
  60. data/lib/em/resolver.rb +195 -0
  61. data/lib/em/spawnable.rb +84 -0
  62. data/lib/em/streamer.rb +118 -0
  63. data/lib/em/threaded_resource.rb +90 -0
  64. data/lib/em/tick_loop.rb +85 -0
  65. data/lib/em/timers.rb +106 -0
  66. data/lib/em/version.rb +3 -0
  67. data/lib/eventmachine-le.rb +10 -0
  68. data/lib/eventmachine.rb +1548 -0
  69. data/rakelib/cpp.rake_example +77 -0
  70. data/rakelib/package.rake +98 -0
  71. data/rakelib/test.rake +8 -0
  72. data/tests/client.crt +31 -0
  73. data/tests/client.key +51 -0
  74. data/tests/em_test_helper.rb +143 -0
  75. data/tests/test_attach.rb +148 -0
  76. data/tests/test_basic.rb +294 -0
  77. data/tests/test_channel.rb +62 -0
  78. data/tests/test_completion.rb +177 -0
  79. data/tests/test_connection_count.rb +33 -0
  80. data/tests/test_defer.rb +18 -0
  81. data/tests/test_deferrable.rb +35 -0
  82. data/tests/test_epoll.rb +134 -0
  83. data/tests/test_error_handler.rb +38 -0
  84. data/tests/test_exc.rb +28 -0
  85. data/tests/test_file_watch.rb +65 -0
  86. data/tests/test_futures.rb +170 -0
  87. data/tests/test_get_sock_opt.rb +37 -0
  88. data/tests/test_handler_check.rb +35 -0
  89. data/tests/test_hc.rb +155 -0
  90. data/tests/test_httpclient.rb +190 -0
  91. data/tests/test_httpclient2.rb +128 -0
  92. data/tests/test_inactivity_timeout.rb +54 -0
  93. data/tests/test_ipv4.rb +125 -0
  94. data/tests/test_ipv6.rb +131 -0
  95. data/tests/test_iterator.rb +110 -0
  96. data/tests/test_kb.rb +34 -0
  97. data/tests/test_line_protocol.rb +33 -0
  98. data/tests/test_ltp.rb +138 -0
  99. data/tests/test_ltp2.rb +288 -0
  100. data/tests/test_next_tick.rb +104 -0
  101. data/tests/test_object_protocol.rb +36 -0
  102. data/tests/test_pause.rb +78 -0
  103. data/tests/test_pending_connect_timeout.rb +52 -0
  104. data/tests/test_pool.rb +196 -0
  105. data/tests/test_process_watch.rb +48 -0
  106. data/tests/test_processes.rb +133 -0
  107. data/tests/test_proxy_connection.rb +168 -0
  108. data/tests/test_pure.rb +88 -0
  109. data/tests/test_queue.rb +50 -0
  110. data/tests/test_resolver.rb +55 -0
  111. data/tests/test_running.rb +14 -0
  112. data/tests/test_sasl.rb +47 -0
  113. data/tests/test_send_file.rb +217 -0
  114. data/tests/test_servers.rb +33 -0
  115. data/tests/test_set_sock_opt.rb +41 -0
  116. data/tests/test_shutdown_hooks.rb +23 -0
  117. data/tests/test_smtpclient.rb +55 -0
  118. data/tests/test_smtpserver.rb +120 -0
  119. data/tests/test_spawn.rb +293 -0
  120. data/tests/test_ssl_args.rb +78 -0
  121. data/tests/test_ssl_methods.rb +48 -0
  122. data/tests/test_ssl_verify.rb +82 -0
  123. data/tests/test_threaded_resource.rb +55 -0
  124. data/tests/test_tick_loop.rb +59 -0
  125. data/tests/test_timers.rb +180 -0
  126. data/tests/test_ud.rb +8 -0
  127. data/tests/test_udp46.rb +53 -0
  128. data/tests/test_unbind_reason.rb +48 -0
  129. metadata +390 -0
data/ext/ssl.h ADDED
@@ -0,0 +1,94 @@
1
+ /*****************************************************************************
2
+
3
+ $Id$
4
+
5
+ File: ssl.h
6
+ Date: 30Apr06
7
+
8
+ Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
9
+ Gmail: blackhedd
10
+
11
+ This program is free software; you can redistribute it and/or modify
12
+ it under the terms of either: 1) the GNU General Public License
13
+ as published by the Free Software Foundation; either version 2 of the
14
+ License, or (at your option) any later version; or 2) Ruby's License.
15
+
16
+ See the file COPYING for complete licensing information.
17
+
18
+ *****************************************************************************/
19
+
20
+
21
+ #ifndef __SslBox__H_
22
+ #define __SslBox__H_
23
+
24
+
25
+
26
+
27
+ #ifdef WITH_SSL
28
+
29
+ /******************
30
+ class SslContext_t
31
+ ******************/
32
+
33
+ class SslContext_t
34
+ {
35
+ public:
36
+ SslContext_t (bool is_server, const string &privkeyfile, const string &certchainfile);
37
+ virtual ~SslContext_t();
38
+
39
+ private:
40
+ static bool bLibraryInitialized;
41
+
42
+ private:
43
+ bool bIsServer;
44
+ SSL_CTX *pCtx;
45
+
46
+ EVP_PKEY *PrivateKey;
47
+ X509 *Certificate;
48
+
49
+ friend class SslBox_t;
50
+ };
51
+
52
+
53
+ /**************
54
+ class SslBox_t
55
+ **************/
56
+
57
+ class SslBox_t
58
+ {
59
+ public:
60
+ SslBox_t (bool is_server, const string &privkeyfile, const string &certchainfile, bool verify_peer, const unsigned long binding);
61
+ virtual ~SslBox_t();
62
+
63
+ int PutPlaintext (const char*, int);
64
+ int GetPlaintext (char*, int);
65
+
66
+ bool PutCiphertext (const char*, int);
67
+ bool CanGetCiphertext();
68
+ int GetCiphertext (char*, int);
69
+ bool IsHandshakeCompleted() {return bHandshakeCompleted;}
70
+
71
+ X509 *GetPeerCert();
72
+
73
+ void Shutdown();
74
+
75
+ protected:
76
+ SslContext_t *Context;
77
+
78
+ bool bIsServer;
79
+ bool bHandshakeCompleted;
80
+ bool bVerifyPeer;
81
+ SSL *pSSL;
82
+ BIO *pbioRead;
83
+ BIO *pbioWrite;
84
+
85
+ PageList OutboundQ;
86
+ };
87
+
88
+ extern "C" int ssl_verify_wrapper(int, X509_STORE_CTX*);
89
+
90
+ #endif // WITH_SSL
91
+
92
+
93
+ #endif // __SslBox__H_
94
+
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
@@ -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