fsevent 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # fsevent
2
+
3
+ fail safe event driven framework
4
+
5
+ # author
6
+
7
+ Tanaka Akira
8
+
9
+ # license
10
+ GPLv3 or later
11
+
12
+ (AIST program administration number: H26PRO-1713)
13
+
data/fsevent.gemspec ADDED
@@ -0,0 +1,43 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'fsevent'
3
+ s.version = '0.1'
4
+ s.date = '2013-06-23'
5
+ s.author = 'Tanaka Akira'
6
+ s.email = 'tanaka-akira@aist.go.jp'
7
+ s.license = 'GPL-3.0+'
8
+ s.required_ruby_version = '>= 1.9.2'
9
+ s.files = %w[
10
+ .gitignore
11
+ LICENSE
12
+ README.md
13
+ fsevent.gemspec
14
+ lib/fsevent.rb
15
+ lib/fsevent/abstractdevice.rb
16
+ lib/fsevent/failsafedevice.rb
17
+ lib/fsevent/framework.rb
18
+ lib/fsevent/periodicschedule.rb
19
+ lib/fsevent/processdevice.rb
20
+ lib/fsevent/processdevicec.rb
21
+ lib/fsevent/schedulemerger.rb
22
+ lib/fsevent/simpledevice.rb
23
+ lib/fsevent/util.rb
24
+ sample/repeat.rb
25
+ sample/repeat2.rb
26
+ ]
27
+ s.test_files = %w[
28
+ test/test_failsafedevice.rb
29
+ test/test_framework.rb
30
+ test/test_processdevice.rb
31
+ test/test_util.rb
32
+ test/test_watch.rb
33
+ ]
34
+ s.has_rdoc = true
35
+ s.homepage = 'https://github.com/fsevent/fsevent'
36
+ s.require_path = 'lib'
37
+ s.summary = 'fail safe event driven framework'
38
+ s.description = <<'End'
39
+ fail safe event driven framework
40
+ End
41
+ end
42
+
43
+
data/lib/fsevent.rb ADDED
@@ -0,0 +1,32 @@
1
+ # fsevent.rb --- library file to be required by users
2
+ #
3
+ # Copyright (C) 2014 National Institute of Advanced Industrial Science and Technology (AIST)
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'rbconfig'
19
+ require 'depq'
20
+
21
+ class FSEvent
22
+ end
23
+
24
+ require 'fsevent/util'
25
+ require 'fsevent/framework'
26
+ require 'fsevent/abstractdevice'
27
+ require 'fsevent/simpledevice'
28
+ require 'fsevent/processdevice'
29
+ require 'fsevent/processdevicec'
30
+ require 'fsevent/failsafedevice.rb'
31
+ require 'fsevent/schedulemerger'
32
+ require 'fsevent/periodicschedule'
@@ -0,0 +1,71 @@
1
+ # abstractdevice.rb --- abstract device definition
2
+ #
3
+ # Copyright (C) 2014 National Institute of Advanced Industrial Science and Technology (AIST)
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ # Abstract class for devices
19
+ #
20
+ class FSEvent::AbstractDevice
21
+ def initialize(device_name)
22
+ @name = device_name
23
+ @current_status = {}
24
+ @schedule = FSEvent::ScheduleMerger.new
25
+ end
26
+ attr_reader :name, :schedule
27
+ attr_writer :framework
28
+
29
+ # Called from the framework when this device is registered.
30
+ def registered
31
+ # child process calls:
32
+ # * @framework.add_watch
33
+ # * @framework.define_status
34
+ # * @framework.status_changed # possible but needless
35
+ # * @framework.set_elapsed_time
36
+ end
37
+
38
+ # Called from the framework when this device is unregistered.
39
+ def unregistered
40
+ end
41
+
42
+ # Called from the framework
43
+ def run(watched_status_change)
44
+ raise NotImplementedError
45
+ # child process calls:
46
+ # * @framework.add_watch # possible but should be rare
47
+ # * @framework.define_status # possible but should be rare
48
+ # * @framework.status_changed
49
+ # * @framework.set_elapsed_time
50
+ end
51
+
52
+ def add_watch(watchee_device_name, status_name, reaction = :immediate)
53
+ @framework.add_watch(watchee_device_name, status_name, reaction)
54
+ end
55
+
56
+ def define_status(status_name, value)
57
+ @framework.define_status(status_name, value)
58
+ end
59
+
60
+ def status_changed(status_name, value)
61
+ @framework.status_changed(status_name, value)
62
+ end
63
+
64
+ def unregister_device(device_name)
65
+ @framework.unregister_device(device_name)
66
+ end
67
+
68
+ def set_elapsed_time(t)
69
+ @framework.set_elapsed_time(t)
70
+ end
71
+ end
@@ -0,0 +1,85 @@
1
+ # failsafedevice.rb --- fail safe device class
2
+ #
3
+ # Copyright (C) 2014 National Institute of Advanced Industrial Science and Technology (AIST)
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ class FSEvent::FailSafeDevice < FSEvent::AbstractDevice
19
+ def initialize(device_name, initial_status, *watchee_device_names)
20
+ super device_name
21
+ raise "One devices required at least" if watchee_device_names.empty?
22
+ @current_status = {} # status_name -> value
23
+ @current_status_list = {} # status_name -> watchee_device_name status_name -> value
24
+ @status_merger = {}
25
+ initial_status.each {|k, v, merger|
26
+ @current_status[k] = v
27
+ @status_merger[k] = merger_callable(merger)
28
+ @current_status_list[k] = {}
29
+ watchee_device_names.each {|watchee_device_name|
30
+ @current_status_list[k][watchee_device_name] = v
31
+ }
32
+ }
33
+ @watchee_device_names = watchee_device_names
34
+ end
35
+
36
+ def merger_callable(merger)
37
+ case merger
38
+ when :max
39
+ method(:merger_max)
40
+ when :min
41
+ method(:merger_min)
42
+ when :lazy
43
+ method(:merger_lazy)
44
+ else
45
+ merger
46
+ end
47
+ end
48
+
49
+ def merger_max(cur, *values) values.max end
50
+ def merger_min(cur, *values) values.min end
51
+ def merger_lazy(cur, *values) values.uniq.length == 1 ? values[0] : cur end
52
+
53
+ def registered
54
+ @watchee_device_names.each {|n|
55
+ @current_status.each {|k, v|
56
+ add_watch n, k
57
+ }
58
+ }
59
+ @current_status.each {|k, v|
60
+ define_status k, v
61
+ }
62
+ end
63
+
64
+ def run(watched_status_change)
65
+ updated = {}
66
+ watched_status_change.each {|watchee_device_name, h|
67
+ h.each {|status_name, value|
68
+ unless updated.has_key? status_name
69
+ updated[status_name] = @current_status_list[status_name]
70
+ end
71
+ updated[status_name][watchee_device_name] = value
72
+ }
73
+ }
74
+ updated.each {|status_name, h|
75
+ merger = @status_merger[status_name]
76
+ cur_val = @current_status[status_name]
77
+ values = @watchee_device_names.map {|d| h[d] }
78
+ new_val = merger.call(cur_val, *values)
79
+ if cur_val != new_val
80
+ @current_status[status_name] = new_val
81
+ status_changed status_name, new_val
82
+ end
83
+ }
84
+ end
85
+ end
@@ -0,0 +1,298 @@
1
+ # framework.rb --- fail safe event driven framework
2
+ #
3
+ # Copyright (C) 2014 National Institute of Advanced Industrial Science and Technology (AIST)
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ class FSEvent
19
+ include FSEvent::Util
20
+
21
+ def initialize(initial_time=Time.now)
22
+ @current_time = initial_time
23
+
24
+ @devices = {} # device_name -> device
25
+
26
+ @status = {} # device_name -> status_name -> value
27
+
28
+ # valid values of reaction: :immediate, :immediate_only_at_beginning, :schedule
29
+ @watches = nested_hash(3) # watchee_device_name -> status_name -> watcher_device_name -> reaction
30
+ @watch_patterns = [] # [watchee_device_name_pat, status_name_pat, watcher_device_name, reaction]
31
+ @watched_status_change = nested_hash(3) # watcher_device_name -> watchee_device_name -> status_name -> value
32
+
33
+ @q = Depq.new
34
+ @schedule_locator = {} # device_name -> locator
35
+ end
36
+ attr_reader :current_time
37
+
38
+ def register_device(device, register_time=@current_time)
39
+ device_name = device.name
40
+ value = [:register, device_name, device]
41
+ @schedule_locator[device_name] = @q.insert value, register_time
42
+ end
43
+
44
+ def start
45
+ until @q.empty?
46
+ loc = @q.delete_min_locator
47
+ event_type, *args = loc.value
48
+ @current_time = loc.priority
49
+ case event_type
50
+ when :register; at_register(loc, *args)
51
+ when :wakeup; at_wakeup(loc, *args)
52
+ when :sleep; at_sleep(loc, *args)
53
+ else
54
+ raise "unexpected event type: #{event_type}"
55
+ end
56
+ end
57
+ end
58
+
59
+ def wrap_device_action(&block)
60
+ Thread.current[:fsevent_device_watch_buffer] = device_watch_buffer = []
61
+ Thread.current[:fsevent_device_define_buffer] = device_define_buffer = []
62
+ Thread.current[:fsevent_device_changed_buffer] = device_changed_buffer = []
63
+ Thread.current[:fsevent_unregister_device_buffer] = unregister_device_buffer = []
64
+ Thread.current[:fsevent_device_elapsed_time] = nil
65
+ t1 = Time.now
66
+ yield
67
+ t2 = Time.now
68
+ elapsed = Thread.current[:fsevent_device_elapsed_time] || t2 - t1
69
+ return device_watch_buffer, device_define_buffer, device_changed_buffer, unregister_device_buffer, elapsed
70
+ ensure
71
+ Thread.current[:fsevent_device_watch_buffer] = nil
72
+ Thread.current[:fsevent_device_define_buffer] = nil
73
+ Thread.current[:fsevent_device_changed_buffer] = nil
74
+ Thread.current[:fsevent_unregister_device_buffer] = nil
75
+ Thread.current[:fsevent_device_elapsed_time] = nil
76
+ end
77
+ private :wrap_device_action
78
+
79
+ # Called from a device. (mainly from registered().)
80
+ def add_watch(watchee_device_name, status_name, reaction = :immediate)
81
+ Thread.current[:fsevent_device_watch_buffer] << [:add, watchee_device_name, status_name, reaction]
82
+ end
83
+
84
+ # Called from a device. (mainly from registered().)
85
+ def del_watch(watchee_device_name, status_name)
86
+ Thread.current[:fsevent_device_watch_buffer] << [:del, watchee_device_name, status_name, nil]
87
+ end
88
+
89
+ # Called from a device to define the status.
90
+ def define_status(status_name, value)
91
+ Thread.current[:fsevent_device_define_buffer] << [status_name, value]
92
+ end
93
+
94
+ # Called from a device to notify the status.
95
+ def status_changed(status_name, value)
96
+ Thread.current[:fsevent_device_changed_buffer] << [status_name, value]
97
+ end
98
+
99
+ # Called from a device.
100
+ def unregister_device(device_name)
101
+ Thread.current[:fsevent_unregister_device_buffer] << device_name
102
+ end
103
+
104
+ # Called from a device to set the elapsed time.
105
+ def set_elapsed_time(t)
106
+ raise "elapsed time must be positive: #{t}" if t <= 0
107
+ Thread.current[:fsevent_device_elapsed_time] = t
108
+ end
109
+
110
+ def at_register(loc, device_name, device)
111
+ if @devices.has_key? device_name
112
+ raise "Device already registered: #{device_name}"
113
+ end
114
+
115
+ device_watch_buffer, device_define_buffer, device_changed_buffer, unregister_device_buffer, elapsed =
116
+ wrap_device_action {
117
+ device.framework = self
118
+ device.registered
119
+ }
120
+
121
+ @devices[device_name] = device
122
+ @status[device_name] = {}
123
+
124
+ value = [:sleep, device_name, device_watch_buffer, device_define_buffer, device_changed_buffer, unregister_device_buffer]
125
+ loc.update value, current_time + elapsed
126
+ @q.insert_locator loc
127
+ end
128
+ private :at_register
129
+
130
+ def at_wakeup(loc, device_name)
131
+ time = loc.priority
132
+ device = @devices[device_name]
133
+
134
+ watched_status_change = @watched_status_change.delete(device_name)
135
+ watched_status_change = nonempty_hash(watched_status_change, 2)
136
+
137
+ device_watch_buffer, device_define_buffer, device_changed_buffer, unregister_device_buffer, elapsed =
138
+ wrap_device_action { device.run(watched_status_change) }
139
+
140
+ value = [:sleep, device_name, device_watch_buffer, device_define_buffer, device_changed_buffer, unregister_device_buffer]
141
+ loc.update value, time + elapsed
142
+ @q.insert_locator loc
143
+ end
144
+ private :at_wakeup
145
+
146
+ def at_sleep(loc, device_name, device_watch_buffer, device_define_buffer, device_changed_buffer, unregister_device_buffer)
147
+ sleep_time = loc.priority
148
+ update_status(device_name, device_define_buffer, device_changed_buffer)
149
+ wakeup_immediate = update_watch(device_name, device_watch_buffer)
150
+ notify_status_change(device_name, sleep_time, device_define_buffer, device_changed_buffer)
151
+ wakeup_immediate ||= immediate_wakeup?(device_name)
152
+ setup_next_schedule(device_name, loc, sleep_time, wakeup_immediate)
153
+ unregister_device_internal(unregister_device_buffer)
154
+ end
155
+ private :at_sleep
156
+
157
+ def update_status(device_name, device_define_buffer, device_changed_buffer)
158
+ device_define_buffer.each {|status_name, value|
159
+ if @status[device_name].has_key? status_name
160
+ raise "device status already defined: #{device_name} #{status_name}"
161
+ end
162
+ @status[device_name][status_name] = value
163
+ }
164
+ device_changed_buffer.each {|status_name, value|
165
+ unless @status[device_name].has_key? status_name
166
+ raise "device status not defined: #{device_name} #{status_name}"
167
+ end
168
+ @status[device_name][status_name] = value
169
+ }
170
+ end
171
+ private :update_status
172
+
173
+ def update_watch(device_name, device_watch_buffer)
174
+ wakeup_immediate = false
175
+ device_watch_buffer.each {|add_or_del, watchee_device_name, status_name, reaction|
176
+ case add_or_del
177
+ when :add
178
+ @watches[watchee_device_name][status_name][device_name] = reaction
179
+ if @status.has_key?(watchee_device_name) &&
180
+ @status[watchee_device_name].has_key?(status_name)
181
+ @watched_status_change[device_name][watchee_device_name][status_name] = @status[watchee_device_name][status_name]
182
+ wakeup_immediate ||= reaction_immediate_at_beginning? reaction
183
+ end
184
+ when :del
185
+ @watches[watchee_device_name][status_name].delete device_name
186
+ @watched_status_change[device_name][watchee_device_name].delete status_name
187
+ else
188
+ raise "unexpected add_or_del: #{add_or_del.inspect}"
189
+ end
190
+ }
191
+ wakeup_immediate
192
+ end
193
+ private :update_watch
194
+
195
+ def notify_status_change(device_name, sleep_time, device_define_buffer, device_changed_buffer)
196
+ device_define_buffer.each {|status_name, _|
197
+ value = @status[device_name][status_name]
198
+ lookup_watchers(device_name, status_name).each {|watcher_device_name, reaction|
199
+ @watched_status_change[watcher_device_name][device_name][status_name] = value
200
+ set_wakeup_if_possible(watcher_device_name, sleep_time) if reaction_immediate_at_beginning? reaction
201
+ }
202
+ }
203
+ device_changed_buffer.each {|status_name, _|
204
+ value = @status[device_name][status_name]
205
+ lookup_watchers(device_name, status_name).each {|watcher_device_name, reaction|
206
+ @watched_status_change[watcher_device_name][device_name][status_name] = value
207
+ set_wakeup_if_possible(watcher_device_name, sleep_time) if reaction_immediate_at_subsequent? reaction
208
+ }
209
+ }
210
+ end
211
+ private :notify_status_change
212
+
213
+ def lookup_watchers(watchee_device_name, status_name)
214
+ result = []
215
+ if @watches.has_key?(watchee_device_name) &&
216
+ @watches[watchee_device_name].has_key?(status_name)
217
+ @watches[watchee_device_name][status_name].each {|watcher_device_name, reaction|
218
+ result << [watcher_device_name, reaction]
219
+ }
220
+ end
221
+ result
222
+ end
223
+ private :lookup_watchers
224
+
225
+ def set_wakeup_if_possible(device_name, time)
226
+ loc = @schedule_locator[device_name]
227
+ if !loc.in_queue?
228
+ loc.update [:wakeup, device_name], time
229
+ @q.insert_locator loc
230
+ return
231
+ end
232
+ case event_type = loc.value.first
233
+ when :wakeup # The device is sleeping now.
234
+ if time < loc.priority
235
+ loc.update_priority time
236
+ end
237
+ when :sleep # The device is working now.
238
+ # Nothing to do. at_sleep itself checks arrived events at last.
239
+ else
240
+ raise "unexpected event type: #{event_type}"
241
+ end
242
+ end
243
+ private :set_wakeup_if_possible
244
+
245
+ def setup_next_schedule(device_name, loc, sleep_time, wakeup_immediate)
246
+ device = @devices[device_name]
247
+ wakeup_time = nil
248
+ if wakeup_immediate
249
+ wakeup_time = sleep_time
250
+ elsif wakeup_time = device.schedule.shift
251
+ if wakeup_time < sleep_time
252
+ wakeup_time = sleep_time
253
+ end
254
+ while device.schedule.first && device.schedule.first < sleep_time
255
+ device.schedule.shift
256
+ end
257
+ end
258
+ if wakeup_time
259
+ value = [:wakeup, device_name]
260
+ loc.update value, wakeup_time
261
+ @q.insert_locator loc
262
+ end
263
+ end
264
+ private :setup_next_schedule
265
+
266
+ def immediate_wakeup?(watcher_device_name)
267
+ return false unless @watched_status_change.has_key?(watcher_device_name)
268
+ @watched_status_change[watcher_device_name].each {|watchee_device_name, h|
269
+ h.each {|status_name, value|
270
+ lookup_watchers(watchee_device_name, status_name).each {|watcher_device_name2, reaction|
271
+ next if watcher_device_name != watcher_device_name2
272
+ return true if reaction_immediate_at_subsequent?(reaction)
273
+ }
274
+ }
275
+ }
276
+ false
277
+ end
278
+ private :immediate_wakeup?
279
+
280
+ def unregister_device_internal(unregister_device_buffer)
281
+ unregister_device_buffer.each {|device_name|
282
+ device = @devices.delete device_name
283
+ @status.delete device_name
284
+ @watches.each {|watchee_device_name, h1|
285
+ h1.each {|status_name, h2|
286
+ h2.delete device_name
287
+ }
288
+ }
289
+ @watched_status_change.delete device_name
290
+ @status.delete device_name
291
+ loc = @schedule_locator.fetch(device_name)
292
+ device.unregistered
293
+ @q.delete_locator loc
294
+ }
295
+ end
296
+ private :unregister_device_internal
297
+
298
+ end