drbarpg 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 904cd118d012de004d15e6c521f20fa962cad506
4
+ data.tar.gz: c4f985153e0ffd0c63362d3eebe4c5c8e168ee84
5
+ SHA512:
6
+ metadata.gz: 06d4ffe76e5ab4baef966ea23f12fb3b077fdf4e90eb7d746f498c28b437a28b953aeb45312c9c692ce067e19981f092ab44a8263b0b43003cda4fa9fb108fec
7
+ data.tar.gz: 578100b75206eb8ae4af1daa1a244824ae4af968f00b76d368be1fda9c98cd3d2f3e861edf21ec7bdcad1439770bcdad5cfdf190c4db31fb1c34565daabb7893
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Lars Kanis <lars@greiz-reinsdorf.de>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,64 @@
1
+ = drbarpg
2
+ {<img src="https://travis-ci.org/larskanis/drbarpg.png?branch=master" alt="Build Status" />}[https://travis-ci.org/larskanis/drbarpg]
3
+
4
+ A protocol implementation for Distributed Ruby (DRb), supporting connections
5
+ via a PostgreSQL server by using the LISTEN/NOTIFY event system.
6
+
7
+ This project rocks and uses MIT-LICENSE.
8
+
9
+ == Usage
10
+ Add to your +Gemfile+ :
11
+ gem 'drbarpg'
12
+
13
+ Run
14
+ $ bundle install
15
+ $ rake drbarpg:install:migrations
16
+ $ rake db:migrate
17
+
18
+ Ensure the value of +pool+ in your +config/database.yml+ is high enough (at least 10).
19
+
20
+ === Example server
21
+ # Startup Rails environment
22
+ require 'config/environment'
23
+
24
+ # start up the DRb service
25
+ DRb.start_service "drbarpg://myserver", []
26
+
27
+ # wait for the DRb service to finish before exiting
28
+ DRb.thread.join
29
+
30
+ === Example client:
31
+ require 'config/environment'
32
+
33
+ # Start a local DRbServer to handle callbacks.
34
+ #
35
+ # Not necessary for this small example, but will be required
36
+ # as soon as we pass a non-marshallable object as an argument
37
+ # to a dRuby call.
38
+ DRb.start_service "drbarpg://"
39
+
40
+ # attach to the DRb server via the server URI
41
+ remote_array = DRbObject.new_with_uri("drbarpg://myserver")
42
+
43
+ p remote_array.size # => 0
44
+
45
+ remote_array << 1
46
+
47
+ p remote_array.size # => 1
48
+
49
+ == Description
50
+
51
+ drbarpg makes it possible to create DRb-connections via the connection to a PostgreSQL server.
52
+ It easily integrates into rails projects and uses the configured database connection for
53
+ inter process communication.
54
+
55
+ Method calls with a size of less than 8000 bytes for all serialized parameters are directly
56
+ passed through by the NOTIFY payload. This bypasses the transaction overhead in the database,
57
+ so the calls are reasonably fast (approximately 1.5 of the time of a direct DRb-TCP connection).
58
+ Calls with bigger parameter size are transferred by INSERT/SELECT and require transaction
59
+ commits, so they usually take several milliseconds.
60
+
61
+ == Requirements
62
+
63
+ * PostgreSQL 9.0+
64
+ * Rails 3.0+
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Drbarpg'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
@@ -0,0 +1,4 @@
1
+ module Drbarpg
2
+ class Message < ActiveRecord::Base
3
+ end
4
+ end
@@ -0,0 +1,8 @@
1
+ class CreateDrbarpgDrbarpgMessages < ActiveRecord::Migration
2
+ def change
3
+ create_table :drbarpg_messages do |t|
4
+ t.references "drbarpg_connection"
5
+ t.text "payload"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ class CreateDrbarpgDrbarpgConnections < ActiveRecord::Migration
2
+ def change
3
+ create_table :drbarpg_connections do |t|
4
+ t.text "listen_channel"
5
+ end
6
+ add_index :drbarpg_connections, "listen_channel", :unique => true
7
+ end
8
+ end
data/lib/drb/ar_pg.rb ADDED
@@ -0,0 +1,183 @@
1
+ require 'drb/drb'
2
+ require 'stringio'
3
+
4
+ module DRb
5
+
6
+ # Implements DRb over an ActiveRecord connection to a PostgreSQL server
7
+ #
8
+ # DRb PostgreSQL socket URIs look like <code>drbarpg:?<option></code>. The
9
+ # option is optional.
10
+
11
+ class DRbArPg
12
+ attr_reader :uri
13
+
14
+ def self.open(uri, config)
15
+ server_channel, = parse_uri(uri)
16
+
17
+ self.new(uri, nil, server_channel, :master, config)
18
+ end
19
+
20
+ def self.open_server(uri, config)
21
+ channel, = parse_uri(uri)
22
+ self.new(uri, channel, nil, :server, config)
23
+ end
24
+
25
+ def accept
26
+ @pgconn.wait_for_notify do |channel, pid, payload|
27
+ return self.class.new(nil, nil, payload, :slave, @config)
28
+ end
29
+ end
30
+
31
+ def self.uri_option(uri, config)
32
+ channel, option = parse_uri(uri)
33
+ return "drbarpg://#{channel}", option
34
+ end
35
+
36
+ def initialize(uri, listen_channel, notify_connection_id, act_as, config={})
37
+ @notify_connection_id = notify_connection_id
38
+ @config = config
39
+ @msg = DRbMessage.new(@config)
40
+ @conn = Drbarpg::Message.connection_pool.checkout
41
+ @pgconn = @conn.raw_connection
42
+
43
+ @listen_connection_id = @conn.exec_query("SELECT nextval('drbarpg_connections_id_seq') AS seq").first['seq']
44
+ @listen_channel = if listen_channel.nil? || listen_channel.empty?
45
+ @conn.quote_table_name("drbarpg__#{@listen_connection_id}")
46
+ else
47
+ @conn.quote_table_name("drbarpg_#{listen_channel}")
48
+ end
49
+
50
+ @conn.execute("LISTEN #{@listen_channel}");
51
+
52
+ case act_as
53
+ when :server
54
+ # Save the connection to the database to ensure that only one server can listen
55
+ # on a given channel.
56
+ @conn.exec_insert('INSERT INTO drbarpg_connections (id, listen_channel)
57
+ VALUES ($1::int, $2::text)', nil,
58
+ [[nil, @listen_connection_id], [nil, @listen_channel]])
59
+
60
+ uri_channel, uri_option = self.class.parse_uri(uri) if uri
61
+ uri = "drbarpg://_#{@listen_connection_id}?#{uri_option}" if uri_channel.nil? || uri_channel.empty?
62
+ @uri = uri
63
+ when :master
64
+ # connect to server channel
65
+ @conn.exec_update("NOTIFY #{@conn.quote_table_name("drbarpg_#{@notify_connection_id}")}, #{@conn.quote(@listen_connection_id)}");
66
+ # wait for acknowledgement with peer channel
67
+ @pgconn.wait_for_notify do |channel, pid, payload|
68
+ @notify_connection_id = payload
69
+ @notify_channel = @conn.quote_table_name("drbarpg__#{@notify_connection_id}")
70
+ end
71
+ when :slave
72
+ @notify_channel = @conn.quote_table_name("drbarpg__#{@notify_connection_id}")
73
+ # acknowledge with peer channel
74
+ @conn.exec_update("NOTIFY #{@notify_channel}, #{@conn.quote(@listen_connection_id)}");
75
+ end
76
+ end
77
+
78
+ def close
79
+ if @conn
80
+ if @notify_channel
81
+ @conn.exec_update("NOTIFY #{@notify_channel}, 'c'");
82
+ end
83
+ @conn.execute("UNLISTEN #{@listen_channel}");
84
+ @conn.exec_delete('DELETE FROM drbarpg_connections WHERE id=$1::int', nil, [[nil, @listen_connection_id]])
85
+ Drbarpg::Message.connection_pool.checkin(@conn)
86
+ @conn = @pgconn = nil
87
+ end
88
+ end
89
+
90
+ def alive?
91
+ @pgconn.consume_input
92
+ if (n=@pgconn.notifies)
93
+ if n[:extra] == 'c'
94
+ close
95
+ else
96
+ raise DRbConnError, "received unexpected notification"
97
+ end
98
+ end
99
+ !!@conn
100
+ end
101
+
102
+ def send_request(ref, msg_id, *arg, &b)
103
+ stream = StringIO.new
104
+ @msg.send_request(stream, ref, msg_id, *arg, &b)
105
+ send_message(stream.string)
106
+ end
107
+
108
+ def recv_reply
109
+ stream = StringIO.new(wait_for_message)
110
+ return @msg.recv_reply(stream)
111
+ end
112
+
113
+ def recv_request
114
+ begin
115
+ stream = StringIO.new(wait_for_message)
116
+ return @msg.recv_request(stream)
117
+ rescue
118
+ close
119
+ raise $!
120
+ end
121
+ end
122
+
123
+ def send_reply(succ, result)
124
+ begin
125
+ stream = StringIO.new
126
+ @msg.send_reply(stream, succ, result)
127
+ send_message(stream.string)
128
+ rescue
129
+ close
130
+ raise $!
131
+ end
132
+ end
133
+
134
+ private
135
+ def send_message(payload)
136
+ payload_b64 = [payload].pack('m')
137
+ if payload_b64.length >= 8000
138
+ @conn.exec_insert('INSERT INTO drbarpg_messages (drbarpg_connection_id, payload)
139
+ VALUES ($1::int, $2::text)', nil, [[nil, @notify_connection_id], [nil, payload_b64]])
140
+ @conn.exec_update("NOTIFY #{@notify_channel}");
141
+ else
142
+ @conn.exec_update("NOTIFY #{@notify_channel}, '#{payload_b64}'");
143
+ end
144
+ end
145
+
146
+ def check_pending_message
147
+ row = @conn.exec_query('SELECT id, payload FROM drbarpg_messages WHERE drbarpg_connection_id=$1::int LIMIT 1',
148
+ nil, [ [nil, @listen_connection_id] ]).first
149
+ if row
150
+ @conn.exec_delete('DELETE FROM drbarpg_messages WHERE id=$1::int', nil, [[nil, row['id']]])
151
+ return row['payload'].unpack('m')[0]
152
+ else
153
+ raise DRbConnError, "no message received"
154
+ end
155
+ end
156
+
157
+ def wait_for_message()
158
+ @pgconn.wait_for_notify do |channel, pid, payload|
159
+ if payload=='c'
160
+ close
161
+ return ''
162
+ elsif payload
163
+ return payload.unpack('m')[0]
164
+ else
165
+ return check_pending_message
166
+ end
167
+ end
168
+ end
169
+
170
+ def self.parse_uri(uri)
171
+ if /^drbarpg:\/\/(.*?)(\?(.*))?$/ =~ uri
172
+ channel = $1
173
+ option = $3
174
+ [channel, option]
175
+ else
176
+ raise(DRbBadScheme, uri) unless uri =~ /^drbarpg:/
177
+ raise(DRbBadURI, 'can\'t parse uri:' + uri)
178
+ end
179
+ end
180
+ end
181
+
182
+ DRbProtocol.add_protocol(DRbArPg)
183
+ end
@@ -0,0 +1,5 @@
1
+ module Drbarpg
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Drbarpg
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module Drbarpg
2
+ VERSION = "0.0.1"
3
+ end
data/lib/drbarpg.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'drbarpg/engine'
2
+ require 'drb/ar_pg'
3
+
4
+ module Drbarpg
5
+ end
@@ -0,0 +1,84 @@
1
+ require 'test_helper'
2
+ require 'drb/ar_pg'
3
+ require 'logger'
4
+ require 'rbconfig'
5
+
6
+ class DrbarpgTest < ActiveSupport::TestCase
7
+ self.use_transactional_fixtures = false
8
+
9
+ attr_reader :drb
10
+
11
+ UriServer = "drbarpg://server"
12
+ UriCallback = "drbarpg://"
13
+ # UriServer = "druby://localhost:33333"
14
+ # UriCallback = nil
15
+
16
+ def setup
17
+ # ActiveRecord::Base.logger = Logger.new STDERR
18
+
19
+ # Start forked server process
20
+ # ActiveRecord::Base.connection_pool.disconnect!
21
+ # rd, wr = IO.pipe
22
+ # fork do
23
+ # DRb.start_service(UriServer, Kernel)
24
+ # wr.puts "started"
25
+ # DRb.thread.join
26
+ # end
27
+
28
+ rd = IO.popen(RbConfig::CONFIG['ruby_install_name'], 'w+')
29
+ rd.write <<-EOT
30
+ $: << #{File.expand_path('..', __FILE__).inspect}
31
+ require 'test_helper'
32
+ require 'drb/ar_pg'
33
+
34
+ # ActiveRecord::Base.logger = Logger.new $stderr
35
+ DRb.start_service(#{UriServer.inspect}, Kernel)
36
+ $stdout.puts "started"
37
+ $stdout.flush
38
+ $stdout.reopen($stderr)
39
+ DRb.thread.join
40
+ EOT
41
+ rd.close_write
42
+
43
+ # Start local server for callbacks
44
+ DRb.start_service(UriCallback)
45
+
46
+ # Wait until server process has started
47
+ rd.gets
48
+
49
+ # Connect to server object
50
+ @drb = DRbObject.new_with_uri(UriServer)
51
+ end
52
+
53
+ def teardown
54
+ drb.eval("DRb.stop_service")
55
+ # Wait until server process has stopped
56
+ Process.wait
57
+ DRb.stop_service
58
+ end
59
+
60
+ test "allows two-way communication" do
61
+ remote_hash = drb.eval("@a = {}.extend(DRb::DRbUndumped)")
62
+
63
+ # Set local Proc in remote hash, re-fetch it and call it. Works without two-way comms
64
+ remote_hash["a"] = lambda { return 1 }
65
+ assert_equal 1, drb.eval("@a['a']").call
66
+
67
+ # Call same Proc in a remote context. This means establishing a connection to the local side, since
68
+ # the Proc is really a DRbObject on the remote side.
69
+ assert_equal 1, drb.eval("@a['a'].call")
70
+ end
71
+
72
+ test "allows big params" do
73
+ remote_hash = drb.eval("@a = {}.extend(DRb::DRbUndumped)")
74
+
75
+ remote_hash["a"] = "b"*8000
76
+ assert_equal "b"*8000, drb.eval("@a['a']")
77
+ end
78
+
79
+ test "runs reasonable fast" do
80
+ 2000.times do
81
+ drb.rand
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,28 @@
1
+ == README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
25
+
26
+
27
+ Please feel free to use a different markup language if you do not plan to run
28
+ <tt>rake doc:app</tt>.
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Dummy::Application.load_tasks
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
+ load Gem.bin_path('bundler', 'bundle')
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
3
+ require_relative '../config/boot'
4
+ require 'rails/commands'
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../config/boot'
3
+ require 'rake'
4
+ Rake.application.run
@@ -0,0 +1,23 @@
1
+ require File.expand_path('../boot', __FILE__)
2
+
3
+ require 'rails/all'
4
+
5
+ Bundler.require(*Rails.groups)
6
+ require "drbarpg"
7
+
8
+ module Dummy
9
+ class Application < Rails::Application
10
+ # Settings in config/environments/* take precedence over those specified here.
11
+ # Application configuration should go into files in config/initializers
12
+ # -- all .rb files in that directory are automatically loaded.
13
+
14
+ # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
15
+ # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
16
+ # config.time_zone = 'Central Time (US & Canada)'
17
+
18
+ # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
19
+ # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
20
+ # config.i18n.default_locale = :de
21
+ end
22
+ end
23
+
@@ -0,0 +1,5 @@
1
+ # Set up gems listed in the Gemfile.
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
3
+
4
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
5
+ $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
@@ -0,0 +1,60 @@
1
+ # PostgreSQL. Versions 8.2 and up are supported.
2
+ #
3
+ # Install the pg driver:
4
+ # gem install pg
5
+ # On OS X with Homebrew:
6
+ # gem install pg -- --with-pg-config=/usr/local/bin/pg_config
7
+ # On OS X with MacPorts:
8
+ # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
9
+ # On Windows:
10
+ # gem install pg
11
+ # Choose the win32 build.
12
+ # Install PostgreSQL and put its /bin directory on your path.
13
+ #
14
+ # Configure Using Gemfile
15
+ # gem 'pg'
16
+ #
17
+ development:
18
+ adapter: postgresql
19
+ encoding: unicode
20
+ database: drbarpg_development
21
+ pool: 50
22
+ # username: drbarpg
23
+ # password:
24
+
25
+ # Connect on a TCP socket. Omitted by default since the client uses a
26
+ # domain socket that doesn't need configuration. Windows does not have
27
+ # domain sockets, so uncomment these lines.
28
+ #host: localhost
29
+
30
+ # The TCP port the server listens on. Defaults to 5432.
31
+ # If your server runs on a different port number, change accordingly.
32
+ #port: 5432
33
+
34
+ # Schema search path. The server defaults to $user,public
35
+ #schema_search_path: myapp,sharedapp,public
36
+
37
+ # Minimum log levels, in increasing order:
38
+ # debug5, debug4, debug3, debug2, debug1,
39
+ # log, notice, warning, error, fatal, and panic
40
+ # Defaults to warning.
41
+ #min_messages: notice
42
+
43
+ # Warning: The database defined as "test" will be erased and
44
+ # re-generated from your development database when you run "rake".
45
+ # Do not set this db to the same as development or production.
46
+ test:
47
+ adapter: postgresql
48
+ encoding: unicode
49
+ database: drbarpg_test
50
+ pool: 50
51
+ # username: drbarpg
52
+ # password:
53
+
54
+ production:
55
+ adapter: postgresql
56
+ encoding: unicode
57
+ database: drbarpg_production
58
+ pool: 50
59
+ # username: drbarpg
60
+ # password:
@@ -0,0 +1,5 @@
1
+ # Load the rails application.
2
+ require File.expand_path('../application', __FILE__)
3
+
4
+ # Initialize the rails application.
5
+ Dummy::Application.initialize!
@@ -0,0 +1,4 @@
1
+ # This file is used by Rack-based servers to start the application.
2
+
3
+ require ::File.expand_path('../config/environment', __FILE__)
4
+ run Rails.application
@@ -0,0 +1,27 @@
1
+ # encoding: UTF-8
2
+ # This file is auto-generated from the current state of the database. Instead
3
+ # of editing this file, please use the migrations feature of Active Record to
4
+ # incrementally modify your database, and then regenerate this schema definition.
5
+ #
6
+ # Note that this schema.rb definition is the authoritative source for your
7
+ # database schema. If you need to create the application database on another
8
+ # system, you should be using db:schema:load, not running all the migrations
9
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
11
+ #
12
+ # It's strongly recommended to check this file into your version control system.
13
+
14
+ ActiveRecord::Schema.define(:version => 20130601144555) do
15
+
16
+ create_table "drbarpg_connections", :force => true do |t|
17
+ t.text "listen_channel"
18
+ end
19
+
20
+ add_index "drbarpg_connections", ["listen_channel"], :name => "index_drbarpg_connections_on_listen_channel", :unique => true
21
+
22
+ create_table "drbarpg_messages", :force => true do |t|
23
+ t.integer "drbarpg_connection_id"
24
+ t.text "payload"
25
+ end
26
+
27
+ end