wires 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 457cb2ed2bd4af2a473bdd857e373aee0d3d9266
4
- data.tar.gz: 2f55be33795d9bccd37e7d90afe95ca0387ceb32
3
+ metadata.gz: bc852c0d3cd3cc7571a39f56b081a006d8e704f2
4
+ data.tar.gz: 937220faab14ab5bd4da7a0c3cb3877f99e56a76
5
5
  SHA512:
6
- metadata.gz: 396cc5c23cb7b7b0e8cd9c52fa9b438310593dd03ba96e4eaf5731b7d7e1933326a31a89d35cc453301f3746016ec1806e39be2afba08f7c8f09c988c44e0097
7
- data.tar.gz: dafd5997b103c258f9fd3776c4eb9f62d11d28c41f0b389e95df7f45827492f9035d5389b140e7edb1d2cdb6a2ce4c151cc3f8ecd096b05ea5c580c2c2f3de73
6
+ metadata.gz: 4f6f27c768dae1cc9bbf6b93495df61b5165c050f420a86317fc732aa8b670443a3b9d62ad5a7c1ae07e45c23267e6ad9b2b6e015beb2f708819896d1593ab66
7
+ data.tar.gz: d8b45765b30d6e690f5a4c6d49d3832a1e41f0b8d4f9bd2c8cb4cde7179a6db3ff62537a42266ede277cc689b9243d70c491f0930a7a364df020d1aa4921a7a1
data/lib/wires.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'set'
2
2
  require 'thread'
3
- require 'active_support/core_ext' # For time convenience functions
3
+ require 'active_support/core_ext' # Convenience functions from Rails
4
4
 
5
5
  require 'wires/events'
6
6
  require 'wires/hub'
data/lib/wires/events.rb CHANGED
@@ -1,91 +1,98 @@
1
+
1
2
  # Store a list of all Event classes that get loaded.
2
3
  class EventRegistry
3
- @@registry = []
4
-
5
- def self.<<(cls)
6
- @@registry << cls
7
- @@registry.uniq!
8
- end
9
-
10
- def self.list
11
- @@registry
12
- end
4
+ @@registry = []
5
+
6
+ def self.<<(cls)
7
+ @@registry << cls
8
+ @@registry.uniq!
9
+ end
10
+
11
+ def self.list
12
+ @@registry
13
+ end
13
14
  end
14
15
 
15
16
  # All Event classes should inherit from this one
16
- class Event
17
+ class Event < Object # explicit for the sake of Event.ancestry
18
+
19
+ # Register with the EventRegistry and make subclasses do the same
20
+ EventRegistry << self
21
+
22
+ # Operate on the metaclass as a type of singleton pattern
23
+ class << self
17
24
 
18
- # Register with the EventRegistry and make subclasses do the same
19
- EventRegistry << self
20
- def self.inherited(subcls)
25
+ def inherited(subcls)
26
+
27
+ # Be sure codestring doesn't conflict
28
+ existing = _from_codestring(subcls.codestring)
29
+ if existing then raise NameError, \
30
+ "New Event subclass '#{subcls}' conflicts with"\
31
+ " existing Event subclass '#{existing}'."\
32
+ " The generated codestring '#{subcls.codestring}'"\
33
+ " must be unique for each Event subclass." end
21
34
 
22
- # Be sure codestring doesn't conflict
23
- existing = self._from_codestring(subcls.codestring)
24
- if existing then raise NameError, \
25
- "New Event subclass '#{subcls}' conflicts with"\
26
- " existing Event subclass '#{existing}'."\
27
- " The generated codestring '#{subcls.codestring}'"\
28
- " must be unique for each Event subclass." end
29
-
30
- # Register, then call super
31
- EventRegistry << subcls
32
- super
35
+ # Register, then call super
36
+ EventRegistry << subcls
37
+ super
33
38
  end
34
39
 
35
40
  # List of class inheritance lineage back to but excluding Object
36
- def self.ancestry(cls=self)
37
- _next = cls.superclass
38
- [cls==Object ? [] : [cls, self.ancestry(_next)]].flatten
41
+ def ancestry(cls=self)
42
+ _next = cls.superclass
43
+ [cls==Object ? [] : [cls, ancestry(_next)]].flatten
39
44
  end
40
45
 
41
46
  # Convert class <ClassNameEvent> to string "class_name"
42
- def self.codestring(cls=self)
43
- cls.to_s
44
- .gsub(/(?<!(?:_|^))([A-Z])/, "_\\1")
45
- .downcase
46
- .gsub(/_event/, "")
47
+ def codestring(cls=self)
48
+ cls.to_s
49
+ .underscore
50
+ .gsub(/_event$/, "")
47
51
  end
48
52
 
49
53
  # List of codestrings associated with this event and ancestors
50
- def self.codestrings
51
- x = self.ancestry
52
- .map {|cls| cls.codestring}
54
+ def codestrings
55
+ x = ancestry
56
+ .map {|cls| cls.codestring}
53
57
  end
54
58
 
55
59
  # Pull class from registry by codestring
56
60
  # (more reliable than crafting a reverse regexp)
57
- def self._from_codestring(str)
58
- return EventRegistry.list
59
- .select{|e| e.codestring==str}[0]
60
- end
61
- def self.from_codestring(str)
62
- cls = self._from_codestring(str)
63
- if not cls then raise NameError,
64
- "No known Event subclass with codestring: '#{str}'" end
65
- cls
66
- end
61
+ def _from_codestring(str)
62
+ return EventRegistry.list
63
+ .select{|e| e.codestring==str}[0]
64
+ end; private :_from_codestring
67
65
 
66
+ def from_codestring(str)
67
+ cls = _from_codestring(str.to_s)
68
+ if not cls then raise NameError,
69
+ "No known Event subclass with codestring: '#{str}'" end
70
+ cls
71
+ end
72
+
68
73
  # Create attributes and accessors for all arguments to the constructor.
69
74
  # This is done here rather than in initialize so that the functionality
70
75
  # will remain if the user developer overrides initialize in the subclass.
71
- def self.new(*args, &block)
72
- obj = super
73
-
74
- kwargs = args[-1].is_a?(Hash) ? args.pop : Hash.new
75
- kwargs[:args] = args
76
- kwargs[:proc] = block if block
77
- for key in kwargs.keys
78
- att = key.to_s
79
- obj.instance_variable_set("@#{att}", kwargs[key])
80
- self.class_eval("def #{att}; @#{att}; end")
81
- self.class_eval("def #{att}=(val); @#{att}=val; end")
82
- end
83
-
84
- obj
76
+ def new(*args, &block)
77
+ obj = super
78
+
79
+ kwargs = args[-1].is_a?(Hash) ? args.pop : Hash.new
80
+ kwargs[:args] = args
81
+ kwargs[:codeblock] = block if block
82
+ for key in kwargs.keys
83
+ att = key.to_s
84
+ obj.instance_variable_set("@#{att}", kwargs[key])
85
+ class_eval("def #{att}; @#{att}; end")
86
+ class_eval("def #{att}=(val); @#{att}=val; end")
87
+ end
88
+
89
+ obj
85
90
  end
86
-
87
- # Calling super in self.new with *args will complain if this isn't here
88
- def initialize(*args, &block) end
91
+
92
+ end
93
+
94
+ # Calling super in new with *args will complain if this isn't here
95
+ def initialize(*args, &block) end
89
96
  end
90
97
 
91
98
 
@@ -95,37 +102,39 @@ end
95
102
 
96
103
  # Reopen Event and add comparison functions
97
104
  class Event
98
- def self.==(other)
99
- other.is_a?(Class) ?
100
- super : self.codestring==other.to_s
105
+ class << self
106
+ def ==(other)
107
+ other.is_a?(Class) ?
108
+ super : codestring==other.to_s
101
109
  end
102
- def self.<=(other)
103
- other.is_a?(Class) ?
104
- super : self.codestrings.include?(other.to_s)
110
+ def <=(other)
111
+ other.is_a?(Class) ?
112
+ super : codestrings.include?(other.to_s)
105
113
  end
106
- def self.<(other)
107
- other.is_a?(Class) ?
108
- super : (self<=other and not self==other)
114
+ def <(other)
115
+ other.is_a?(Class) ?
116
+ super : (self<=other and not self==other)
109
117
  end
110
- def self.>=(other)
111
- other.is_a?(Class) ?
112
- super : Event.from_codestring(other.to_s)<=self
118
+ def >=(other)
119
+ other.is_a?(Class) ?
120
+ super : Event.from_codestring(other.to_s)<=self
113
121
  end
114
- def self.>(other)
115
- other.is_a?(Class) ?
116
- super : Event.from_codestring(other.to_s)<self
122
+ def >(other)
123
+ other.is_a?(Class) ?
124
+ super : Event.from_codestring(other.to_s)<self
117
125
  end
126
+ end
118
127
  end
119
128
 
120
129
  # Autogenerate the inverse comparison functions for Symbol/String
121
130
  for cls in [Symbol, String]
122
- %w(== < > <= >=).zip(%w(== > < >= <=))
123
- .each do |ops|
124
- op, opinv = ops # unzip operator and inverse operator
125
- cls.class_eval(
126
- "def #{op}(other)\n"\
127
- " (other.is_a?(Class) and other<=Event) ? \n"\
128
- " (other#{opinv}self) : super\n"\
129
- "end\n")
130
- end
131
+ %w(== < > <= >=).zip(%w(== > < >= <=))
132
+ .each do |ops|
133
+ op, opinv = ops # unzip operator and inverse operator
134
+ cls.class_eval(
135
+ "def #{op}(other)\n"\
136
+ " (other.is_a?(Class) and other<=Event) ? \n"\
137
+ " (other#{opinv}self) : super\n"\
138
+ "end\n")
139
+ end
131
140
  end
data/lib/wires/hub.rb CHANGED
@@ -9,6 +9,9 @@ class Hub
9
9
  @queue = Queue.new
10
10
  @state = [:dead, :alive, :dying][0]
11
11
 
12
+ @child_threads = Array.new
13
+ @child_threads_lock = Mutex.new
14
+
12
15
  @before_kills = Queue.new
13
16
  @after_kills = Queue.new
14
17
 
@@ -20,47 +23,66 @@ class Hub
20
23
  def dying?; @state==:dying end
21
24
  def state; @state end
22
25
 
26
+ # Clear the Hub queue, but do not kill working threads
23
27
  def clear; @queue.clear end
24
28
 
25
- # Start the Hub event loop in a new thread
26
- def run
27
- if dead?
28
- @thread = Thread.new() do
29
- self.send(:run_loop)
30
- end
29
+ ##
30
+ # Start the Hub event loop (optional flags change thread behavior)
31
+ #
32
+ # valid flags:
33
+ # [+:blocking+] Hub event loop will be run in calling thread,
34
+ # blocking until Hub is killed. If this flag is not
35
+ # specified, the Hub event loop is run in a new thread,
36
+ # which the main thread joins in at_exit.
37
+ def run(*flags)
38
+ if dead? # Only run if not already alive or dying
39
+
40
+ # If :blocking is not set, run in a new thread and join at_exit
41
+ if not (flags.include? :blocking)
42
+ @thread = Thread.new() do
43
+ self.send(:run_loop)
44
+ end
45
+ # Only join if main thread wasn't killed by an exception
46
+ at_exit { @thread.join if not $! }
47
+
48
+ # If :blocking is set, run in main thread and block until Hub death
49
+ else self.send(:run_loop) end
31
50
 
32
- at_exit { @thread.join if not $! }
33
51
  end
52
+
53
+ sleep 0 # Yield to other threads
54
+
34
55
  nil end
35
56
 
36
- # Start the Hub event loop in the current thread
37
- def run_in_place()
38
- self.send(:run_loop) if dead?
57
+ ##
58
+ # Kill the Hub event loop (optional flags change thread behavior)
59
+ #
60
+ # valid flags:
61
+ # [+:finish_all+] Hub thread won't be done until all child threads done
62
+ # [+:blocking+] calling thread won't be done until Hub thread is done
63
+ def kill(*flags)
64
+ @finish_all = (flags.include? :finish_all)
65
+ @state=:dying if alive?
66
+ @thread.join if (flags.include? :blocking)
39
67
  nil end
40
68
 
41
- # Kill the Hub event loop (softly)
42
- def kill()
43
- # Stop the main event loop
44
- @state=:dying;
45
- end
46
-
47
69
  # Register hook to execute before kill - can call multiple times
48
- def before_kill(proc=nil, &block)
70
+ def before_kill(proc=nil, retain:false, &block)
49
71
  func = (block or proc)
50
72
  if not func.is_a?(Proc)
51
73
  raise TypeError, "Expected a Proc or code block to execute."
52
74
  end
53
- @before_kills << func
54
- end
75
+ @before_kills << [func, retain]
76
+ nil end
55
77
 
56
78
  # Register hook to execute after kill - can call multiple times
57
- def after_kill(proc=nil, &block)
79
+ def after_kill(proc=nil, retain:false, &block)
58
80
  func = (block or proc)
59
81
  if not func.is_a?(Proc)
60
82
  raise TypeError, "Expected a Proc or code block to execute."
61
83
  end
62
- @after_kills << func
63
- end
84
+ @after_kills << [func, retain]
85
+ nil end
64
86
 
65
87
  # Put x in the queue, and block until x is processed (if Hub is running)
66
88
  def fire(x)
@@ -70,19 +92,67 @@ class Hub
70
92
  else # don't wait if Hub isn't running - would cause lockup
71
93
  @queue << [x, nil]
72
94
  end
73
- end
95
+ nil end
74
96
  def <<(x); fire(x); end
75
97
 
76
98
  private
77
99
 
78
- def die
79
- # Call the before kill hooks # TODO move
80
- while not @before_kills.empty?
81
- @before_kills.shift.call
100
+ # Kill all currently working child threads
101
+ # Newly fired events could still queue up,
102
+ # Waiting to be born until this thread is done killing
103
+ def kill_children
104
+ @child_threads_lock.synchronize do
105
+ until @child_threads.empty?
106
+ @child_threads.shift.exit
107
+ end
108
+ end
109
+ nil end
110
+
111
+ # Kill all currently working child threads
112
+ # Newly fired events could still queue up,
113
+ # But they will be cleared out and never be born
114
+ def kill_children_and_clear
115
+ @child_threads_lock.synchronize do
116
+ until @child_threads.empty?
117
+ @child_threads.shift.exit
118
+ end
119
+ clear
120
+ end
121
+ nil end
122
+
123
+ # Join child threads, one by one, allowing more children to appear
124
+ def join_children
125
+ a_thread = Thread.new{nil}
126
+ while a_thread
127
+ @child_threads_lock.synchronize do
128
+ a_thread = @child_threads.shift
129
+ end
130
+ a_thread.join if a_thread
131
+ sleep 0 # Yield to other threads
132
+ end
133
+ nil end
134
+
135
+ # Flush/run queue of [proc, retain]s, retaining those with retain==true
136
+ def run_hooks(queue)
137
+ retained = Queue.new
138
+ while not queue.empty?
139
+ proc, retain = queue.shift
140
+ retained << [proc, retain] if retain
141
+ proc.call
142
+ end
143
+ while not retained.empty?
144
+ queue << retained.shift
82
145
  end
146
+ nil end
147
+
148
+ # Run before_kill hooks, optionally join child threads, then die
149
+ def die
150
+ run_hooks(@before_kills)
151
+ join_children if @finish_all
83
152
  @state = :dead
84
- end
153
+ nil end
85
154
 
155
+ # Run main event loop, not finished until Hub is killed
86
156
  def run_loop
87
157
  @state = :alive
88
158
 
@@ -90,36 +160,48 @@ class Hub
90
160
  if @queue.empty? then sleep(0)
91
161
  else process_item(@queue.shift) end
92
162
 
93
- if dying?; die_thread ||= Thread.new { die } end
163
+ if dying?
164
+ die_thread ||= Thread.new { die }
165
+ end
94
166
  end
95
167
 
96
- while not @after_kills.empty?
97
- @after_kills.shift.call
98
- end
99
- end
168
+ run_hooks(@after_kills)
169
+ @finish_all = false
170
+
171
+ nil end
100
172
 
101
173
  def process_item(x)
102
174
  x, waiting_thread = x
103
175
  string, event, blocking, proc = x
104
- Thread.new do
105
- begin
106
- waiting_thread.wakeup unless blocking or not waiting_thread
107
- proc.call($event = event)
108
- waiting_thread.wakeup if blocking and waiting_thread
109
-
110
- rescue Interrupt, SystemExit => e
111
- @state = :dying
112
- unhandled_exception(e)
113
-
114
- rescue Exception => e
115
- unhandled_exception(e)
176
+
177
+ # Do all dealings with @child_threads under mutex
178
+ @child_threads_lock.synchronize do
179
+
180
+ # Clear dead child threads to free up memory
181
+ @child_threads.select! {|t| t.status}
182
+
183
+ # Start the new child thread
184
+ @child_threads << Thread.new do
185
+ begin
186
+ waiting_thread.wakeup unless blocking or not waiting_thread
187
+ proc.call($event = event)
188
+ waiting_thread.wakeup if blocking and waiting_thread
189
+
190
+ rescue Interrupt, SystemExit => e
191
+ @state = :dying
192
+ unhandled_exception(e)
193
+
194
+ rescue Exception => e
195
+ unhandled_exception(e)
196
+ end
116
197
  end
117
198
  end
118
- end
199
+
200
+ nil end
119
201
 
120
202
  def unhandled_exception(x)
121
203
  $stderr.puts $!
122
204
  $stderr.puts $@
123
- end
205
+ nil end
124
206
  end
125
207
  end
data/lib/wires/time.rb CHANGED
@@ -1,66 +1,99 @@
1
+ require 'pry'
1
2
 
2
-
3
- class StartSchedulerEvent < Event; end
3
+ class TimeSchedulerEvent < Event; end
4
+ class TimeSchedulerStartEvent < TimeSchedulerEvent; end
5
+ class TimeSchedulerAnonEvent < TimeSchedulerEvent; end
4
6
 
5
7
  # A singleton class to schedule future firing of events
6
8
  class TimeScheduler
7
9
  @schedule = Array.new
8
10
  @schedule_lock = Mutex.new
9
- @grain = 0.2.seconds
11
+ @thread = Thread.new {nil}
10
12
 
11
13
  # Operate on the metaclass as a type of singleton pattern
12
14
  class << self
13
15
 
14
- # Get or set the time grain from outside the class
15
- attr_accessor :grain
16
+ # Get a copy of the event schedule from outside the class
17
+ def list; @schedule_lock.synchronize {@schedule.clone} end
18
+ # Clear the event schedule from outside the class
19
+ def clear; @schedule_lock.synchronize {@schedule.clear} end
16
20
 
17
- # Fire an event delayed by time value
18
- def fire(time, event, channel='*')
21
+ # Fire an event at a specific time
22
+ def fire(time, event, channel='*', ignore_past:false)
19
23
  if not time.is_a? Time
20
24
  raise TypeError, "Expected #{time.inspect} to be an instance of Time."
21
25
  end
22
26
 
27
+ # Ignore past events if flag is set
28
+ if ignore_past and time < Time.now; return nil; end
29
+
23
30
  # Under mutex, push the event into the schedule and sort
24
31
  @schedule_lock.synchronize do
25
- @schedule << [time, event, channel]
26
- @schedule.sort! { |a,b| a[0] <=> b[0] }
32
+ @schedule << {time:time, event:event, channel:channel}
33
+ @schedule.sort! { |a,b| a[:time] <=> b[:time] }
27
34
  end
28
35
 
29
- end
36
+ # Wakeup main_loop thread if it is sleeping
37
+ begin @thread.wakeup; rescue ThreadError; end
38
+
39
+ nil end
30
40
 
31
41
  private
32
42
 
43
+ # Do scheduled firing of events as long as Hub is alive
33
44
  def main_loop
34
45
 
46
+ @keepgoing = true
47
+ @thread = Thread.current
35
48
  pending = Array.new
49
+ on_deck = nil
36
50
 
37
- while true
51
+ while @keepgoing
38
52
 
53
+ # Under mutex, pull any events that are ready into pending
39
54
  pending.clear
40
- this_time = Time.now
41
-
42
- # Under mutex, pull any events that are ready
43
55
  @schedule_lock.synchronize do
44
- while ((not @schedule.empty?) and (this_time > @schedule[0][0]))
56
+ while ((not @schedule.empty?) and
57
+ (Time.now > @schedule[0][:time]))
45
58
  pending << @schedule.shift
46
59
  end
60
+ on_deck = @schedule[0]
47
61
  end
48
62
 
49
63
  # Fire pending events
50
- pending.each { |x| Channel(x[2]).fire(x[1]) }
64
+ pending.each { |x| Channel(x[:channel]).fire(x[:event]) }
51
65
 
52
- # Calculate the time to sleep based on the time left in the "grain"
53
- sleep [@grain-(Time.now-this_time), 0].max
66
+ # Calculate the time to sleep based on next event's time
67
+ if on_deck
68
+ sleep [(on_deck[:time]-Time.now), 0].max
69
+ else
70
+ sleep # sleep until wakeup if no event is on deck
71
+ end
54
72
 
55
73
  end
56
- end
74
+
75
+ nil end
76
+
57
77
 
58
78
  end
59
79
 
60
80
  # Use fired event to only start scheduler when Hub is running
61
81
  # This also gets the scheduler loop its own thread within the Hub's threads
62
- on :start_scheduler, self do; main_loop; end;
63
- Channel(self).fire(:start_scheduler)
82
+ on :time_scheduler_start, self do; main_loop; end;
83
+ Channel(self).fire(:time_scheduler_start)
84
+
85
+ # Stop the main loop upon death of Hub
86
+ Hub.before_kill(retain:true) do
87
+ @keepgoing=false
88
+ @thread.wakeup
89
+ sleep 0
90
+ end
91
+ # Refire the start event after Hub dies in case it restarts
92
+ Hub.after_kill(retain:true) do
93
+ Channel(self).fire(:time_scheduler_start)
94
+ end
95
+
96
+
64
97
  end
65
98
 
66
99
 
@@ -75,14 +108,14 @@ end
75
108
 
76
109
  # Reopen ActiveSupport::Duration to enable nifty syntax like:
77
110
  # 32.minutes.from_now do some_stuff end
78
- class TimeSchedulerAnonEvent < Event; end
79
111
  class ActiveSupport::Duration
80
112
 
81
113
  alias :__original_since :since
82
114
  def since(*args, &block)
83
115
  if block
84
116
  on :time_scheduler_anon, block.object_id do block.call end
85
- __original_since(*args).fire :time_scheduler_anon, block.object_id
117
+ __original_since(*args).fire(:time_scheduler_anon,
118
+ block.object_id)
86
119
  nil
87
120
  else
88
121
  __original_since(*args)
@@ -94,7 +127,8 @@ class ActiveSupport::Duration
94
127
  def ago(*args, &block)
95
128
  if block
96
129
  on :time_scheduler_anon, block.object_id do block.call end
97
- __original_ago(*args).fire :time_scheduler_anon, block.object_id
130
+ __original_ago(*args).fire(:time_scheduler_anon,
131
+ block.object_id)
98
132
  nil
99
133
  else
100
134
  __original_ago(*args)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wires
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe McIlvain
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-06-27 00:00:00.000000000 Z
11
+ date: 2013-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport