hakuban 0.6.5 → 0.8.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: 79f0597271e97381de53272d50c3878a56b816ff24947aedca9e7c309b2f2490
4
+ data.tar.gz: e845a290fa0e13548ead5d04110720fb8f6ffa63966201702c1e0a02b116433e
5
5
  SHA512:
6
- metadata.gz: 95ba2b481c476d7b5640a929531ee9e0b1cffaaed4968970a063bfe718a6cbe99151cc3b2c85cadef2227ea6ba3acf9264e9f10a2dc44077c3dee9b389639dcc
7
- data.tar.gz: '0945092f4ebe7b07dadc6288ad39e05bc7a6ca282779b9b12f92f5427f233d651bc61337b272bed9e20186696e0359ba0d8431372d5bb56565a22056dfe357c7'
6
+ metadata.gz: 2002cf8603e02ce094bc3220a3cd28af3ef55695b8cefe232cd76cdd8543c5e02e894c456fd84e740ad16fba4253dacc670254d8ee991335fc0e3bc70d664156
7
+ data.tar.gz: 32dcf7d561c69d6d6ff2b53c695f2978a324789b90766224eb468cfd71c0709c76231ee30d03bdf2bfed6f403c050b299de18a24221781e1d89b0bd8d8e3e6b0
@@ -1,15 +1,14 @@
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
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', "Node 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,29 +16,31 @@ OPTIONS = Slop.parse { |o|
17
16
 
18
17
 
19
18
  Hakuban.logger_initialize("hakuban=debug") if OPTIONS.debug?
20
- $hakuban = Hakuban::LocalNode.new(name: OPTIONS["name"])
21
- connector = Hakuban::WebsocketConnector.new($hakuban, 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
- }.each { |engine|
35
- engine.start($hakuban)
36
- puts "Started engine: #{engine.class.name}"
33
+ }.map { |engine|
34
+ Thread.new {
35
+ puts "Starting engine: #{engine.class.name}"
36
+ engine.run(exchange)
37
+ }
37
38
  }
38
39
 
39
40
  sleep
40
41
 
41
- started_engines.each { |engine|
42
- engine.stop
42
+ engine_threads.each { |thread|
43
+ Hakuban::Engine.stop(thread)
43
44
  puts "Stopped engine: #{engine.class.name}"
44
45
  }
45
46
 
@@ -0,0 +1,61 @@
1
+ #!/bin/env ruby
2
+
3
+ require 'slop'
4
+ require 'hakuban'
5
+ require 'set'
6
+
7
+
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#name=observer"
10
+ o.string '-o', '--object', "Object descriptor"
11
+ o.array '-t', '--tag', "Tag descriptor(s)", delimiter: nil
12
+ o.bool '-d', '--debug', 'Show debug messages'
13
+ o.on '-h', '--help' do puts o; exit end
14
+ }
15
+
16
+ Hakuban.logger_initialize("hakuban=debug") if OPTIONS.debug?
17
+ exchange = Hakuban::Exchange.new()
18
+ connector = Hakuban::WebsocketConnector.new(exchange, OPTIONS["connect"])
19
+
20
+ observe_contract = if OPTIONS["object"]
21
+ json = JSON.load(OPTIONS["object"])
22
+ tags = OPTIONS["tag"].map { |tag| JSON.load(tag) }
23
+ exchange.object_observe_contract(tags, json).build
24
+ elsif (OPTIONS["tag"]&.size || 0) > 0
25
+ tag = OPTIONS["tag"].map { |tag| JSON.load(tag) }
26
+ exchange.tag_observe_contract(tag[0]).build
27
+ else
28
+ puts "I have no idea what to observe :(\nPlease use -o or -t, or both."
29
+ exit 1
30
+ end
31
+
32
+
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")
40
+ end
41
+
42
+
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")
55
+ }
56
+
57
+
58
+
59
+ sleep
60
+
61
+ observe_contract.drop
@@ -0,0 +1,148 @@
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
+ class ObserveContract < Contract; end
18
+ class ExposeContract < Contract; end
19
+
20
+ class ObjectObserveContract < ObserveContract
21
+
22
+ private_class_method :new
23
+
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
+ }
32
+ }
33
+ do_and_drop_or_return(&block)
34
+ }
35
+ end
36
+
37
+ public
38
+
39
+ include Stream
40
+
41
+ def next(&block)
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
+ )
47
+ end
48
+
49
+
50
+ end
51
+
52
+
53
+ class ObjectExposeContract < ExposeContract
54
+
55
+ private_class_method :new
56
+
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
+ }
65
+ }
66
+ do_and_drop_or_return(&block)
67
+ }
68
+ end
69
+
70
+ public
71
+
72
+ include Stream
73
+
74
+ def next(&block)
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
+ )
80
+ end
81
+
82
+ end
83
+
84
+
85
+ class TagObserveContract < ObserveContract
86
+
87
+ private_class_method :new
88
+
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
+ }
97
+ }
98
+ do_and_drop_or_return(&block)
99
+ }
100
+ end
101
+
102
+ public
103
+
104
+ include Stream
105
+
106
+ def next(&block)
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
+ )
112
+ end
113
+
114
+ end
115
+
116
+
117
+ class TagExposeContract < ExposeContract
118
+
119
+ private_class_method :new
120
+
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
+ }
129
+ }
130
+ do_and_drop_or_return(&block)
131
+ }
132
+ end
133
+
134
+ public
135
+
136
+ include Stream
137
+
138
+ def next(&block)
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
+ )
144
+ end
145
+
146
+ end
147
+
148
+ end
@@ -0,0 +1,85 @@
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
+ super()
14
+ Hakuban::hakuban_initialize
15
+ @json = json.freeze
16
+ @tags = Set.new(tags.map { |tag| tag.kind_of?(TagDescriptor) ? tag : TagDescriptor.new(tag) })
17
+ FFIObject.with_pointers(@tags) { |tag_pointers|
18
+ tag_pointers_array = ::FFI::MemoryPointer.new(:pointer, tag_pointers.size)
19
+ tag_pointers_array.write_array_of_pointer(tag_pointers)
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)
21
+ }
22
+ end
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
32
+
33
+ def initialize_copy(original)
34
+ super
35
+ @tags = original.tags.map(&:clone)
36
+ end
37
+
38
+ def ==(other)
39
+ @tags == other.tags and @json == other.json
40
+ end
41
+
42
+ alias eql? ==
43
+
44
+ def hash
45
+ [@tags.hash, @json.hash].hash
46
+ end
47
+
48
+ def inspect
49
+ "#<ObjectDescriptor @tags={%s}, @json=%p>"%[self.tags.map(&:inspect).join(","), self.json]
50
+ end
51
+
52
+ end
53
+
54
+
55
+ class TagDescriptor < FFIObject
56
+ attr_reader :json
57
+
58
+ def initialize(json)
59
+ super()
60
+ Hakuban::hakuban_initialize
61
+ @json = json.freeze
62
+ initialize_pointer(FFI::hakuban_tag_descriptor_new(json.to_json).unwrap, :hakuban_tag_descriptor_drop, :hakuban_tag_descriptor_clone)
63
+ end
64
+
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)
68
+ end
69
+
70
+ def ==(other)
71
+ @json == other.json
72
+ end
73
+
74
+ alias eql? ==
75
+
76
+ def hash
77
+ @json.hash
78
+ end
79
+
80
+ def inspect
81
+ "#<TagDescriptor @json=%p>"%[self.json]
82
+ end
83
+ end
84
+
85
+ end
@@ -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(hakuban)
12
- @contracts.concat([init(hakuban)])
13
- @contracts.flatten!
14
- @contracts.compact!
15
- @contracts.uniq!
16
- end
17
-
18
- def init(hakuban)
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
@@ -0,0 +1,115 @@
1
+ require 'hakuban/ffi-object.rb'
2
+ require 'socket'
3
+
4
+ module Hakuban
5
+
6
+ class Exchange < FFIObject
7
+
8
+ def initialize(&block)
9
+ super()
10
+ Hakuban::hakuban_initialize
11
+ Hakuban::logger_initialize("hakuban=warn", skip_if_already_initialized: true)
12
+ Thread.handle_interrupt(Object => :never) {
13
+ pointer = FFI::hakuban_exchange_new().unwrap
14
+ initialize_pointer(pointer, :hakuban_exchange_drop, :hakuban_exchange_clone)
15
+ self.do_and_drop_or_return(&block)
16
+ }
17
+ end
18
+
19
+ public
20
+
21
+ def object_observe_contract(tags, descriptor=nil)
22
+ if tags.kind_of? ObjectDescriptor
23
+ ObjectObserveContractBuilder.new(self, tags)
24
+ else
25
+ ObjectObserveContractBuilder.new(self, ObjectDescriptor.new(tags, descriptor))
26
+ end
27
+ end
28
+
29
+ def object_expose_contract(tags, descriptor=nil)
30
+ if tags.kind_of? ObjectDescriptor
31
+ ObjectExposeContractBuilder.new(self, tags)
32
+ else
33
+ ObjectExposeContractBuilder.new(self, ObjectDescriptor.new(tags, descriptor))
34
+ end
35
+ end
36
+
37
+ def tag_observe_contract(descriptor)
38
+ if descriptor.kind_of? TagDescriptor
39
+ TagObserveContractBuilder.new(self, descriptor)
40
+ else
41
+ TagObserveContractBuilder.new(self, TagDescriptor.new(descriptor))
42
+ end
43
+ end
44
+
45
+ def tag_expose_contract(descriptor)
46
+ if descriptor.kind_of? TagDescriptor
47
+ TagExposeContractBuilder.new(self, descriptor)
48
+ else
49
+ TagExposeContractBuilder.new(self, TagDescriptor.new(descriptor))
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+
56
+ class ObjectObserveContractBuilder
57
+
58
+ def initialize(exchange, descriptor)
59
+ @exchange, @descriptor = exchange, descriptor
60
+ end
61
+
62
+ def build(&block)
63
+ ObjectObserveContract.send(:new, @exchange, @descriptor, &block)
64
+ end
65
+
66
+ end
67
+
68
+ class ObjectExposeContractBuilder
69
+
70
+ def initialize(exchange, descriptor)
71
+ @exchange, @descriptor = exchange, descriptor
72
+ @capacity = 1
73
+ end
74
+
75
+ def with_capacity(capacity)
76
+ @capacity = capacity
77
+ end
78
+
79
+ def build(&block)
80
+ ObjectExposeContract.send(:new, @exchange, @descriptor, @capacity, &block)
81
+ end
82
+
83
+ end
84
+
85
+
86
+ class TagObserveContractBuilder
87
+
88
+ def initialize(exchange, descriptor)
89
+ @exchange, @descriptor = exchange, descriptor
90
+ end
91
+
92
+ def build(&block)
93
+ TagObserveContract.send(:new, @exchange, @descriptor, &block)
94
+ end
95
+
96
+ end
97
+
98
+ class TagExposeContractBuilder
99
+
100
+ def initialize(exchange, descriptor)
101
+ @exchange, @descriptor = exchange, descriptor
102
+ @capacity = 1
103
+ end
104
+
105
+ def with_capacity(capacity)
106
+ @capacity = capacity
107
+ end
108
+
109
+ def build(&block)
110
+ TagExposeContract.send(:new, @exchange, @descriptor, @capacity, &block)
111
+ end
112
+
113
+ end
114
+
115
+ end