hakuban 0.6.5 → 0.7.0

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: 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