griffin 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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