griffin 0.1.0 → 0.1.1

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.
@@ -0,0 +1,37 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: routeguide.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ Google::Protobuf::DescriptorPool.generated_pool.build do
7
+ add_message "routeguide.Point" do
8
+ optional :latitude, :int32, 1
9
+ optional :longitude, :int32, 2
10
+ end
11
+ add_message "routeguide.Rectangle" do
12
+ optional :lo, :message, 1, "routeguide.Point"
13
+ optional :hi, :message, 2, "routeguide.Point"
14
+ end
15
+ add_message "routeguide.Feature" do
16
+ optional :name, :string, 1
17
+ optional :location, :message, 2, "routeguide.Point"
18
+ end
19
+ add_message "routeguide.RouteNote" do
20
+ optional :location, :message, 1, "routeguide.Point"
21
+ optional :message, :string, 2
22
+ end
23
+ add_message "routeguide.RouteSummary" do
24
+ optional :point_count, :int32, 1
25
+ optional :feature_count, :int32, 2
26
+ optional :distance, :int32, 3
27
+ optional :elapsed_time, :int32, 4
28
+ end
29
+ end
30
+
31
+ module Routeguide
32
+ Point = Google::Protobuf::DescriptorPool.generated_pool.lookup("routeguide.Point").msgclass
33
+ Rectangle = Google::Protobuf::DescriptorPool.generated_pool.lookup("routeguide.Rectangle").msgclass
34
+ Feature = Google::Protobuf::DescriptorPool.generated_pool.lookup("routeguide.Feature").msgclass
35
+ RouteNote = Google::Protobuf::DescriptorPool.generated_pool.lookup("routeguide.RouteNote").msgclass
36
+ RouteSummary = Google::Protobuf::DescriptorPool.generated_pool.lookup("routeguide.RouteSummary").msgclass
37
+ end
@@ -0,0 +1,61 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # Source: routeguide.proto for package 'routeguide'
3
+ # Original file comments:
4
+ # Copyright 2015 gRPC authors.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'grpc'
20
+ require 'routeguide_pb'
21
+
22
+ module Routeguide
23
+ module RouteGuide
24
+ # Interface exported by the server.
25
+ class Service
26
+
27
+ include GRPC::GenericService
28
+
29
+ self.marshal_class_method = :encode
30
+ self.unmarshal_class_method = :decode
31
+ self.service_name = 'routeguide.RouteGuide'
32
+
33
+ # A simple RPC.
34
+ #
35
+ # Obtains the feature at a given position.
36
+ #
37
+ # A feature with an empty name is returned if there's no feature at the given
38
+ # position.
39
+ rpc :GetFeature, Point, Feature
40
+ # A server-to-client streaming RPC.
41
+ #
42
+ # Obtains the Features available within the given Rectangle. Results are
43
+ # streamed rather than returned at once (e.g. in a response message with a
44
+ # repeated field), as the rectangle may cover a large area and contain a
45
+ # huge number of features.
46
+ rpc :ListFeatures, Rectangle, stream(Feature)
47
+ # A client-to-server streaming RPC.
48
+ #
49
+ # Accepts a stream of Points on a route being traversed, returning a
50
+ # RouteSummary when traversal is completed.
51
+ rpc :RecordRoute, stream(Point), RouteSummary
52
+ # A Bidirectional streaming RPC.
53
+ #
54
+ # Accepts a stream of RouteNotes sent while a route is being traversed,
55
+ # while receiving other RouteNotes (e.g. from other users).
56
+ rpc :RouteChat, stream(RouteNote), stream(RouteNote)
57
+ end
58
+
59
+ Stub = Service.rpc_stub_class
60
+ end
61
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Style/GlobalVars
4
+
5
+ $LOAD_PATH.unshift File.expand_path('./examples/routeguide')
6
+
7
+ require 'grpc_kit'
8
+ require 'pry'
9
+ require 'json'
10
+ require 'logger'
11
+ require 'routeguide_services_pb'
12
+
13
+ RESOURCE_PATH = './examples/routeguide/routeguide.json'
14
+
15
+ $logger = Logger.new(STDOUT)
16
+
17
+ def get_feature(stub)
18
+ $logger.info('===== get_feature =====')
19
+ points = [
20
+ Routeguide::Point.new(latitude: 409_146_138, longitude: -746_188_906),
21
+ Routeguide::Point.new(latitude: 0, longitude: 0)
22
+ ]
23
+
24
+ points.each do |pt|
25
+ feature = stub.get_feature(pt, metadata: { 'metadata' => 'data1' })
26
+ if feature.name == ''
27
+ $logger.info("Found nothing at #{feature.inspect}")
28
+ else
29
+ $logger.info("Found '#{feature.name}' at #{feature.location.inspect}")
30
+ end
31
+ end
32
+ end
33
+
34
+ def list_features(stub)
35
+ $logger.info('===== list_features =====')
36
+ rect = Routeguide::Rectangle.new(
37
+ lo: Routeguide::Point.new(latitude: 400_000_000, longitude: -750_000_000),
38
+ hi: Routeguide::Point.new(latitude: 420_000_000, longitude: -730_000_000),
39
+ )
40
+
41
+ stream = stub.list_features(rect)
42
+
43
+ loop do
44
+ r = stream.recv
45
+ $logger.info("Found #{r.name} at #{r.location.inspect}")
46
+ end
47
+ end
48
+
49
+ def record_route(stub, size)
50
+ features = File.open(RESOURCE_PATH) do |f|
51
+ JSON.parse(f.read)
52
+ end
53
+
54
+ stream = stub.record_route({})
55
+
56
+ size.times do
57
+ location = features.sample['location']
58
+ point = Routeguide::Point.new(latitude: location['latitude'], longitude: location['longitude'])
59
+ puts "Next point is #{point.inspect}"
60
+ stream.send_msg(point)
61
+ sleep(rand(0..1))
62
+ end
63
+
64
+ resp = stream.close_and_recv
65
+ puts "summary: #{resp[0].inspect}"
66
+ end
67
+
68
+ opts = {}
69
+
70
+ if ENV['GRPC_INTERCEPTOR']
71
+ require_relative 'interceptors/client_logging_interceptor'
72
+ opts[:interceptors] = [LoggingInterceptor.new]
73
+ elsif ENV['GRPC_TIMEOUT']
74
+ opts[:timeout] = Integer(ENV['GRPC_TIMEOUT'])
75
+ end
76
+
77
+ stub = Routeguide::RouteGuide::Stub.new('localhost', 50051, **opts)
78
+
79
+ get_feature(stub)
80
+ list_features(stub)
81
+ record_route(stub, 10)
82
+
83
+ # rubocop:enable Style/GlobalVars
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path('./examples/routeguide')
4
+
5
+ require 'grpc_kit'
6
+ require 'pry'
7
+ require 'json'
8
+ require 'routeguide_services_pb'
9
+ require 'logger'
10
+
11
+ class Server < Routeguide::RouteGuide::Service
12
+ RESOURCE_PATH = './examples/routeguide/routeguide.json'
13
+
14
+ def initialize
15
+ @logger = Logger.new(STDOUT)
16
+ File.open(RESOURCE_PATH) do |f|
17
+ features = JSON.parse(f.read)
18
+ @features = Hash[features.map { |x| [x['location'], x['name']] }]
19
+ end
20
+ end
21
+
22
+ def get_feature(point, ctx)
23
+ name = @features.fetch({ 'longitude' => point.longitude, 'latitude' => point.latitude }, '')
24
+ @logger.info("Point longitude=#{point.longitude}, latitude=#{point.latitude}, metadata=#{ctx.metadata}")
25
+ Routeguide::Feature.new(location: point, name: name)
26
+ end
27
+
28
+ def list_features(rect, stream)
29
+ @logger.info('===== list_features =====')
30
+
31
+ @features.each do |location, name|
32
+ if name.nil? || name == '' || !in_range(location, rect)
33
+ next
34
+ end
35
+
36
+ pt = Routeguide::Point.new(location)
37
+ resp = Routeguide::Feature.new(location: pt, name: name)
38
+ @logger.info(resp)
39
+ stream.send_msg(resp)
40
+ end
41
+ end
42
+
43
+ def record_route(stream)
44
+ @logger.info('===== record_route =====')
45
+ distance = 0
46
+ count = 0
47
+ features = 0
48
+ start_at = Time.now.to_i
49
+ last = nil
50
+
51
+ loop do
52
+ point = stream.recv # XXX: raise StopIteration
53
+ @logger.info(point)
54
+
55
+ count += 1
56
+ name = @features.fetch({ 'longitude' => point.longitude, 'latitude' => point.latitude }, '')
57
+ unless name == ''
58
+ features += 1
59
+ end
60
+
61
+ last = point
62
+ distance += calculate_distance(point, last)
63
+ end
64
+
65
+ Routeguide::RouteSummary.new(
66
+ point_count: count,
67
+ feature_count: features,
68
+ distance: distance,
69
+ elapsed_time: Time.now.to_i - start_at,
70
+ )
71
+ end
72
+
73
+ private
74
+
75
+ COORD_FACTOR = 1e7
76
+ RADIUS = 637_100
77
+
78
+ def calculate_distance(point_a, point_b)
79
+ lat_a = (point_a.latitude / COORD_FACTOR) * Math::PI / 180
80
+ lat_b = (point_b.latitude / COORD_FACTOR) * Math::PI / 180
81
+ lon_a = (point_a.longitude / COORD_FACTOR) * Math::PI / 180
82
+ lon_b = (point_b.longitude / COORD_FACTOR) * Math::PI / 180
83
+
84
+ delta_lat = lat_a - lat_b
85
+ delta_lon = lon_a - lon_b
86
+ a = Math.sin(delta_lat / 2)**2 + Math.cos(lat_a) * Math.cos(lat_b) + Math.sin(delta_lon / 2)**2
87
+ (2 * RADIUS * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))).to_i
88
+ end
89
+
90
+ def in_range(point, rect)
91
+ longitudes = [rect.lo.longitude, rect.hi.longitude]
92
+ left = longitudes.min
93
+ right = longitudes.max
94
+
95
+ latitudes = [rect.lo.latitude, rect.hi.latitude]
96
+ bottom = latitudes.min
97
+ top = latitudes.max
98
+ (point['longitude'] >= left) && (point['longitude'] <= right) && (point['latitude'] >= bottom) && (point['latitude'] <= top)
99
+ end
100
+ end
101
+
102
+ sock = TCPServer.new(50051)
103
+
104
+ opts = {}
105
+
106
+ if ENV['GRPC_INTERCEPTOR']
107
+ require_relative 'interceptors/server_logging_interceptor'
108
+ opts[:interceptors] = [LoggingInterceptor.new]
109
+ end
110
+
111
+ server = GrpcKit::Server.new(**opts)
112
+ server.handle(Server.new)
113
+ server.run
114
+
115
+ loop do
116
+ conn = sock.accept
117
+ server.session_start(conn)
118
+ end
data/griffin.gemspec CHANGED
@@ -1,27 +1,33 @@
1
+ # frozen_string_literal: true
1
2
 
2
- lib = File.expand_path("../lib", __FILE__)
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "griffin/version"
5
+ require 'griffin/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "griffin"
8
+ spec.name = 'griffin'
8
9
  spec.version = Griffin::VERSION
9
10
  spec.authors = ['ganmacs']
10
11
  spec.email = ['ganmacs@gmail.com']
11
12
 
12
- spec.summary = ""
13
- spec.description = ""
14
- spec.homepage = "https://github.com/ganmacs/griffin"
15
- spec.license = "MIT"
13
+ spec.summary = 'Send and Receive RPCs from Ruby'
14
+ spec.description = 'Send and Receive RPCs from Ruby'
15
+ spec.homepage = 'https://github.com/ganmacs/griffin'
16
+ spec.license = 'MIT'
16
17
 
17
18
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
19
  f.match(%r{^(test|spec|features)/})
19
20
  end
20
- spec.bindir = "exe"
21
+ spec.bindir = 'exe'
21
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
- spec.require_paths = ["lib"]
23
+ spec.require_paths = ['lib']
23
24
 
24
- spec.add_development_dependency "bundler"
25
- spec.add_development_dependency "rake"
26
- spec.add_development_dependency "rspec"
25
+ spec.add_development_dependency 'bundler'
26
+ spec.add_development_dependency 'rake'
27
+ spec.add_development_dependency 'rspec'
28
+ spec.add_development_dependency 'rubocop'
29
+
30
+ spec.add_dependency 'connection_pool', '~> 2.2.2'
31
+ spec.add_dependency 'grpc_kit', '~> 0.1.5'
32
+ spec.add_dependency 'serverengine', '~> 2.0.7'
27
33
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'connection_pool'
4
+
5
+ module Griffin
6
+ module ConnectionPool
7
+ # This code is based on https://github.com/drbrain/net-http-persistent/blob/0e5a8fb8a27deff37247ddb3bc5e6c9e390face5/lib/net/http/persistent/timed_stack_multi.rb
8
+ class MultiTimedStack < ::ConnectionPool::TimedStack
9
+ def initialize(size = 0, &block)
10
+ super
11
+
12
+ @enqueued = 0
13
+ @ques = Hash.new { |h, k| h[k] = [] }
14
+ @lru = []
15
+ end
16
+
17
+ def empty?
18
+ length <= 0
19
+ end
20
+
21
+ def length
22
+ @max - @created + @enqueued
23
+ end
24
+
25
+ private
26
+
27
+ def connection_stored?(options = {})
28
+ !@ques[options[:connection_args]].empty?
29
+ end
30
+
31
+ def fetch_connection(options = {})
32
+ connection_args = options[:connection_args]
33
+
34
+ @enqueued -= 1
35
+ lru_update(connection_args)
36
+ @ques[connection_args].pop
37
+ end
38
+
39
+ def shutdown_connections
40
+ @ques.each_key do |key|
41
+ super(connection_args: key)
42
+ end
43
+ end
44
+
45
+ def store_connection(obj, options = {})
46
+ @ques[options[:connection_args]].push(obj)
47
+ @enqueued += 1
48
+ end
49
+
50
+ def try_create(options = {})
51
+ connection_args = options[:connection_args]
52
+
53
+ if @created >= @max && @enqueued >= 1
54
+ oldest = lru_delete
55
+ @ques[oldest].pop
56
+
57
+ @created -= 1
58
+ end
59
+
60
+ if @created < @max
61
+ obj = @create_block.call(connection_args)
62
+ @created += 1
63
+ lru_update(connection_args)
64
+ obj
65
+ end
66
+ end
67
+
68
+ def lru_update(key)
69
+ @lru.delete(key)
70
+ @lru.push(key)
71
+ end
72
+
73
+ def lru_delete
74
+ @lru.shift
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'connection_pool'
4
+
5
+ require 'griffin/connection_pool/multi_timed_stack'
6
+
7
+ module Griffin
8
+ module ConnectionPool
9
+ class Pool
10
+ DEFAULTS = { size: 5, timeout: 5 }.freeze
11
+
12
+ def initialize(options = {}, &block)
13
+ raise ArgumentError, 'Connection pool requires a block' unless block
14
+
15
+ options = DEFAULTS.merge(options)
16
+
17
+ @size = Integer(options.fetch(:size))
18
+ @timeout = Integer(options.fetch(:timeout))
19
+
20
+ @available = Griffin::ConnectionPool::MultiTimedStack.new(@size, &block)
21
+ @key = :"current-#{@available.object_id}"
22
+ Thread.current[@key] = Hash.new { |h, k| h[k] = [] }
23
+ end
24
+
25
+ def checkin(key)
26
+ stack = Thread.current[@key][key]
27
+ raise 'no connections are checked out' if stack.empty?
28
+
29
+ conn = stack.pop
30
+ if stack.empty?
31
+ @available.push(conn, connection_args: key)
32
+ end
33
+ nil
34
+ end
35
+
36
+ def checkout(key)
37
+ stack = Thread.current[@key][key]
38
+
39
+ conn =
40
+ if stack.empty?
41
+ @available.pop(connection_args: key)
42
+ else
43
+ stack.last
44
+ end
45
+
46
+ stack.push(conn)
47
+
48
+ conn
49
+ end
50
+
51
+ def shutdown(&block)
52
+ @available.shutdown(&block)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Griffin
4
+ class CountingSemaphore
5
+ def initialize(size)
6
+ @size = size
7
+ @queue = Queue.new
8
+ @size.times { @queue.push(0) }
9
+ end
10
+
11
+ def wait
12
+ @queue.pop
13
+ end
14
+
15
+ def signal
16
+ @queue.push(0)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'griffin/listener'
4
+
5
+ module Griffin
6
+ module Engine
7
+ module Server
8
+ attr_reader :core, :listener
9
+
10
+ def initialize
11
+ @core = Griffin::Server.new
12
+ end
13
+
14
+ def before_run
15
+ config[:services].each do |s|
16
+ @core.handle(s)
17
+ end
18
+ end
19
+
20
+ def stop(stop_graceful)
21
+ super # needed
22
+ end
23
+
24
+ # def after_start; end
25
+ # def restart; end
26
+ # def reload_config; end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'griffin/server'
4
+ require 'griffin/listener'
5
+
6
+ module Griffin
7
+ module Engine
8
+ class Single
9
+ def self.create(config)
10
+ new(Griffin::Server.new, config)
11
+ end
12
+
13
+ def initialize(server, config)
14
+ @server = server
15
+ @config = config
16
+ @listener = Griffin::Listener.new(@config[:bind], @config[:port])
17
+ end
18
+
19
+ def run
20
+ @config[:services].each do |s|
21
+ @server.handle(s)
22
+ end
23
+
24
+ install_handler
25
+
26
+ @server.before_run
27
+ @server.run(@listener.listen)
28
+ end
29
+
30
+ def install_handler
31
+ trap('INT') do
32
+ @server.shutdown
33
+ end
34
+
35
+ trap('TERM') do
36
+ @server.shutdown
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'griffin/listener'
4
+
5
+ module Griffin
6
+ module Engine
7
+ module Worker
8
+ def before_fork
9
+ @listener = Griffin::Listener.new(config[:bind], config[:port])
10
+ server.core.before_run(worker_id)
11
+ end
12
+
13
+ def run
14
+ server.core.run(@listener.listen)
15
+ ensure
16
+ @listener.close
17
+ end
18
+
19
+ def stop
20
+ server.core.shutdown
21
+ end
22
+
23
+ # def after_fork; end
24
+ # def reload; end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'serverengine'
4
+ require 'griffin/engine/single'
5
+ require 'griffin/engine/server'
6
+ require 'griffin/engine/worker'
7
+
8
+ module Griffin
9
+ module Engine
10
+ def self.start(config, cluster: false)
11
+ if cluster
12
+ Griffin.logger.info("Griffin v#{Griffin::VERSION} starts as cluster mode")
13
+ ServerEngine.create(Griffin::Engine::Server, Griffin::Engine::Worker, config).run
14
+ else
15
+ Griffin.logger.info("Griffin v#{Griffin::VERSION} starts as single mode")
16
+ Griffin::Engine::Single.create(config).run
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+
5
+ module Griffin
6
+ class Listener
7
+ DEFAULT_BACKLOG_SIZE = 1024
8
+
9
+ # @params host [String]
10
+ # @params port [Integer]
11
+ # @params backlog [Integer]
12
+ def initialize(host, port, backlog: DEFAULT_BACKLOG_SIZE)
13
+ @host = host
14
+ @port = port
15
+ @backlog = backlog
16
+ end
17
+
18
+ def listen(tcp_opt: true)
19
+ @sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
20
+ if tcp_opt
21
+ @sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
22
+ end
23
+
24
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true)
25
+
26
+ @sock.bind(Addrinfo.tcp(@host, @port))
27
+ @sock.listen(@backlog)
28
+ Griffin.logger.info("Start listening #{@host}:#{@port}")
29
+ @sock
30
+ end
31
+
32
+ def close
33
+ @sock.close
34
+ end
35
+ end
36
+ end