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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b96f9b3980a477679e6500a226142873db6c0569706e2eae58b29c04b8a956e9
4
- data.tar.gz: c66a0a751a8d95bbd5944edb8c5c9a2cdbd5f1dbc5f8094277357cb4aac146e0
3
+ metadata.gz: 73f369272bfa710ed6771c98f8ae2dc96986bff8937ee05a1a0019fa68ad11f6
4
+ data.tar.gz: 2495665b20ff9016201d09e22ece82414637cec6f501c79e4f6d80f3b11ca84f
5
5
  SHA512:
6
- metadata.gz: 95ba2b481c476d7b5640a929531ee9e0b1cffaaed4968970a063bfe718a6cbe99151cc3b2c85cadef2227ea6ba3acf9264e9f10a2dc44077c3dee9b389639dcc
7
- data.tar.gz: '0945092f4ebe7b07dadc6288ad39e05bc7a6ca282779b9b12f92f5427f233d651bc61337b272bed9e20186696e0359ba0d8431372d5bb56565a22056dfe357c7'
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/thread'
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', "Node name (default: engine-PID)", default: "engine##{$$}@#{Socket.gethostname}"
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
- $hakuban = Hakuban::LocalNode.new(name: OPTIONS["name"])
21
- connector = Hakuban::WebsocketConnector.new($hakuban, OPTIONS["connect"])
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
- }.each { |engine|
35
- engine.start($hakuban)
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 'pp'
5
- require 'hakuban/thread'
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
- hakuban = Hakuban::LocalNode.new(name: OPTIONS["name"])
18
- connector = Hakuban::WebsocketConnector.new(hakuban, OPTIONS["connect"])
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
- hakuban.object(tags, json).observe
24
- else
23
+ exchange.object(tags, json).observe
24
+ elsif (OPTIONS["tag"]&.size || 0) > 0
25
25
  tag = OPTIONS["tag"].map { |tag| JSON.load(tag) }
26
- hakuban.tag(tag[0]).observe
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 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 '}'
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
- 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",
51
- if state = object.state
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")
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
- puts "]"
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
@@ -8,14 +8,14 @@ module Hakuban
8
8
  end
9
9
 
10
10
 
11
- def start(hakuban)
12
- @contracts.concat([init(hakuban)])
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(hakuban)
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