hakuban 0.6.5 → 0.7.0
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 +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 -38
- data/lib/hakuban/event-queue.rb +0 -75
- data/lib/hakuban/hakuban.rb +0 -545
- data/lib/hakuban/manager.rb +0 -398
- data/lib/hakuban/thread.rb +0 -35
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
|