wires 0.1.9 → 0.1.10

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.
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