hakuban 0.6.4 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/{hakuban-thread-engine → hakuban-engine} +7 -6
- data/bin/hakuban-observer +39 -35
- data/lib/hakuban/contract.rb +196 -0
- data/lib/hakuban/descriptor.rb +90 -0
- data/lib/hakuban/engine.rb +3 -3
- data/lib/hakuban/exchange.rb +75 -0
- data/lib/hakuban/ffi-object.rb +132 -0
- data/lib/hakuban/ffi.rb +217 -141
- data/lib/hakuban/logger.rb +16 -0
- data/lib/hakuban/object_state.rb +106 -0
- data/lib/hakuban/object_state_sink.rb +64 -0
- data/lib/hakuban/object_state_stream.rb +39 -0
- data/lib/hakuban/stream.rb +119 -0
- data/lib/hakuban/tokio-websocket-connector.rb +32 -0
- data/lib/hakuban/version.rb +1 -1
- data/lib/hakuban.rb +21 -6
- metadata +29 -10
- data/lib/hakuban/async.rb +0 -32
- data/lib/hakuban/event-queue.rb +0 -75
- data/lib/hakuban/hakuban.rb +0 -545
- data/lib/hakuban/manager.rb +0 -400
- data/lib/hakuban/thread.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 73f369272bfa710ed6771c98f8ae2dc96986bff8937ee05a1a0019fa68ad11f6
|
4
|
+
data.tar.gz: 2495665b20ff9016201d09e22ece82414637cec6f501c79e4f6d80f3b11ca84f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6da6b69026da23cc2388dbaae4a3e5e431db7636c6c6ed1744ce0c1547088c80591df142f25d062f8b12b1cb87b193820787b6888657693d5403f3c2893a092
|
7
|
+
data.tar.gz: 0cf9bd05050f247eb58276952eca9c54dbe3f4d9da0fdca3dccc1c0eabc90e1663a4bead9a9e74dc26602b424d9289ad0d9ffdcdce0d4152516d49aee5da0200
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/bin/env ruby
|
2
2
|
|
3
3
|
require 'slop'
|
4
|
-
require 'hakuban
|
4
|
+
require 'hakuban'
|
5
5
|
require 'socket'
|
6
6
|
|
7
7
|
STDOUT.sync = true
|
@@ -9,7 +9,7 @@ STDOUT.sync = true
|
|
9
9
|
|
10
10
|
OPTIONS = Slop.parse { |o|
|
11
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', "
|
12
|
+
o.string '-n', '--name', "Exchange name (default: engine-PID)", default: "engine##{$$}@#{Socket.gethostname}"
|
13
13
|
o.array '-e', '--engines', "engines to start, a glob pattern matched against class name (default: *)", delimiter: ",", default: ["*"]
|
14
14
|
o.bool '-d', '--debug', 'Show debug messages'
|
15
15
|
o.on '-h', '--help' do puts o; exit end
|
@@ -17,8 +17,8 @@ OPTIONS = Slop.parse { |o|
|
|
17
17
|
|
18
18
|
|
19
19
|
Hakuban.logger_initialize("hakuban=debug") if OPTIONS.debug?
|
20
|
-
$
|
21
|
-
connector = Hakuban::WebsocketConnector.new($
|
20
|
+
$exchange = Hakuban::LocalExchange.new(name: OPTIONS["name"])
|
21
|
+
connector = Hakuban::WebsocketConnector.new($exchange, OPTIONS["connect"])
|
22
22
|
|
23
23
|
|
24
24
|
OPTIONS.arguments.each { |to_require|
|
@@ -31,9 +31,10 @@ started_engines = OPTIONS[:engines].map { |engine_name_pattern|
|
|
31
31
|
}
|
32
32
|
}.flatten.uniq.map { |engine_class|
|
33
33
|
engine_class.new
|
34
|
-
}.
|
35
|
-
engine.start($
|
34
|
+
}.map { |engine|
|
35
|
+
engine.start($exchange)
|
36
36
|
puts "Started engine: #{engine.class.name}"
|
37
|
+
engine
|
37
38
|
}
|
38
39
|
|
39
40
|
sleep
|
data/bin/hakuban-observer
CHANGED
@@ -1,64 +1,68 @@
|
|
1
1
|
#!/bin/env ruby
|
2
2
|
|
3
3
|
require 'slop'
|
4
|
-
require '
|
5
|
-
require '
|
4
|
+
require 'hakuban'
|
5
|
+
require 'set'
|
6
6
|
|
7
7
|
|
8
8
|
OPTIONS = Slop.parse { |o|
|
9
9
|
o.string '-c', '--connect', "Hakuban upstream address (default: ws://127.0.0.1:3001)", default: "ws://127.0.0.1:3001"
|
10
10
|
o.string '-o', '--object', "Object descriptor"
|
11
|
-
o.array '-t', '--tag', "Tag descriptor(s)"
|
11
|
+
o.array '-t', '--tag', "Tag descriptor(s)", delimiter: nil
|
12
12
|
o.bool '-d', '--debug', 'Show debug messages'
|
13
13
|
o.on '-h', '--help' do puts o; exit end
|
14
14
|
}
|
15
15
|
|
16
16
|
Hakuban.logger_initialize("hakuban=debug") if OPTIONS.debug?
|
17
|
-
|
18
|
-
connector = Hakuban::WebsocketConnector.new(
|
17
|
+
exchange = Hakuban::LocalExchange.new(name: OPTIONS["name"])
|
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
|
-
|
24
|
-
|
23
|
+
exchange.object(tags, json).observe
|
24
|
+
elsif (OPTIONS["tag"]&.size || 0) > 0
|
25
25
|
tag = OPTIONS["tag"].map { |tag| JSON.load(tag) }
|
26
|
-
|
26
|
+
exchange.tag(tag[0]).observe
|
27
|
+
else
|
28
|
+
puts "I have no idea what to observe :(\nPlease use -o or -t, or both."
|
29
|
+
exit 1
|
27
30
|
end
|
28
31
|
|
29
32
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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 '}'
|
33
|
+
def event_description(descriptor, event)
|
34
|
+
[
|
35
|
+
'',
|
36
|
+
'Event: %s'%[event],
|
37
|
+
'Descriptor JSON: %s'%[JSON.dump(descriptor.json)],
|
38
|
+
'Descriptor tags: [%s]'%[descriptor.tags.map { |tag| JSON.dump(tag.json) }.join(", ")],
|
39
|
+
].join("\n")
|
42
40
|
end
|
43
41
|
|
44
42
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
57
|
end
|
58
|
-
|
58
|
+
puts event_description(states_stream.descriptor, "drop")
|
59
|
+
states_stream.drop
|
60
|
+
threads.delete(Thread.current)
|
61
|
+
}
|
59
62
|
end
|
60
|
-
print_event(object.descriptor, "drop", nil)
|
61
63
|
}
|
62
64
|
|
63
65
|
$stdin.read(1)
|
64
|
-
|
66
|
+
|
67
|
+
observe_contract.terminate
|
68
|
+
threads.each { |thread| thread.join }
|
@@ -0,0 +1,196 @@
|
|
1
|
+
require 'hakuban/ffi-object.rb'
|
2
|
+
require 'hakuban/stream.rb'
|
3
|
+
|
4
|
+
|
5
|
+
module Hakuban
|
6
|
+
|
7
|
+
class Contract < FFIObject
|
8
|
+
|
9
|
+
attr_reader :descriptor
|
10
|
+
|
11
|
+
def inspect
|
12
|
+
"#<#{self.class.name} #{descriptor} #{self.dropped? ? "DROPPED" : ""}>"
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
class ObjectObserveContract < Contract
|
19
|
+
|
20
|
+
private_class_method :new
|
21
|
+
|
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)
|
27
|
+
}
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
public
|
32
|
+
|
33
|
+
include Stream
|
34
|
+
|
35
|
+
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
|
39
|
+
end
|
40
|
+
|
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
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
class ObjectExposeContract < Contract
|
56
|
+
|
57
|
+
private_class_method :new
|
58
|
+
|
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)
|
64
|
+
}
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
public
|
69
|
+
|
70
|
+
include Stream
|
71
|
+
|
72
|
+
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
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
class TagObserveContract < Contract
|
93
|
+
|
94
|
+
private_class_method :new
|
95
|
+
|
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)
|
101
|
+
}
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
public
|
106
|
+
|
107
|
+
include Stream
|
108
|
+
|
109
|
+
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
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
class TagExposeContract < Contract
|
146
|
+
|
147
|
+
private_class_method :new
|
148
|
+
|
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)
|
154
|
+
}
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
public
|
159
|
+
|
160
|
+
include Stream
|
161
|
+
|
162
|
+
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
|
166
|
+
end
|
167
|
+
|
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
|
+
end
|
195
|
+
|
196
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
require 'hakuban/ffi-object.rb'
|
4
|
+
|
5
|
+
|
6
|
+
module Hakuban
|
7
|
+
|
8
|
+
|
9
|
+
class ObjectDescriptor < FFIObject
|
10
|
+
attr_reader :tags, :json
|
11
|
+
|
12
|
+
def initialize(tags, json)
|
13
|
+
Hakuban::hakuban_initialize
|
14
|
+
@json = json.freeze
|
15
|
+
@tags = Set.new(tags.map { |tag| tag.kind_of?(TagDescriptor) ? tag : TagDescriptor.new(tag) })
|
16
|
+
FFIObject.with_pointers(@tags) { |tag_pointers|
|
17
|
+
tag_pointers_array = ::FFI::MemoryPointer.new(:pointer, tag_pointers.size)
|
18
|
+
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
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def initialize_copy(original)
|
25
|
+
super
|
26
|
+
@tags = original.tags.map(&:clone)
|
27
|
+
end
|
28
|
+
|
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
|
+
def ==(other)
|
43
|
+
@tags == other.tags and @json == other.json
|
44
|
+
end
|
45
|
+
|
46
|
+
alias eql? ==
|
47
|
+
|
48
|
+
def hash
|
49
|
+
[@tags.hash, @json.hash].hash
|
50
|
+
end
|
51
|
+
|
52
|
+
def inspect
|
53
|
+
"#<ObjectDescriptor @tags={%s}, @json=%p>"%[self.tags.map(&:inspect).join(","), self.json]
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
class TagDescriptor < FFIObject
|
60
|
+
attr_reader :json
|
61
|
+
|
62
|
+
def initialize(json)
|
63
|
+
Hakuban::hakuban_initialize
|
64
|
+
@json = json.freeze
|
65
|
+
initialize_pointer(FFI::hakuban_tag_descriptor_new(JSON.dump(json)).unwrap, :hakuban_tag_descriptor_drop, :hakuban_tag_descriptor_clone)
|
66
|
+
end
|
67
|
+
|
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
|
73
|
+
end
|
74
|
+
|
75
|
+
def ==(other)
|
76
|
+
@json == other.json
|
77
|
+
end
|
78
|
+
|
79
|
+
alias eql? ==
|
80
|
+
|
81
|
+
def hash
|
82
|
+
@json.hash
|
83
|
+
end
|
84
|
+
|
85
|
+
def inspect
|
86
|
+
"#<TagDescriptor @json=%p>"%[self.json]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
data/lib/hakuban/engine.rb
CHANGED
@@ -8,14 +8,14 @@ module Hakuban
|
|
8
8
|
end
|
9
9
|
|
10
10
|
|
11
|
-
def start(
|
12
|
-
@contracts.concat([init(
|
11
|
+
def start(exchange)
|
12
|
+
@contracts.concat([init(exchange)])
|
13
13
|
@contracts.flatten!
|
14
14
|
@contracts.compact!
|
15
15
|
@contracts.uniq!
|
16
16
|
end
|
17
17
|
|
18
|
-
def init(
|
18
|
+
def init(exchange)
|
19
19
|
raise "Hakuban::Engine::init is a pure virtual function, descendants should bring their own implementation"
|
20
20
|
end
|
21
21
|
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'hakuban/ffi-object.rb'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
module Hakuban
|
5
|
+
|
6
|
+
class LocalExchange < FFIObject
|
7
|
+
|
8
|
+
def initialize(name: false, &block)
|
9
|
+
Hakuban::hakuban_initialize
|
10
|
+
Hakuban::logger_initialize("hakuban=warn", skip_if_already_initialized: true)
|
11
|
+
name = LocalExchange.default_name if name == false
|
12
|
+
pointer = FFI::hakuban_local_exchange_new(name).unwrap
|
13
|
+
initialize_pointer(pointer, :hakuban_local_exchange_drop, :hakuban_local_exchange_clone)
|
14
|
+
self.do_and_drop_or_return(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.default_name
|
18
|
+
"#{Socket.gethostname}:#{File.basename(caller_locations(0..1)[1].path)}:#{$$}"
|
19
|
+
end
|
20
|
+
|
21
|
+
public
|
22
|
+
|
23
|
+
def object(tags, descriptor=nil)
|
24
|
+
if tags.kind_of? ObjectDescriptor
|
25
|
+
ObjectBuilder.new(self, tags)
|
26
|
+
else
|
27
|
+
ObjectBuilder.new(self, ObjectDescriptor.new(tags, descriptor))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def tag(descriptor)
|
32
|
+
if descriptor.kind_of? TagDescriptor
|
33
|
+
TagBuilder.new(self, descriptor)
|
34
|
+
else
|
35
|
+
TagBuilder.new(self, TagDescriptor.new(descriptor))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
class ObjectBuilder
|
43
|
+
|
44
|
+
def initialize(store, descriptor)
|
45
|
+
@store, @descriptor = store, descriptor
|
46
|
+
end
|
47
|
+
|
48
|
+
def observe(&block)
|
49
|
+
ObjectObserveContract.send(:new, @store, @descriptor).do_and_drop_or_return(&block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def expose(&block)
|
53
|
+
ObjectExposeContract.send(:new, @store, @descriptor).do_and_drop_or_return(&block)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
class TagBuilder
|
60
|
+
|
61
|
+
def initialize(store, descriptor)
|
62
|
+
@store, @descriptor = store, descriptor
|
63
|
+
end
|
64
|
+
|
65
|
+
def observe(&block)
|
66
|
+
TagObserveContract.send(:new, @store, @descriptor).do_and_drop_or_return(&block)
|
67
|
+
end
|
68
|
+
|
69
|
+
def expose(&block)
|
70
|
+
TagExposeContract.send(:new, @store, @descriptor).do_and_drop_or_return(&block)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
|
4
|
+
module Hakuban
|
5
|
+
|
6
|
+
class FFIObject
|
7
|
+
|
8
|
+
class PointerAlreadyDropped < Exception; end
|
9
|
+
|
10
|
+
private def initialize_pointer(pointer, drop_fn, clone_fn)
|
11
|
+
@pointer, @drop_fn, @clone_fn = pointer.address, drop_fn, clone_fn
|
12
|
+
@mutex ||= Mutex.new
|
13
|
+
@drop_requested = false
|
14
|
+
@drop_locked_by = Set.new
|
15
|
+
@drop_locked_by_condvar = nil
|
16
|
+
ObjectSpace.define_finalizer(self, FFIObject::generate_finalizer(drop_fn, @pointer))
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def initialize_copy(original)
|
21
|
+
raise "#{self.class.name} can't be cloned. Consider making a new one instead." if @clone_fn.nil?
|
22
|
+
@mutex.synchronize {
|
23
|
+
raise "#{self.class.name} can't be cloned after 'drop' has been called on it." if @pointer.nil?
|
24
|
+
@pointer = FFI::method(@clone_fn).call(::FFI::Pointer.new(@pointer)).address
|
25
|
+
@mutex = Mutex.new
|
26
|
+
@drop_requested = false
|
27
|
+
@drop_locked_by = Set.new
|
28
|
+
@drop_locked_by_condvar = nil
|
29
|
+
ObjectSpace.undefine_finalizer(self)
|
30
|
+
ObjectSpace.define_finalizer(self, FFIObject::generate_finalizer(@drop_fn, @pointer))
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def with_pointer
|
36
|
+
@mutex.synchronize {
|
37
|
+
raise PointerAlreadyDropped if dropped?
|
38
|
+
yield ::FFI::Pointer.new(@pointer)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def self.with_pointers(objects,&original_block)
|
44
|
+
do_locked = proc { |original_block, remaining_objects, pointers|
|
45
|
+
if remaining_objects.size == 0
|
46
|
+
original_block.call(pointers)
|
47
|
+
else
|
48
|
+
object, i = remaining_objects.shift
|
49
|
+
object.with_pointer { |pointer|
|
50
|
+
pointers[i] = pointer
|
51
|
+
do_locked.call(original_block, remaining_objects, pointers)
|
52
|
+
}
|
53
|
+
end
|
54
|
+
}
|
55
|
+
do_locked.call(original_block, objects.each.with_index.sort_by { |object, i| object.instance_variable_get(:@pointer) || -i }, Array.new(objects.size))
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def drop
|
60
|
+
@mutex.synchronize {
|
61
|
+
#TODO: wait here till dropped?
|
62
|
+
return if @drop_requested
|
63
|
+
@drop_requested = true
|
64
|
+
@drop_locked_by.each { |queue| queue << :dropping }
|
65
|
+
@drop_locked_by_condvar ||= ConditionVariable.new
|
66
|
+
@drop_locked_by_condvar.wait(@mutex) while @drop_locked_by.size > 0
|
67
|
+
return if dropped?
|
68
|
+
ObjectSpace.undefine_finalizer(self)
|
69
|
+
FFI::method(@drop_fn).call(::FFI::Pointer.new(@pointer))
|
70
|
+
@pointer = @finalizer = nil
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
def drop_lock(queue)
|
76
|
+
@mutex.synchronize {
|
77
|
+
raise FFIObject::PointerAlreadyDropped if @drop_requested
|
78
|
+
@drop_locked_by << queue
|
79
|
+
}
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def drop_release(queue)
|
85
|
+
@mutex.synchronize {
|
86
|
+
@drop_locked_by.delete(queue)
|
87
|
+
@drop_locked_by_condvar.broadcast if @drop_locked_by_condvar
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
def dropped?
|
93
|
+
@pointer.nil?
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
def self.generate_finalizer(symbol, pointer_address)
|
98
|
+
proc { |_|
|
99
|
+
FFI::method(symbol).call(::FFI::Pointer.new(pointer_address))
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
def do_and_drop
|
105
|
+
Thread.handle_interrupt(Object => :never) {
|
106
|
+
begin
|
107
|
+
Thread.handle_interrupt(Object => :immediate) {
|
108
|
+
yield self
|
109
|
+
}
|
110
|
+
ensure
|
111
|
+
self.drop
|
112
|
+
end
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
def do_and_drop_or_return(&block)
|
118
|
+
if block
|
119
|
+
do_and_drop(&block)
|
120
|
+
else
|
121
|
+
self
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
def inspect
|
127
|
+
"#<#{self.class.name} #{self.dropped? ? "DROPPED" : "%016X"%@pointer}>"
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|