hakuban 0.7.0 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73f369272bfa710ed6771c98f8ae2dc96986bff8937ee05a1a0019fa68ad11f6
4
- data.tar.gz: 2495665b20ff9016201d09e22ece82414637cec6f501c79e4f6d80f3b11ca84f
3
+ metadata.gz: aa2b0b0f5fc01b63fef4ef426d04edff5edc0bcd2c4098f5fe3ca0878e61ddd7
4
+ data.tar.gz: '038c42c98be06d5172c1a9dd17cca48ac9d2594ebd8231b8bb5f497ccfb80aa5'
5
5
  SHA512:
6
- metadata.gz: c6da6b69026da23cc2388dbaae4a3e5e431db7636c6c6ed1744ce0c1547088c80591df142f25d062f8b12b1cb87b193820787b6888657693d5403f3c2893a092
7
- data.tar.gz: 0cf9bd05050f247eb58276952eca9c54dbe3f4d9da0fdca3dccc1c0eabc90e1663a4bead9a9e74dc26602b424d9289ad0d9ffdcdce0d4152516d49aee5da0200
6
+ metadata.gz: e8b9c10a92ed1fb5e007653e4bfaac180c686f7824742585f2782464a57d1147ed6a325626c64b656950463b026271ab03cb09e399d410497038fd1198b0c202
7
+ data.tar.gz: 3f47125cd3d8ec7987472e0fccbdeb33b947457706499f019b08de8d1fd9c791c5f76c0526c2ca4283d91e385b7b22d43d3ad6c101d460b56134e245f32ac2fc
data/bin/hakuban-engine CHANGED
@@ -8,8 +8,7 @@ STDOUT.sync = true
8
8
 
9
9
 
10
10
  OPTIONS = Slop.parse { |o|
11
- o.string '-c', '--connect', "Hakuban upstream address (default: ws://127.0.0.1:3001)", default: "ws://127.0.0.1:3001"
12
- o.string '-n', '--name', "Exchange name (default: engine-PID)", default: "engine##{$$}@#{Socket.gethostname}"
11
+ o.string '-c', '--connect', "Hakuban upstream address (default: ws://127.0.0.1:3001)", default: "ws://127.0.0.1:3001#name=engine"
13
12
  o.array '-e', '--engines', "engines to start, a glob pattern matched against class name (default: *)", delimiter: ",", default: ["*"]
14
13
  o.bool '-d', '--debug', 'Show debug messages'
15
14
  o.on '-h', '--help' do puts o; exit end
@@ -17,30 +16,31 @@ OPTIONS = Slop.parse { |o|
17
16
 
18
17
 
19
18
  Hakuban.logger_initialize("hakuban=debug") if OPTIONS.debug?
20
- $exchange = Hakuban::LocalExchange.new(name: OPTIONS["name"])
21
- connector = Hakuban::WebsocketConnector.new($exchange, OPTIONS["connect"])
19
+ exchange = Hakuban::Exchange.new()
20
+ connector = Hakuban::WebsocketConnector.new(exchange, OPTIONS["connect"])
22
21
 
23
22
 
24
23
  OPTIONS.arguments.each { |to_require|
25
24
  require to_require
26
25
  }
27
26
 
28
- started_engines = OPTIONS[:engines].map { |engine_name_pattern|
27
+ engine_threads = OPTIONS[:engines].map { |engine_name_pattern|
29
28
  Hakuban::Engine.engines.select { |engine_class|
30
29
  File.fnmatch(engine_name_pattern, engine_class.name)
31
30
  }
32
31
  }.flatten.uniq.map { |engine_class|
33
32
  engine_class.new
34
33
  }.map { |engine|
35
- engine.start($exchange)
36
- puts "Started engine: #{engine.class.name}"
37
- engine
34
+ Thread.new {
35
+ puts "Starting engine: #{engine.class.name}"
36
+ engine.run(exchange)
37
+ }
38
38
  }
39
39
 
40
40
  sleep
41
41
 
42
- started_engines.each { |engine|
43
- engine.stop
42
+ engine_threads.each { |thread|
43
+ Hakuban::Engine.stop(thread)
44
44
  puts "Stopped engine: #{engine.class.name}"
45
45
  }
46
46
 
@@ -6,7 +6,7 @@ require 'set'
6
6
 
7
7
 
8
8
  OPTIONS = Slop.parse { |o|
9
- o.string '-c', '--connect', "Hakuban upstream address (default: ws://127.0.0.1:3001)", default: "ws://127.0.0.1:3001"
9
+ o.string '-c', '--connect', "Hakuban upstream address (default: ws://127.0.0.1:3001)", default: "ws://127.0.0.1:3001#name=observer"
10
10
  o.string '-o', '--object', "Object descriptor"
11
11
  o.array '-t', '--tag', "Tag descriptor(s)", delimiter: nil
12
12
  o.bool '-d', '--debug', 'Show debug messages'
@@ -14,16 +14,16 @@ OPTIONS = Slop.parse { |o|
14
14
  }
15
15
 
16
16
  Hakuban.logger_initialize("hakuban=debug") if OPTIONS.debug?
17
- exchange = Hakuban::LocalExchange.new(name: OPTIONS["name"])
17
+ exchange = Hakuban::Exchange.new()
18
18
  connector = Hakuban::WebsocketConnector.new(exchange, OPTIONS["connect"])
19
19
 
20
20
  observe_contract = if OPTIONS["object"]
21
21
  json = JSON.load(OPTIONS["object"])
22
22
  tags = OPTIONS["tag"].map { |tag| JSON.load(tag) }
23
- exchange.object(tags, json).observe
23
+ exchange.object_observe_contract(tags, json).build
24
24
  elsif (OPTIONS["tag"]&.size || 0) > 0
25
25
  tag = OPTIONS["tag"].map { |tag| JSON.load(tag) }
26
- exchange.tag(tag[0]).observe
26
+ exchange.tag_observe_contract(tag[0]).build
27
27
  else
28
28
  puts "I have no idea what to observe :(\nPlease use -o or -t, or both."
29
29
  exit 1
@@ -40,29 +40,22 @@ def event_description(descriptor, event)
40
40
  end
41
41
 
42
42
 
43
- threads = Set.new
44
- threads << Thread.new {
45
- while new_states_stream = observe_contract.next
46
- Thread.new(new_states_stream) { |states_stream|
47
- threads.add(Thread.current)
48
- puts event_description(states_stream.descriptor, "create")
49
- while state = states_stream.next
50
- puts event_description(states_stream.descriptor, "change") +
51
- [
52
- '',
53
- 'Version": %s'%[JSON.dump(state.version)],
54
- 'Last_sync_us_ago": %s'%[JSON.dump(state.synchronized_us_ago)],
55
- 'Data: %s'%[JSON.dump(state.data)],
56
- ].join("\n")
57
- end
58
- puts event_description(states_stream.descriptor, "drop")
59
- states_stream.drop
60
- threads.delete(Thread.current)
61
- }
62
- end
43
+ observe_contract.for_each_concurrent { |states_stream|
44
+ puts event_description(states_stream.descriptor, "create")
45
+ states_stream.for_each { |state|
46
+ puts event_description(states_stream.descriptor, "change") +
47
+ [
48
+ '',
49
+ 'Version: %s'%[JSON.dump(state.version)],
50
+ 'Last_sync_us_ago: %s'%[JSON.dump(state.synchronized_us_ago)],
51
+ 'Data: %s'%[JSON.dump(state.data)],
52
+ ].join("\n")
53
+ }
54
+ puts event_description(states_stream.descriptor, "drop")
63
55
  }
64
56
 
65
- $stdin.read(1)
66
57
 
67
- observe_contract.terminate
68
- threads.each { |thread| thread.join }
58
+
59
+ sleep
60
+
61
+ observe_contract.drop
@@ -14,17 +14,23 @@ module Hakuban
14
14
 
15
15
  end
16
16
 
17
+ class ObserveContract < Contract; end
18
+ class ExposeContract < Contract; end
17
19
 
18
- class ObjectObserveContract < Contract
20
+ class ObjectObserveContract < ObserveContract
19
21
 
20
22
  private_class_method :new
21
23
 
22
- def initialize(local_exchange, descriptor)
23
- @local_exchange, @descriptor = local_exchange, descriptor
24
- @local_exchange.with_pointer { |local_exchange_pointer|
25
- @descriptor.with_pointer { |descriptor_pointer|
26
- initialize_pointer(FFI::hakuban_object_observe_contract_new(local_exchange_pointer, descriptor_pointer),:hakuban_object_observe_contract_drop,nil)
24
+ def initialize(exchange, descriptor, &block)
25
+ super()
26
+ @exchange, @descriptor = exchange, descriptor
27
+ Thread.handle_interrupt(Object => :never) {
28
+ @exchange.with_pointer { |exchange_pointer|
29
+ @descriptor.with_pointer { |descriptor_pointer|
30
+ initialize_pointer(FFI::hakuban_object_observe_contract_new(exchange_pointer, descriptor_pointer),:hakuban_object_observe_contract_drop,nil)
31
+ }
27
32
  }
33
+ do_and_drop_or_return(&block)
28
34
  }
29
35
  end
30
36
 
@@ -33,35 +39,31 @@ module Hakuban
33
39
  include Stream
34
40
 
35
41
  def next(&block)
36
- return nil if ! pointer = FFI::FFIFutureReturningPointer.create_and_await(self) { |pointer| FFI::hakuban_object_observe_contract_next(pointer) }.unwrap
37
- ObjectStateStream.send(:new, pointer).do_and_drop_or_return(&block)
38
- rescue FFIObject::PointerAlreadyDropped
42
+ process_item(
43
+ lambda { |pointer| FFI::hakuban_object_observe_contract_next(pointer) },
44
+ lambda { |pointer| FFIObject::from_ffi_pointer(ObjectStateStream, pointer) },
45
+ &block
46
+ )
39
47
  end
40
48
 
41
- def ready(&block)
42
- return nil if ! pointer = with_pointer { |pointer| FFI::hakuban_object_observe_contract_ready(pointer) }.unwrap
43
- ObjectStateStream.send(:new, pointer).do_and_drop_or_return(&block)
44
- rescue FFIObject::PointerAlreadyDropped
45
- end
46
-
47
- def terminate
48
- with_pointer { |pointer| FFI::hakuban_object_observe_contract_terminate(pointer) }
49
- rescue FFIObject::PointerAlreadyDropped
50
- end
51
49
 
52
50
  end
53
51
 
54
52
 
55
- class ObjectExposeContract < Contract
53
+ class ObjectExposeContract < ExposeContract
56
54
 
57
55
  private_class_method :new
58
56
 
59
- def initialize(local_exchange, descriptor)
60
- @local_exchange, @descriptor = local_exchange, descriptor
61
- @local_exchange.with_pointer { |local_exchange_pointer|
62
- @descriptor.with_pointer { |descriptor_pointer|
63
- initialize_pointer(FFI::hakuban_object_expose_contract_new(local_exchange_pointer, descriptor_pointer),:hakuban_object_expose_contract_drop,nil)
57
+ def initialize(exchange, descriptor, capacity, &block)
58
+ super()
59
+ @exchange, @descriptor = exchange, descriptor
60
+ Thread.handle_interrupt(Object => :never) {
61
+ @exchange.with_pointer { |exchange_pointer|
62
+ @descriptor.with_pointer { |descriptor_pointer|
63
+ initialize_pointer(FFI::hakuban_object_expose_contract_new(exchange_pointer, descriptor_pointer, capacity),:hakuban_object_expose_contract_drop,nil)
64
+ }
64
65
  }
66
+ do_and_drop_or_return(&block)
65
67
  }
66
68
  end
67
69
 
@@ -70,35 +72,30 @@ module Hakuban
70
72
  include Stream
71
73
 
72
74
  def next(&block)
73
- return nil if ! pointer = FFI::FFIFutureReturningPointer.create_and_await(self) { |pointer| FFI::hakuban_object_expose_contract_next(pointer) }.unwrap
74
- ObjectStateSink.send(:new, pointer).do_and_drop_or_return(&block)
75
- rescue FFIObject::PointerAlreadyDropped
76
- end
77
-
78
- def ready(&block)
79
- return nil if ! pointer = with_pointer { |pointer| FFI::hakuban_object_expose_contract_ready(pointer) }.unwrap
80
- ObjectStateSink.send(:new, pointer).do_and_drop_or_return(&block)
81
- rescue FFIObject::PointerAlreadyDropped
82
- end
83
-
84
- def terminate
85
- with_pointer { |pointer| FFI::hakuban_object_expose_contract_terminate(pointer) }
86
- rescue FFIObject::PointerAlreadyDropped
75
+ process_item(
76
+ lambda { |pointer| FFI::hakuban_object_expose_contract_next(pointer) },
77
+ lambda { |pointer| FFIObject::from_ffi_pointer(ObjectStateSink, pointer) },
78
+ &block
79
+ )
87
80
  end
88
81
 
89
82
  end
90
83
 
91
84
 
92
- class TagObserveContract < Contract
85
+ class TagObserveContract < ObserveContract
93
86
 
94
87
  private_class_method :new
95
88
 
96
- def initialize(local_exchange, descriptor)
97
- @local_exchange, @descriptor = local_exchange, descriptor
98
- @local_exchange.with_pointer { |local_exchange_pointer|
99
- @descriptor.with_pointer { |descriptor_pointer|
100
- initialize_pointer(FFI::hakuban_tag_observe_contract_new(local_exchange_pointer, descriptor_pointer),:hakuban_tag_observe_contract_drop,nil)
89
+ def initialize(exchange, descriptor, &block)
90
+ super()
91
+ @exchange, @descriptor = exchange, descriptor
92
+ Thread.handle_interrupt(Object => :never) {
93
+ @exchange.with_pointer { |exchange_pointer|
94
+ @descriptor.with_pointer { |descriptor_pointer|
95
+ initialize_pointer(FFI::hakuban_tag_observe_contract_new(exchange_pointer, descriptor_pointer),:hakuban_tag_observe_contract_drop,nil)
96
+ }
101
97
  }
98
+ do_and_drop_or_return(&block)
102
99
  }
103
100
  end
104
101
 
@@ -107,51 +104,30 @@ module Hakuban
107
104
  include Stream
108
105
 
109
106
  def next(&block)
110
- return nil if ! pointer = FFI::FFIFutureReturningPointer.create_and_await(self) { |pointer| FFI::hakuban_tag_observe_contract_next(pointer) }.unwrap
111
- ObjectStateStream.send(:new, pointer).do_and_drop_or_return(&block)
112
- rescue FFIObject::PointerAlreadyDropped
113
- end
114
-
115
- def ready(&block)
116
- streams_array = with_pointer { |pointer| FFI::hakuban_tag_observe_contract_ready(pointer) }
117
- object_state_streams = streams_array[:pointer].read_array_of_pointer(streams_array[:length]).map { |object_state_stream_pointer|
118
- ObjectStateStream.send(:new, object_state_stream_pointer)
119
- }
120
- FFI::hakuban_array_drop(streams_array)
121
- if block
122
- Thread.handle_interrupt(Object => :never) {
123
- begin
124
- Thread.handle_interrupt(Object => :immediate) {
125
- block.call(object_state_streams)
126
- }
127
- ensure
128
- object_state_streams.each(&:drop)
129
- end
130
- }
131
- else
132
- object_state_streams
133
- end
134
- rescue FFIObject::PointerAlreadyDropped
135
- end
136
-
137
- def terminate
138
- with_pointer { |pointer| FFI::hakuban_tag_observe_contract_terminate(pointer) }
139
- rescue FFIObject::PointerAlreadyDropped
107
+ process_item(
108
+ lambda { |pointer| FFI::hakuban_tag_observe_contract_next(pointer) },
109
+ lambda { |pointer| FFIObject::from_ffi_pointer(ObjectStateStream, pointer) },
110
+ &block
111
+ )
140
112
  end
141
113
 
142
114
  end
143
115
 
144
116
 
145
- class TagExposeContract < Contract
117
+ class TagExposeContract < ExposeContract
146
118
 
147
119
  private_class_method :new
148
120
 
149
- def initialize(local_exchange, descriptor)
150
- @local_exchange, @descriptor = local_exchange, descriptor
151
- @local_exchange.with_pointer { |local_exchange_pointer|
152
- @descriptor.with_pointer { |descriptor_pointer|
153
- initialize_pointer(FFI::hakuban_tag_expose_contract_new(local_exchange_pointer, descriptor_pointer),:hakuban_tag_expose_contract_drop,nil)
121
+ def initialize(exchange, descriptor, capacity, &block)
122
+ super()
123
+ @exchange, @descriptor = exchange, descriptor
124
+ Thread.handle_interrupt(Object => :never) {
125
+ @exchange.with_pointer { |exchange_pointer|
126
+ @descriptor.with_pointer { |descriptor_pointer|
127
+ initialize_pointer(FFI::hakuban_tag_expose_contract_new(exchange_pointer, descriptor_pointer, capacity),:hakuban_tag_expose_contract_drop,nil)
128
+ }
154
129
  }
130
+ do_and_drop_or_return(&block)
155
131
  }
156
132
  end
157
133
 
@@ -160,37 +136,13 @@ module Hakuban
160
136
  include Stream
161
137
 
162
138
  def next(&block)
163
- return nil if ! pointer = FFI::FFIFutureReturningPointer.create_and_await(self) { |pointer| FFI::hakuban_tag_expose_contract_next(pointer) }.unwrap
164
- ObjectStateSink.send(:new, pointer).do_and_drop_or_return(&block)
165
- rescue FFIObject::PointerAlreadyDropped
139
+ process_item(
140
+ lambda { |pointer| FFI::hakuban_tag_expose_contract_next(pointer) },
141
+ lambda { |pointer| FFIObject::from_ffi_pointer(ObjectStateSink, pointer) },
142
+ &block
143
+ )
166
144
  end
167
145
 
168
- def ready(&block)
169
- object_state_sinks_array = with_pointer { |pointer| FFI::hakuban_tag_expose_contract_ready(pointer) }
170
- object_state_sinks = object_state_sinks_array[:pointer].read_array_of_pointer(object_state_sinks_array[:length]).map { |object_state_sinks_pointer|
171
- ObjectStateSink.send(:new, object_state_sinks_pointer)
172
- }
173
- FFI::hakuban_array_drop(object_state_sinks_array)
174
- if block
175
- Thread.handle_interrupt(Object => :never) {
176
- begin
177
- Thread.handle_interrupt(Object => :immediate) {
178
- block.call(object_state_sinks)
179
- }
180
- ensure
181
- object_state_sinks.each(&:drop)
182
- end
183
- }
184
- else
185
- object_state_sinks
186
- end
187
- rescue FFIObject::PointerAlreadyDropped
188
- end
189
-
190
- def terminate
191
- with_pointer { |pointer| FFI::hakuban_tag_expose_contract_terminate(pointer) }
192
- rescue FFIObject::PointerAlreadyDropped
193
- end
194
146
  end
195
147
 
196
148
  end
@@ -10,35 +10,31 @@ module Hakuban
10
10
  attr_reader :tags, :json
11
11
 
12
12
  def initialize(tags, json)
13
+ super()
13
14
  Hakuban::hakuban_initialize
14
15
  @json = json.freeze
15
16
  @tags = Set.new(tags.map { |tag| tag.kind_of?(TagDescriptor) ? tag : TagDescriptor.new(tag) })
16
17
  FFIObject.with_pointers(@tags) { |tag_pointers|
17
18
  tag_pointers_array = ::FFI::MemoryPointer.new(:pointer, tag_pointers.size)
18
19
  tag_pointers_array.write_array_of_pointer(tag_pointers)
19
- initialize_pointer(FFI::hakuban_object_descriptor_new(JSON.dump(@json),tag_pointers.size,tag_pointers_array).unwrap,:hakuban_object_descriptor_drop,:hakuban_object_descriptor_clone)
20
+ initialize_pointer(FFI::hakuban_object_descriptor_new(@json.to_json,tag_pointers.size,tag_pointers_array).unwrap,:hakuban_object_descriptor_drop,:hakuban_object_descriptor_clone)
20
21
  }
21
22
  end
22
23
 
24
+ def initialize_from_ffi_pointer(pointer)
25
+ super(pointer, :hakuban_object_descriptor_drop,:hakuban_object_descriptor_clone)
26
+ @json = JSON::parse(FFI::hakuban_object_descriptor_json(pointer).clone)
27
+ tags_array = FFI::hakuban_object_descriptor_tags(pointer)
28
+ @tags = tags_array[:pointer].read_array_of_pointer(tags_array[:length]).map { |tag_pointer|
29
+ FFIObject.from_ffi_pointer(TagDescriptor, FFI::hakuban_tag_descriptor_clone(tag_pointer))
30
+ }
31
+ end
23
32
 
24
33
  def initialize_copy(original)
25
34
  super
26
35
  @tags = original.tags.map(&:clone)
27
36
  end
28
37
 
29
-
30
- private_class_method def self.from_ffi_pointer(pointer)
31
- new_instance = allocate
32
- new_instance.send(:initialize_pointer, pointer, :hakuban_object_descriptor_drop,:hakuban_object_descriptor_clone)
33
- new_instance.instance_variable_set(:@json, JSON::parse(FFI::hakuban_object_descriptor_json(pointer).clone).freeze)
34
- tags_array = FFI::hakuban_object_descriptor_tags(pointer)
35
- tags = tags_array[:pointer].read_array_of_pointer(tags_array[:length]).map { |tag_pointer|
36
- TagDescriptor.send(:from_ffi_pointer, FFI::hakuban_tag_descriptor_clone(tag_pointer))
37
- }
38
- new_instance.instance_variable_set(:@tags, tags)
39
- new_instance
40
- end
41
-
42
38
  def ==(other)
43
39
  @tags == other.tags and @json == other.json
44
40
  end
@@ -60,16 +56,15 @@ module Hakuban
60
56
  attr_reader :json
61
57
 
62
58
  def initialize(json)
59
+ super()
63
60
  Hakuban::hakuban_initialize
64
61
  @json = json.freeze
65
- initialize_pointer(FFI::hakuban_tag_descriptor_new(JSON.dump(json)).unwrap, :hakuban_tag_descriptor_drop, :hakuban_tag_descriptor_clone)
62
+ initialize_pointer(FFI::hakuban_tag_descriptor_new(json.to_json).unwrap, :hakuban_tag_descriptor_drop, :hakuban_tag_descriptor_clone)
66
63
  end
67
64
 
68
- private_class_method def self.from_ffi_pointer(pointer)
69
- new_instance = allocate
70
- new_instance.send(:initialize_pointer, pointer, :hakuban_tag_descriptor_drop, :hakuban_tag_descriptor_clone)
71
- new_instance.instance_variable_set(:@json, JSON::parse(FFI::hakuban_tag_descriptor_json(pointer).clone))
72
- new_instance
65
+ def initialize_from_ffi_pointer(pointer)
66
+ super(pointer, :hakuban_tag_descriptor_drop,:hakuban_tag_descriptor_clone)
67
+ @json = JSON::parse(FFI::hakuban_tag_descriptor_json(pointer).clone)
73
68
  end
74
69
 
75
70
  def ==(other)
@@ -2,41 +2,85 @@ module Hakuban
2
2
 
3
3
  class Engine
4
4
 
5
- def initialize
6
- raise "Hakuban::Engine is abstract" if self.class == Hakuban::Engine
7
- @contracts = []
8
- end
9
-
10
-
11
- def start(exchange)
12
- @contracts.concat([init(exchange)])
13
- @contracts.flatten!
14
- @contracts.compact!
15
- @contracts.uniq!
16
- end
17
-
18
- def init(exchange)
19
- raise "Hakuban::Engine::init is a pure virtual function, descendants should bring their own implementation"
20
- end
21
-
22
- def stop
23
- @contracts.each { |contract|
24
- contract.drop
25
- }
26
- end
27
-
28
-
29
-
30
5
  @@descendants = []
6
+ def self.engines = @@descendants
7
+ def self.inherited(subclass) = @@descendants << subclass
31
8
 
9
+ Stop = Class.new(Exception)
32
10
 
33
- def self.engines
34
- @@descendants
11
+ #TODO: handle errors in Ractors
12
+ def run(exchange)
13
+ contract = contract(exchange)
14
+ while next_sink_or_stream = contract.next
15
+ if respond_to?(:handle_with_thread)
16
+ if contract.class < ObserveContract
17
+ Thread.new(next_sink_or_stream) do |sink_or_stream|
18
+ handle_with_thread(sink_or_stream)
19
+ rescue Object => error
20
+ $stderr.puts "#{self.class.name} exception: #{error.message}\n#{error.backtrace.join("\n")}\n"
21
+ contract.drop
22
+ ensure
23
+ sink_or_stream.drop
24
+ end
25
+ else
26
+ Thread.new(next_sink_or_stream) do |sink_or_stream|
27
+ thread = Thread.handle_interrupt(Object => :never) {
28
+ Thread.new do
29
+ Thread.handle_interrupt(Object => :immediate) {
30
+ handle_with_thread(sink_or_stream)
31
+ }
32
+ rescue Stop
33
+ ensure
34
+ sink_or_stream.drop
35
+ end
36
+ }
37
+ while sink_or_stream.next; end
38
+ thread.raise Hakuban::Engine::Stop
39
+ thread.join
40
+ rescue Object => error
41
+ $stderr.puts "#{self.class.name} exception: #{error.message}\n#{error.backtrace.join("\n")}\n"
42
+ contract.drop
43
+ end
44
+ end
45
+ elsif respond_to?(:handle_with_ractor)
46
+ raise "Ractors are not supported yet"
47
+ if contract.class < ObserveContract
48
+ Ractor.new(self, next_sink_or_stream) do |engine, sink_or_stream|
49
+ engine.handle_with_ractor(sink_or_stream)
50
+ ensure
51
+ sink_or_stream.drop
52
+ end
53
+ else
54
+ Ractor.new(self, next_sink_or_stream) do |engine, sink_or_stream|
55
+ thread = Thread.handle_interrupt(Object => :never) {
56
+ Thread.new do
57
+ Thread.handle_interrupt(Object => :immediate) {
58
+ handle_with_ractor(sink_or_stream)
59
+ }
60
+ rescue Stop
61
+ ensure
62
+ sink_or_stream.drop
63
+ end
64
+ }
65
+ while sink_or_stream.next; end
66
+ thread.raise Hakuban::Engine::Stop
67
+ thread.join
68
+ end
69
+ end
70
+ else
71
+ raise "No handler defined. Engine class should define one of the following methods: handle_with_thread, handle_with_ractor"
72
+ end
73
+ end
74
+ rescue Stop
75
+ ensure
76
+ contract.drop if contract
35
77
  end
36
78
 
37
79
 
38
- def self.inherited(subclass)
39
- @@descendants << subclass
80
+ def self::stop(thread)
81
+ thread.raise Hakuban::Engine::Stop
82
+ thread.value
83
+ rescue Stop
40
84
  end
41
85
 
42
86
  end