hakuban 0.6.2 → 0.6.5

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
  SHA256:
3
- metadata.gz: 6d0b0d54e3b6dce8ecd11b991e94768247f2e978cca397d430fcab6bfd987484
4
- data.tar.gz: 81466178ba54513cb665cd49800599125bda1ba108f7eee71efff3e5b496cbb5
3
+ metadata.gz: b96f9b3980a477679e6500a226142873db6c0569706e2eae58b29c04b8a956e9
4
+ data.tar.gz: c66a0a751a8d95bbd5944edb8c5c9a2cdbd5f1dbc5f8094277357cb4aac146e0
5
5
  SHA512:
6
- metadata.gz: dc7157372663dc871a85b36ee042e0fc12b66ad45fb322398b593086b47896167ab4cb913ab8b1f636071a9a006d2a3ab3af34b6d32e4024d7f3121dc4d9e19c
7
- data.tar.gz: 6b0b7c59f66be811b16322574f8f0670c5802760b8cf8e690b3d4fa4107ea17dffa1e085c0c9607a72fa0a025eef0665087d5b18b0d671e43bab1cf7b5eea95f
6
+ metadata.gz: 95ba2b481c476d7b5640a929531ee9e0b1cffaaed4968970a063bfe718a6cbe99151cc3b2c85cadef2227ea6ba3acf9264e9f10a2dc44077c3dee9b389639dcc
7
+ data.tar.gz: '0945092f4ebe7b07dadc6288ad39e05bc7a6ca282779b9b12f92f5427f233d651bc61337b272bed9e20186696e0359ba0d8431372d5bb56565a22056dfe357c7'
data/bin/hakuban-observer CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'slop'
4
4
  require 'pp'
5
- require 'hakuban/async'
5
+ require 'hakuban/thread'
6
6
 
7
7
 
8
8
  OPTIONS = Slop.parse { |o|
@@ -27,30 +27,38 @@ else
27
27
  end
28
28
 
29
29
 
30
- def print_event(descriptor, event)
31
- puts "-"*120
32
- puts "Event: " + event
33
- puts "Descriptor: "
34
- descriptor.tags.each { |tag|
35
- puts "\tTags: " + JSON.dump(tag.json)
36
- }
37
- puts "\tJSON: " + JSON.dump(descriptor.json)
30
+ def print_event(descriptor, event, more)
31
+ if $subsequent
32
+ puts ','
33
+ else
34
+ $subsequent = true
35
+ end
36
+ puts '{'
37
+ puts '"event": "%s",'%[event]
38
+ puts '"descriptor_json": %s,'%[JSON.dump(descriptor.json)]
39
+ puts '"descriptor_tags": [%s],'%[descriptor.tags.map { |tag| JSON.dump(tag.json) }.join(",")]
40
+ puts more if more
41
+ puts '}'
38
42
  end
39
43
 
40
- Async {
41
-
42
- observe_contract.async { |object|
43
- print_event(object.descriptor, "create")
44
- while object.next_change
45
- print_event(object.descriptor, "change")
44
+
45
+ puts "["
46
+
47
+ contract = observe_contract.manage.with_thread { |object|
48
+ print_event(object.descriptor, "create", nil)
49
+ while object.next_change
50
+ print_event(object.descriptor, "change",
46
51
  if state = object.state
47
- puts "Version: " + state.version.inspect
48
- puts "Last synchronized: " + state.synchronized.to_s + "ms ago"
49
- puts "Data:\n"
50
- pp state.data
52
+ [
53
+ '"version": %s,'%[JSON.dump(state.version)],
54
+ '"last_sync_ms_ago": %s,'%[JSON.dump(state.synchronized)],
55
+ '"data": %s,'%[JSON.dump(state.data)],
56
+ ].join("\n")
51
57
  end
52
- end
53
- print_event(object.descriptor, "drop")
54
- }
55
-
58
+ )
59
+ end
60
+ print_event(object.descriptor, "drop", nil)
56
61
  }
62
+
63
+ $stdin.read(1)
64
+ puts "]"
data/lib/hakuban/async.rb CHANGED
@@ -5,41 +5,34 @@ require 'async'
5
5
  module Hakuban
6
6
 
7
7
  class AsyncObjectManager < ObjectManager
8
+
8
9
  def async_run
9
10
  Async { yield }
10
11
  end
11
12
 
12
- def async_join(async)
13
- async.wait
13
+ def async_join(task)
14
+ task.wait
14
15
  end
15
- end
16
-
17
16
 
18
- class ObjectObserve
19
- def async(&block)
20
- AsyncObjectManager.new(self, ObservedObject, block)
17
+ def async_stop(task)
18
+ task.stop
21
19
  end
22
- end
23
-
24
-
25
- class ObjectExpose
26
- def async(&block)
27
- AsyncObjectManager.new(self, ExposedObject, block)
28
- end
29
- end
30
-
31
20
 
32
- class TagObserve
33
- def async(&block)
34
- AsyncObjectManager.new(self, ObservedObject, block)
21
+ def async_filter_out_stop_exception
22
+ yield
23
+ rescue Async::Stop
24
+ nil
35
25
  end
26
+
36
27
  end
37
28
 
38
29
 
39
- class TagExpose
40
- def async(&block)
41
- AsyncObjectManager.new(self, ExposedObject, block)
30
+ class ObjectManagerBuilder
31
+
32
+ def with_async(&block)
33
+ build(AsyncObjectManager, block)
42
34
  end
35
+
43
36
  end
44
37
 
45
38
  end
@@ -2,8 +2,6 @@
2
2
 
3
3
  require 'ostruct'
4
4
 
5
- require_relative './ffi.rb'
6
-
7
5
 
8
6
  module Hakuban
9
7
 
@@ -1,7 +1,6 @@
1
1
  require 'set'
2
2
  require 'ostruct'
3
3
 
4
- require_relative './ffi.rb'
5
4
  require_relative './event-queue.rb'
6
5
 
7
6
  #TODO: explicit drops?
@@ -33,9 +32,14 @@ module Hakuban
33
32
  end
34
33
 
35
34
 
35
+ def self.hakuban_initialize
36
+ require_relative './ffi.rb'
37
+ end
38
+
36
39
  @@logger_initialized = false
37
40
 
38
41
  def self.logger_initialize(default_level, skip_if_already_initialized = false)
42
+ ::Hakuban::hakuban_initialize
39
43
  if @@logger_initialized and !skip_if_already_initialized
40
44
  raise "Logger already initialized. This can't be done more than once. Make sure logger_initialize is called before any LocalNode gets constructed."
41
45
  end
@@ -53,6 +57,7 @@ module Hakuban
53
57
  attr_reader :local_node_pointer #todo: hide
54
58
 
55
59
  def initialize(name: nil)
60
+ ::Hakuban::hakuban_initialize
56
61
  @default_serializer = lambda { |data_type, data|
57
62
  [["JSON"]+data_type, JSON.dump(data)]
58
63
  }
@@ -6,17 +6,56 @@ require 'ostruct'
6
6
 
7
7
  module Hakuban
8
8
 
9
+ class ObjectManagerBuilder
10
+
11
+ def initialize(contract)
12
+ @contract = contract
13
+ @on_exception_policy = :retry
14
+ @retry_backoff = [0.01, 0.1, 1.0, 10.0]
15
+ @terminate_on_deactivation = false
16
+ end
17
+
18
+
19
+ def on_exception(policy)
20
+ @on_exception_policy = policy
21
+ self
22
+ end
23
+
24
+
25
+ def terminate_on_deactivation
26
+ @terminate_on_deactivation = true
27
+ self
28
+ end
29
+
30
+
31
+ def retry_backoff(*times)
32
+ @retry_backoff = times.flatten
33
+ self
34
+ end
35
+
36
+
37
+ def build(manager_class, block)
38
+ raise "don't use build() directly, use .with_thread {} or .with_async {} instead" if not manager_class
39
+ manager_class.new(@contract, block, @retry_backoff, @terminate_on_deactivation, @on_exception_policy)
40
+ end
41
+
42
+ end
43
+
44
+
9
45
  class ObjectManager
10
46
 
11
47
  attr_reader :contract
12
48
 
13
49
  class Event < OpenStruct; end
50
+ class HandlerException < OpenStruct; end
51
+
14
52
 
15
- def initialize(contract, object_class, block)
53
+ def initialize(contract, block, retry_backoff, terminate_on_deactivation, on_exception_policy)
16
54
  @contract = contract
55
+ object_class = @contract.class.const_get(:ManagedObject)
17
56
  @objects_mutex = Mutex.new
18
- @objects = {}
19
- @running_handlers = {}
57
+ @existing_objects = {} #TODO: turn this into a WeakMap, to keep object if external code still holds reference to it
58
+ @active_objects = {}
20
59
  @event_queue = Queue.new
21
60
 
22
61
  # This callback gets called from a separate thread, with no async reactor running.
@@ -31,38 +70,82 @@ module Hakuban
31
70
  while @event_queue and event = @event_queue.shift
32
71
  @objects_mutex.synchronize {
33
72
  case event.action
34
- when :insert
35
- raise if @objects[event.descriptor]
36
- @objects[event.descriptor] = object_class.new(@contract, event.descriptor)
37
- @event_queue << Event.new(action: :handler_start, descriptor: event.descriptor) if block
38
- when :change
39
- raise if not @objects[event.descriptor]
40
- @objects[event.descriptor].do_change(:change)
41
- when :remove
42
- raise if not @objects[event.descriptor]
43
- @objects[event.descriptor].do_change(:remove)
44
- @objects.delete(event.descriptor)
45
- when :handler_start
46
- if (object = @objects[event.descriptor]) and !object.handler_already_run and !@running_handlers[event.descriptor]
47
- descriptor_for_lambda = event.descriptor
48
- @running_handlers[event.descriptor] = async_run {
49
- object.run(block)
50
- @event_queue << Event.new(action: :handler_finished, descriptor: descriptor_for_lambda) if @event_queue
51
- }
73
+ when :insert, :change, :remove, :stopped, :timer
74
+ object = @existing_objects[event.descriptor] ||= object_class.new(@contract, event.descriptor)
75
+ if !object.running and object.async
76
+ error = async_join(object.async)
77
+ object.async = nil
78
+ if object.last_exception_at
79
+ interval_since_last_exception = Time.new - object.last_exception_at
80
+ heal_multiplier = 10.0
81
+ object.current_delay_index -= (interval_since_last_exception / (retry_backoff[-1]*heal_multiplier)).floor
82
+ object.current_delay_index = 0 if object.current_delay_index < 0
83
+ end
84
+ if error.kind_of? HandlerException
85
+ case on_exception_policy
86
+ when :raise
87
+ raise error.exception
88
+ when :retry
89
+ object.last_exception_at = Time.new
90
+ object.earliest_next_run = Time.new + retry_backoff[object.current_delay_index]
91
+ lambda_sleep_time = object.earliest_next_run - Time.new
92
+ lambda_descriptor = event.descriptor
93
+ async_run {
94
+ sleep lambda_sleep_time
95
+ @event_queue << Event.new(action: :timer, descriptor: lambda_descriptor) if @event_queue
96
+ } #TODO: do we have to join this?
97
+ object.current_delay_index += 1 if object.current_delay_index < retry_backoff.size - 1
98
+ #when :ignore_failing_descriptor
99
+ #when :drop_contract
100
+ end
101
+ end
102
+ end
103
+ if object.check_active
104
+ if block and !object.running and (!object.earliest_next_run or Time.new >= object.earliest_next_run)
105
+ descriptor_for_lambda = event.descriptor
106
+ object.running = true
107
+ object.async = async_run do
108
+ async_filter_out_stop_exception {
109
+ object.run(block)
110
+ }
111
+ rescue Exception => e
112
+ $stderr.puts "Exception in hakuban manager lambda: #{e}"
113
+ $stderr.puts "Contract: "+@contract.inspect
114
+ $stderr.puts "Descriptor: "+descriptor_for_lambda.inspect
115
+ $stderr.puts "Backtrace:\n\t#{e.backtrace.join("\n\t")}"
116
+ HandlerException.new(exception: e) # exception has to wrapped here to stop async from throwing on join
117
+ ensure
118
+ object.running = false
119
+ @event_queue << Event.new(action: :stopped, descriptor: descriptor_for_lambda) if @event_queue
120
+ end
121
+ end
122
+ @active_objects[event.descriptor] ||= object
123
+ object.change
124
+ else
125
+ @active_objects.delete(event.descriptor)
126
+ if object.running
127
+ object.stop
128
+ async_stop(object.async) if terminate_on_deactivation
129
+ else
130
+ @existing_objects.delete(event.descriptor)
131
+ end
52
132
  end
53
- when :handler_finished
54
- raise if not @running_handlers[event.descriptor]
55
- @running_handlers.delete(event.descriptor)
56
- @event_queue << Event.new(action: :handler_start, descriptor: event.descriptor) if @objects[event.descriptor]
57
133
  when :drop
58
134
  @ffi_events.callback_unregister
59
- @objects.values.each { |object| object.do_change(:remove) }
60
- while @running_handlers.size > 0
135
+ @active_objects.clear
136
+ @existing_objects.dup.each { |descriptor, object|
137
+ if object.running
138
+ object.stop
139
+ async_stop(object.async) if terminate_on_deactivation
140
+ else
141
+ @existing_objects.delete(descriptor)
142
+ end
143
+ }
144
+ while @existing_objects.size > 0
61
145
  event = @event_queue.shift
62
- @running_handlers.delete(event.descriptor) if event.action == :handler_finished
146
+ @existing_objects.delete(event.descriptor) if event.action == :stopped
63
147
  end
64
148
  @event_queue.clear
65
- @objects.clear
66
149
  @contract.drop
67
150
  @contract, @event_queue = nil, nil
68
151
  break
@@ -75,14 +158,14 @@ module Hakuban
75
158
 
76
159
  def objects
77
160
  @objects_mutex.synchronize {
78
- @objects.dup
161
+ @active_objects.dup
79
162
  }
80
163
  end
81
164
 
82
165
 
83
166
  def object
84
167
  @objects_mutex.synchronize {
85
- @objects.values.first
168
+ @active_objects.values.first
86
169
  }
87
170
  end
88
171
 
@@ -102,20 +185,20 @@ module Hakuban
102
185
 
103
186
 
104
187
 
105
- class ManagedObject
188
+ class ManagedObjectBase
106
189
 
107
- attr_reader :descriptor, :async, :handler_already_run
190
+ attr_reader :descriptor
191
+ attr_accessor :running, :async, :last_exception_at, :current_delay_index, :earliest_next_run
108
192
 
109
193
 
110
194
  def initialize(contract, descriptor)
111
195
  @contract,@descriptor = contract, descriptor
112
196
  @changes = Queue.new
113
- @handler_already_run = false
197
+ @current_delay_index = 0
114
198
  end
115
199
 
116
200
 
117
201
  def run(handler)
118
- @handler_already_run = true
119
202
  handler.call(self)
120
203
  end
121
204
 
@@ -125,23 +208,27 @@ module Hakuban
125
208
  loop {
126
209
  case @changes.shift
127
210
  when :change then return true
128
- when :remove then return false
211
+ when :stop then return false
129
212
  end
130
213
  }
131
214
  end
132
215
 
133
216
 
134
- def do_change(change)
135
- @changes.push(change)
217
+ def change
218
+ @changes.push(:change)
136
219
  end
137
220
 
221
+ def stop
222
+ @changes.push(:stop)
223
+ end
224
+
138
225
  end
139
226
 
140
227
 
141
228
 
142
229
  class ObjectObserve
143
230
 
144
- class ObservedObject < ManagedObject
231
+ class ManagedObject < ManagedObjectBase
145
232
 
146
233
  def state
147
234
  @contract.object_state
@@ -151,6 +238,15 @@ module Hakuban
151
238
  @contract.object_state&.data
152
239
  end
153
240
 
241
+ def check_active
242
+ !!state
243
+ end
244
+
245
+ end
246
+
247
+
248
+ def manage
249
+ ObjectManagerBuilder.new(self)
154
250
  end
155
251
 
156
252
  end
@@ -158,7 +254,7 @@ module Hakuban
158
254
 
159
255
  class ObjectExpose
160
256
 
161
- class ExposedObject < ManagedObject
257
+ class ManagedObject < ManagedObjectBase
162
258
 
163
259
  def initialize(contract, descriptor)
164
260
  super(contract, descriptor)
@@ -201,6 +297,15 @@ module Hakuban
201
297
  set_data(value)
202
298
  end
203
299
 
300
+ def check_active
301
+ assigned?
302
+ end
303
+
304
+ end
305
+
306
+
307
+ def manage
308
+ ObjectManagerBuilder.new(self)
204
309
  end
205
310
 
206
311
  end
@@ -208,7 +313,7 @@ module Hakuban
208
313
 
209
314
  class TagObserve
210
315
 
211
- class ObservedObject < ManagedObject
316
+ class ManagedObject < ManagedObjectBase
212
317
 
213
318
  def state
214
319
  @contract.object_state(@descriptor)
@@ -218,6 +323,15 @@ module Hakuban
218
323
  @contract.object_state(@descriptor)&.data
219
324
  end
220
325
 
326
+ def check_active
327
+ !!state
328
+ end
329
+
330
+ end
331
+
332
+
333
+ def manage
334
+ ObjectManagerBuilder.new(self)
221
335
  end
222
336
 
223
337
  end
@@ -225,7 +339,7 @@ module Hakuban
225
339
 
226
340
  class TagExpose
227
341
 
228
- class ExposedObject < ManagedObject
342
+ class ManagedObject < ManagedObjectBase
229
343
 
230
344
  def initialize(contract, descriptor)
231
345
  super(contract, descriptor)
@@ -268,8 +382,17 @@ module Hakuban
268
382
  set_data(value)
269
383
  end
270
384
 
385
+ def check_active
386
+ assigned?
387
+ end
388
+
389
+ end
390
+
391
+
392
+ def manage
393
+ ObjectManagerBuilder.new(self)
271
394
  end
272
395
 
273
396
  end
274
397
 
275
- end
398
+ end
@@ -4,41 +4,32 @@ require 'hakuban/manager'
4
4
  module Hakuban
5
5
 
6
6
  class ThreadObjectManager < ObjectManager
7
+
7
8
  def async_run
8
9
  Thread.new { yield }
9
10
  end
10
11
 
11
12
  def async_join(thread)
12
- thread.join
13
+ thread.value
13
14
  end
14
- end
15
-
16
15
 
17
- class ObjectObserve
18
- def thread(&block)
19
- ThreadObjectManager.new(self, ObservedObject, block)
16
+ def async_stop(thread)
17
+ thread.kill
20
18
  end
21
- end
22
-
23
-
24
- class ObjectExpose
25
- def thread(&block)
26
- ThreadObjectManager.new(self, ExposedObject, block)
19
+
20
+ def async_filter_out_stop_exception
21
+ yield
27
22
  end
23
+
28
24
  end
29
25
 
30
26
 
31
- class TagObserve
32
- def thread(&block)
33
- ThreadObjectManager.new(self, ObservedObject, block)
27
+ class ObjectManagerBuilder
28
+
29
+ def with_thread(&block)
30
+ build(ThreadObjectManager, block)
34
31
  end
35
- end
36
32
 
37
-
38
- class TagExpose
39
- def thread(&block)
40
- ThreadObjectManager.new(self, ExposedObject, block)
41
- end
42
33
  end
43
34
 
44
35
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hakuban
4
- VERSION = "0.6.2"
4
+ VERSION = "0.6.5"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hakuban
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.6.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - yunta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-13 00:00:00.000000000 Z
11
+ date: 2022-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec