seekingalpha_thread 1.0.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.
@@ -0,0 +1,197 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'thread'
12
+
13
+ # An every runs the given block every given seconds, you can then get the
14
+ # value, check if the value is old and you can check how many seconds
15
+ # until the next run.
16
+ class Thread::Every
17
+ Cancel = Class.new(Exception)
18
+ Restart = Class.new(Exception)
19
+
20
+ # Create an every with the given seconds and block.
21
+ def initialize(every, &block)
22
+ raise ArgumentError, 'no block given' unless block
23
+
24
+ @every = every
25
+ @old = true
26
+ @mutex = Mutex.new
27
+ @thread = Thread.new {
28
+ loop do
29
+ begin
30
+ value = block.call
31
+
32
+ @mutex.synchronize {
33
+ @at = Time.now
34
+ @value = value
35
+ @old = false
36
+ @exception = nil
37
+ }
38
+ rescue Restart
39
+ next
40
+ rescue Exception => e
41
+ @mutex.synchronize {
42
+ @at = Time.now
43
+ @exception = e
44
+ }
45
+
46
+ break if e.is_a? Cancel
47
+ end
48
+
49
+ cond.broadcast if cond?
50
+
51
+ begin
52
+ sleep @every
53
+ rescue Restart
54
+ next
55
+ rescue Cancel => e
56
+ @mutex.synchronize {
57
+ @at = Time.now
58
+ @exception = e
59
+ }
60
+
61
+ break
62
+ end
63
+ end
64
+ }
65
+
66
+ ObjectSpace.define_finalizer self, self.class.finalizer(@thread)
67
+ end
68
+
69
+ # @private
70
+ def self.finalizer(thread)
71
+ proc {
72
+ thread.raise Cancel.new
73
+ }
74
+ end
75
+
76
+ # Change the number of seconds between each call.
77
+ def every(seconds)
78
+ @every = seconds
79
+
80
+ restart
81
+ end
82
+
83
+ # Cancel the every, {#value} will yield a Cancel exception.
84
+ def cancel
85
+ @mutex.synchronize {
86
+ @thread.raise Cancel.new('every cancelled')
87
+ }
88
+
89
+ self
90
+ end
91
+
92
+ # Check if the every has been cancelled.
93
+ def cancelled?
94
+ @mutex.synchronize {
95
+ @exception.is_a? Cancel
96
+ }
97
+ end
98
+
99
+ # Checks when the every was cancelled.
100
+ def cancelled_at
101
+ if cancelled?
102
+ @mutex.synchronize {
103
+ @at
104
+ }
105
+ end
106
+ end
107
+
108
+ # Restart the every.
109
+ def restart
110
+ @mutex.synchronize {
111
+ @thread.raise Restart.new
112
+ }
113
+
114
+ self
115
+ end
116
+
117
+ # Check if the every is running.
118
+ def running?
119
+ !cancelled?
120
+ end
121
+
122
+ # Check if the every is old, after the first #value call it becomes old,
123
+ # until another run of the block is gone)
124
+ def old?
125
+ @mutex.synchronize {
126
+ @old
127
+ }
128
+ end
129
+
130
+ # Gets the Time when the block was called.
131
+ def called_at
132
+ @mutex.synchronize {
133
+ @at
134
+ }
135
+ end
136
+
137
+ # Gets how many seconds are missing before another call.
138
+ def next_in
139
+ return if cancelled?
140
+
141
+ @mutex.synchronize {
142
+ @every - (Time.now - @at)
143
+ }
144
+ end
145
+
146
+ # Gets the current every value.
147
+ def value(timeout = nil)
148
+ @mutex.synchronize {
149
+ if @old
150
+ cond.wait(@mutex, *timeout)
151
+ end
152
+
153
+ @old = true
154
+
155
+ if @exception
156
+ raise @exception
157
+ else
158
+ @value
159
+ end
160
+ }
161
+ end
162
+
163
+ alias ~ value
164
+
165
+ # Gets the current every value, without blocking and waiting for the next
166
+ # call.
167
+ def value!
168
+ @mutex.synchronize {
169
+ @old = true
170
+
171
+ @value unless @exception
172
+ }
173
+ end
174
+
175
+ private
176
+ def cond?
177
+ instance_variable_defined? :@cond
178
+ end
179
+
180
+ def cond
181
+ @cond ||= ConditionVariable.new
182
+ end
183
+ end
184
+
185
+ class Thread
186
+ # Helper to create an every
187
+ def self.every(every, &block)
188
+ Thread::Every.new(every, &block)
189
+ end
190
+ end
191
+
192
+ module Kernel
193
+ # Helper to create an every
194
+ def every(every, &block)
195
+ Thread::Every.new(every, &block)
196
+ end
197
+ end
@@ -0,0 +1,164 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'thread'
12
+
13
+ # A future is an object that incapsulates a block which is called in a
14
+ # different thread, upon retrieval the caller gets blocked until the block has
15
+ # finished running, and its result is returned and cached.
16
+ class Thread::Future
17
+ Cancel = Class.new(Exception)
18
+
19
+ # Create a future with the passed block and optionally using the passed pool.
20
+ def initialize(pool = nil, &block)
21
+ raise ArgumentError, 'no block given' unless block
22
+
23
+ @mutex = Mutex.new
24
+
25
+ task = proc {
26
+ begin
27
+ deliver block.call
28
+ rescue Exception => e
29
+ @exception = e
30
+
31
+ deliver nil
32
+ end
33
+ }
34
+
35
+ @thread = pool ? pool.process(&task) : Thread.new(&task)
36
+
37
+ ObjectSpace.define_finalizer self, self.class.finalizer(@thread)
38
+ end
39
+
40
+ # @private
41
+ def self.finalizer(thread)
42
+ proc {
43
+ thread.raise Cancel.new
44
+ }
45
+ end
46
+
47
+ # Check if an exception has been raised.
48
+ def exception?
49
+ @mutex.synchronize {
50
+ instance_variable_defined? :@exception
51
+ }
52
+ end
53
+
54
+ # Return the raised exception.
55
+ def exception
56
+ @mutex.synchronize {
57
+ @exception
58
+ }
59
+ end
60
+
61
+ # Check if the future has been called.
62
+ def delivered?
63
+ @mutex.synchronize {
64
+ instance_variable_defined? :@value
65
+ }
66
+ end
67
+
68
+ alias realized? delivered?
69
+
70
+ # Cancel the future, {#value} will yield a Cancel exception
71
+ def cancel
72
+ return self if delivered?
73
+
74
+ @mutex.synchronize {
75
+ @thread.raise Cancel.new('future cancelled')
76
+ }
77
+
78
+ self
79
+ end
80
+
81
+ # Check if the future has been cancelled
82
+ def cancelled?
83
+ @mutex.synchronize {
84
+ @exception.is_a? Cancel
85
+ }
86
+ end
87
+
88
+ # Get the value of the future, if it's not finished running this call will block.
89
+ #
90
+ # In case the block raises an exception, it will be raised, the exception is cached
91
+ # and will be raised every time you access the value.
92
+ #
93
+ # An optional timeout can be passed which will return nil if nothing has been
94
+ # delivered.
95
+ def value(timeout = nil)
96
+ raise @exception if exception?
97
+
98
+ return @value if delivered?
99
+
100
+ @mutex.synchronize {
101
+ cond.wait(@mutex, *timeout)
102
+ }
103
+
104
+ if exception?
105
+ raise @exception
106
+ elsif delivered?
107
+ return @value
108
+ end
109
+ end
110
+
111
+ alias ~ value
112
+
113
+ # Do the same as {#value}, but return nil in case of exception.
114
+ def value!(timeout = nil)
115
+ begin
116
+ value(timeout)
117
+ rescue Exception
118
+ nil
119
+ end
120
+ end
121
+
122
+ alias ! value!
123
+
124
+ private
125
+ def cond?
126
+ instance_variable_defined? :@cond
127
+ end
128
+
129
+ def cond
130
+ @cond ||= ConditionVariable.new
131
+ end
132
+
133
+ def deliver (value)
134
+ return if delivered?
135
+
136
+ @mutex.synchronize {
137
+ @value = value
138
+
139
+ cond.broadcast if cond?
140
+ }
141
+
142
+ self
143
+ end
144
+ end
145
+
146
+ class Thread
147
+ # Helper to create a future
148
+ def self.future(pool = nil, &block)
149
+ Thread::Future.new(pool, &block)
150
+ end
151
+ end
152
+
153
+ module Kernel
154
+ # Helper to create a future.
155
+ def future(pool = nil, &block)
156
+ Thread::Future.new(pool, &block)
157
+ end
158
+ end
159
+
160
+ class Thread::Pool
161
+ def future(&block)
162
+ Thread.future self, &block
163
+ end
164
+ end
@@ -0,0 +1,119 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'thread'
12
+
13
+ # A pipe lets you execute various tasks on a set of data in parallel,
14
+ # each datum inserted in the pipe is passed along through queues to the various
15
+ # functions composing the pipe, the final result is inserted in the final queue.
16
+ class Thread::Pipe
17
+ # A task encapsulates a part of the pipe.
18
+ class Task
19
+ attr_accessor :input, :output
20
+
21
+ # Create a Task which will call the passed function and get input
22
+ # from the optional parameter and put output in the optional parameter.
23
+ def initialize(func, input = Queue.new, output = Queue.new)
24
+ @input = input
25
+ @output = output
26
+ @handling = false
27
+
28
+ @thread = Thread.new {
29
+ while true
30
+ value = @input.deq
31
+
32
+ @handling = true
33
+ begin
34
+ value = func.call(value)
35
+ @output.enq value
36
+ rescue Exception; end
37
+ @handling = false
38
+ end
39
+ }
40
+ end
41
+
42
+ # Check if the task has nothing to do.
43
+ def empty?
44
+ !@handling && @input.empty? && @output.empty?
45
+ end
46
+
47
+ # Stop the task.
48
+ def kill
49
+ @thread.raise
50
+ end
51
+ end
52
+
53
+ # Create a pipe using the optionally passed objects as input and
54
+ # output queue.
55
+ #
56
+ # The objects must respond to #enq and #deq, and block on #deq.
57
+ def initialize(input = Queue.new, output = Queue.new)
58
+ @tasks = []
59
+
60
+ @input = input
61
+ @output = output
62
+
63
+ ObjectSpace.define_finalizer self, self.class.finalizer(@tasks)
64
+ end
65
+
66
+ # @private
67
+ def self.finalizer(tasks)
68
+ proc {
69
+ tasks.each(&:kill)
70
+ }
71
+ end
72
+
73
+ # Add a task to the pipe, it must respond to #call and #arity,
74
+ # and #arity must return 1.
75
+ def |(func)
76
+ if func.arity != 1
77
+ raise ArgumentError, 'wrong arity'
78
+ end
79
+
80
+ Task.new(func, (@tasks.empty? ? @input : Queue.new), @output).tap {|t|
81
+ @tasks.last.output = t.input unless @tasks.empty?
82
+ @tasks << t
83
+ }
84
+
85
+ self
86
+ end
87
+
88
+ # Check if the pipe is empty.
89
+ def empty?
90
+ @input.empty? && @output.empty? && @tasks.all?(&:empty?)
91
+ end
92
+
93
+ # Insert data in the pipe.
94
+ def enq(data)
95
+ return if @tasks.empty?
96
+
97
+ @input.enq data
98
+
99
+ self
100
+ end
101
+
102
+ alias push enq
103
+ alias << enq
104
+
105
+ # Get an element from the output queue.
106
+ def deq(non_block = false)
107
+ @output.deq(non_block)
108
+ end
109
+
110
+ alias pop deq
111
+ alias ~ deq
112
+ end
113
+
114
+ class Thread
115
+ # Helper to create a pipe.
116
+ def self.|(func)
117
+ Pipe.new | func
118
+ end
119
+ end