bg 0.0.1 → 0.0.2

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
  SHA1:
3
- metadata.gz: c84b2acf5bc22423b5a47b7705fd9f20022fab38
4
- data.tar.gz: 087b132f0f6ad791d32608192b7d8676325649e5
3
+ metadata.gz: 701fc3678ec92e6f30a6df96f361b447c4e9a73b
4
+ data.tar.gz: f882ff93d30fe5c0e77c7e3e585933b28015fa82
5
5
  SHA512:
6
- metadata.gz: ee2a7e4363d06db876253b5383514c82e66b82582a73db39c95d8edd9fd7d2042c7ce080a2d47bd5ab47618f001954aa9d2d2afe21ee1cfa79eb49a3df5ccf7e
7
- data.tar.gz: 993822472d6d798aa60acbcfb79bbaf0c814663ed72ab4fae54f48bbfd01c8ccd53c7341b54263e8d7c605cc71344b6c7b79ebf6db1498b3413b63e5ddbd1505
6
+ metadata.gz: 9bbbc79e1998a606803a4ce94d2e9213561ff6576998a67c5fc72a700c165ea854a9b2cf6ab1407c324a9c59ca4f57f289314d802d051dd9a82e44668903ad8c
7
+ data.tar.gz: dc287ca50eb890ddb1b0a827300d5f0b1d634000af02500de4e308060e340484954d412c91aa7ee389abc94168bbf65724c1deb374de83095cee4de7ad92120c
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Declare your gem's dependencies in bg.gemspec.
4
+ # Bundler will treat runtime dependencies like base dependencies, and
5
+ # development dependencies will be added by default to the :development group.
6
+ gemspec
7
+
8
+ # Declare any dependencies that are still in development here instead of in
9
+ # your gemspec. These might include edge Rails or gems from your path or
10
+ # Git. Remember to move these dependencies to your gemspec before releasing
11
+ # your gem to rubygems.org.
12
+
13
+ # To use a debugger
14
+ # gem 'byebug', group: [:development, :test]
data/Gemfile.lock ADDED
@@ -0,0 +1,75 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ bg (0.0.2)
5
+ activejob (>= 5.0)
6
+ activerecord (>= 5.0)
7
+ concurrent-ruby (>= 1.0)
8
+ globalid (>= 0.3)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ activejob (5.0.0.1)
14
+ activesupport (= 5.0.0.1)
15
+ globalid (>= 0.3.6)
16
+ activemodel (5.0.0.1)
17
+ activesupport (= 5.0.0.1)
18
+ activerecord (5.0.0.1)
19
+ activemodel (= 5.0.0.1)
20
+ activesupport (= 5.0.0.1)
21
+ arel (~> 7.0)
22
+ activesupport (5.0.0.1)
23
+ concurrent-ruby (~> 1.0, >= 1.0.2)
24
+ i18n (~> 0.7)
25
+ minitest (~> 5.1)
26
+ tzinfo (~> 1.1)
27
+ arel (7.1.2)
28
+ binding_of_caller (0.7.2)
29
+ debug_inspector (>= 0.0.1)
30
+ byebug (9.0.6)
31
+ coderay (1.1.1)
32
+ concurrent-ruby (1.0.2)
33
+ debug_inspector (0.0.2)
34
+ globalid (0.3.7)
35
+ activesupport (>= 4.1.0)
36
+ i18n (0.7.0)
37
+ interception (0.5)
38
+ method_source (0.8.2)
39
+ minitest (5.9.1)
40
+ os (0.9.6)
41
+ pry (0.10.4)
42
+ coderay (~> 1.1.0)
43
+ method_source (~> 0.8.1)
44
+ slop (~> 3.4)
45
+ pry-byebug (3.4.0)
46
+ byebug (~> 9.0)
47
+ pry (~> 0.10)
48
+ pry-rescue (1.4.4)
49
+ interception (>= 0.5)
50
+ pry
51
+ pry-stack_explorer (0.4.9.2)
52
+ binding_of_caller (>= 0.7)
53
+ pry (>= 0.9.11)
54
+ pry-test (0.6.4)
55
+ os
56
+ pry
57
+ pry-byebug
58
+ pry-rescue
59
+ pry-stack_explorer
60
+ rake (11.3.0)
61
+ slop (3.6.0)
62
+ thread_safe (0.3.5)
63
+ tzinfo (1.2.2)
64
+ thread_safe (~> 0.1)
65
+
66
+ PLATFORMS
67
+ ruby
68
+
69
+ DEPENDENCIES
70
+ bg!
71
+ pry-test
72
+ rake
73
+
74
+ BUNDLED WITH
75
+ 1.13.1
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Nathan Hopkins
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.md ADDED
@@ -0,0 +1,69 @@
1
+ # Bg
2
+
3
+ ## Non-blocking ActiveRecord method invocation
4
+
5
+ This library allows you to invoke ActiveRecord instance methods in the background.
6
+
7
+ * `Bg::Asyncable` uses concurrent-ruby to execute methods in a different thread
8
+ * `Bg::Deferrable` uses ActiveJob to execute methods in a background process
9
+
10
+
11
+ ## Quickstart
12
+
13
+ ### Setup
14
+
15
+ ```ruby
16
+ class User < ApplicationRecord
17
+ include Bg::Asyncable # uses concurrent-ruby
18
+ include Bg::Deferrable # uses ActiveJob
19
+ end
20
+ ```
21
+
22
+ ### Usage
23
+
24
+ ```ruby
25
+ user = User.find(params[:id])
26
+ user.do_hard_work # blocking in-process
27
+ user.async.do_hard_work # non-blocking in-process
28
+ user.defer.do_hard_work # non-blocking out-of-process background job
29
+ user.defer(queue: :low, wait: 5.minutes).do_hard_work
30
+ ```
31
+
32
+ ## Provisos
33
+
34
+ Bg leverages [GlobalID](https://github.com/rails/globalid) to marshal ActiveRecord instances across thread & process boundaries.
35
+ This means that state is not shared between the main process/thread with the process/thread actually executing the method.
36
+
37
+ * Do not depend on lexically scoped bindings when invoking methods with Bg::Deferrable
38
+ * Do not pass unmarshallable types as arguments with Bg::Deferrable.
39
+ Follow Sidekiq's [simple parameters](https://github.com/mperham/sidekiq/wiki/Best-Practices#1-make-your-job-parameters-small-and-simple) rule
40
+
41
+ ### Examples
42
+
43
+ #### Good
44
+
45
+ ```ruby
46
+ user = User.find(params[:id])
47
+ user.update(name: "new value") # persisted changes will be available in Bg invoked methods
48
+
49
+ user.async.do_hard_work 1, true, "foo", :bar, Time.now
50
+ user.defer.do_hard_work 1, true, "foo"
51
+ ```
52
+
53
+ #### Bad
54
+
55
+ ```ruby
56
+ user = User.find(params[:id])
57
+ user.name = "new value" # in memory changes will not be available in Bg invoked methods
58
+
59
+ user.async.do_hard_work do
60
+ # this is dangerous... you better know what you're doing
61
+ # best to avoid
62
+ end
63
+
64
+ user.defer.do_hard_work :foo, Time.now # args won't marshal properly
65
+
66
+ user.defer.do_hard_work do
67
+ # blocks are not supported
68
+ end
69
+ ```
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "bg"
5
+
6
+ require "pry"
7
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,62 @@
1
+ require "active_record"
2
+ require "concurrent"
3
+ require "globalid"
4
+
5
+ module Bg
6
+ class Asyncable
7
+ class Wrapper
8
+ include ::Concurrent::Async
9
+ attr_reader :global_id, :delay
10
+
11
+ def initialize(global_id, delay: 0)
12
+ # IMPORTANT: call super without any arguments
13
+ # https://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Async.html
14
+ super()
15
+ @global_id = global_id
16
+ @delay = delay.to_f
17
+ end
18
+
19
+ def invoke_method(name, *args)
20
+ sleep delay if delay > 0
21
+ ::ActiveRecord::Base.connection_pool.with_connection do
22
+ global_id.find.send name, *args
23
+ end
24
+ end
25
+ end
26
+
27
+ module Behavior
28
+ def async(delay: 0)
29
+ ::Bg::Asyncable.new(self, delay: delay.to_f)
30
+ end
31
+ end
32
+
33
+ attr_reader :object, :delay
34
+
35
+ def initialize(object, delay: 0)
36
+ raise ::ArgumentError unless object.is_a?(::GlobalID::Identification)
37
+ super()
38
+ @object = object
39
+ @delay = delay.to_f
40
+ end
41
+
42
+ def method_missing(name, *args)
43
+ if object.respond_to? name
44
+ raise ::ArgumentError.new("blocks are not supported") if block_given?
45
+ begin
46
+ wrapped = ::Bg::Asyncable::Wrapper.new(object.to_global_id, delay: delay)
47
+ wrapped.async.invoke_method name, *args
48
+ rescue ::StandardError => e
49
+ raise ::ArgumentError.new("Failed to execute method asynchronously! <#{object.class.name}##{name}> #{e.message}")
50
+ ensure
51
+ return
52
+ end
53
+ end
54
+ super
55
+ end
56
+
57
+ def respond_to?(name)
58
+ return true if object.respond_to? name
59
+ super
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,45 @@
1
+ require "globalid"
2
+ require "bg/deferred_method_call_job"
3
+
4
+ module Bg
5
+ class Deferrable
6
+ module Behavior
7
+ # Enqueues the method call to be executed by a DeferredMethodCallJob background worker.
8
+ def defer(queue: :default, wait: 0)
9
+ ::Bg::Deferrable.new self, queue: queue, wait: wait
10
+ end
11
+ end
12
+
13
+ attr_reader :object, :queue, :wait
14
+
15
+ def initialize(object, queue: :default, wait: 0)
16
+ raise ::ArgumentError unless object.is_a?(::GlobalID::Identification)
17
+ @object = object
18
+ @queue = queue || :default
19
+ @wait = wait.to_i
20
+ end
21
+
22
+ def method_missing(name, *args)
23
+ if object.respond_to? name
24
+ raise ::ArgumentError.new("blocks are not supported") if block_given?
25
+ begin
26
+ if wait > 0
27
+ job = ::Bg::DeferredMethodCallJob.set(queue: queue, wait: wait).perform_later object, name.to_s, *args
28
+ else
29
+ job = ::Bg::DeferredMethodCallJob.set(queue: queue).perform_later object, name.to_s, *args
30
+ end
31
+ rescue ::StandardError => e
32
+ raise ::ArgumentError.new("Failed to background method call! <#{object.class.name}##{name}> #{e.message}")
33
+ ensure
34
+ return job
35
+ end
36
+ end
37
+ super
38
+ end
39
+
40
+ def respond_to?(name)
41
+ return true if object.respond_to? name
42
+ super
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,11 @@
1
+ require "active_job"
2
+
3
+ module Bg
4
+ class DeferredMethodCallJob < ::ActiveJob::Base
5
+ queue_as :default
6
+
7
+ def perform(object, method, *args)
8
+ object.send method, *args
9
+ end
10
+ end
11
+ end
data/lib/bg/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Bg
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/bg.rb CHANGED
@@ -1,13 +1,6 @@
1
1
  require "bg/version"
2
- require "bg/runner"
2
+ require "bg/asyncable"
3
+ require "bg/deferrable"
3
4
 
4
5
  module Bg
5
- class << self
6
- attr_accessor :logfile
7
-
8
- def run(*args, &block)
9
- Runner.new(logfile: logfile).run(*args, &block)
10
- end
11
-
12
- end
13
6
  end
metadata CHANGED
@@ -1,73 +1,73 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Hopkins
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-11 00:00:00.000000000 Z
11
+ date: 2016-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: os
14
+ name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.9.6
19
+ version: '5.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.9.6
26
+ version: '5.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: method_source
28
+ name: activejob
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.8.2
33
+ version: '5.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 0.8.2
40
+ version: '5.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: bundler
42
+ name: globalid
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '1.5'
48
- type: :development
47
+ version: '0.3'
48
+ type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '1.5'
54
+ version: '0.3'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rake
56
+ name: concurrent-ruby
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
61
+ version: '1.0'
62
+ type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '1.0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: pry
70
+ name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -81,31 +81,38 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: micro_test
84
+ name: pry-test
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: 0.4.4
89
+ version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: 0.4.4
97
- description: Easily run code in a separate background process.
96
+ version: '0'
97
+ description:
98
98
  email:
99
99
  - natehop@gmail.com
100
100
  executables: []
101
101
  extensions: []
102
102
  extra_rdoc_files: []
103
103
  files:
104
+ - Gemfile
105
+ - Gemfile.lock
106
+ - MIT-LICENSE
107
+ - README.md
108
+ - Rakefile
109
+ - bin/console
110
+ - bin/setup
104
111
  - lib/bg.rb
105
- - lib/bg/drb_runner.rb
106
- - lib/bg/runner.rb
112
+ - lib/bg/asyncable.rb
113
+ - lib/bg/deferrable.rb
114
+ - lib/bg/deferred_method_call_job.rb
107
115
  - lib/bg/version.rb
108
- - test/runner_test.rb
109
116
  homepage: https://github.com/hopsoft/bg
110
117
  licenses:
111
118
  - MIT
@@ -126,9 +133,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
133
  version: '0'
127
134
  requirements: []
128
135
  rubyforge_project:
129
- rubygems_version: 2.2.0
136
+ rubygems_version: 2.5.1
130
137
  signing_key:
131
138
  specification_version: 4
132
- summary: Easily run code in a separate background process.
133
- test_files:
134
- - test/runner_test.rb
139
+ summary: ''
140
+ test_files: []
data/lib/bg/drb_runner.rb DELETED
@@ -1,36 +0,0 @@
1
- require "thread"
2
- require "drb"
3
- require "logger"
4
-
5
- module Bg
6
- class DrbRunner
7
- attr_reader :logger
8
-
9
- def initialize(logfile: logfile)
10
- @logger = Logger.new(logfile || "/dev/null")
11
- end
12
-
13
- def ready?
14
- true
15
- end
16
-
17
- def run(proc: nil, args: [])
18
- Thread.new do
19
- begin
20
- sleep 0
21
- method = "define_method :run #{proc}"
22
- runner = Class.new { eval method }
23
- logger.info "Start exec: #{method}"
24
- runner.new.run(*Marshal.load(args))
25
- logger.info "Finish exec: #{method}"
26
- rescue Exception => e
27
- logger.error "Failed exec: #{method}\n#{e}"
28
- ensure
29
- DRb.stop_service rescue nil
30
- Process.exit
31
- end
32
- end
33
- end
34
-
35
- end
36
- end
data/lib/bg/runner.rb DELETED
@@ -1,62 +0,0 @@
1
- require "method_source"
2
- require "thread"
3
- require "drb"
4
- require "bg/drb_runner"
5
-
6
- module Bg
7
- class Runner
8
- attr_reader :logfile, :drb_uri, :drb_pid, :drb_runner
9
-
10
- def initialize(logfile: nil)
11
- @logfile = logfile
12
- @drb_uri = "druby://127.0.0.1:#{random_port}"
13
- end
14
-
15
- def run(*args, &block)
16
- DRb.start_service
17
- start_drb_runner
18
- drb_runner.run(
19
- proc: proc_string(block),
20
- args: Marshal.dump(args)
21
- )
22
- DRb.stop_service
23
- end
24
-
25
- private
26
-
27
- def proc_string(proc)
28
- code = proc.source
29
- index = code.index(/\{|do/)
30
- code[index..-1].strip
31
- end
32
-
33
- def start_drb_runner
34
- return if drb_runner
35
- @drb_pid = fork do
36
- DRb.start_service drb_uri, DrbRunner.new(logfile: logfile)
37
- DRb.thread.join
38
- end
39
- Process.detach drb_pid
40
- @drb_runner = DRbObject.new_with_uri(drb_uri)
41
- sleep 0.001 while !drb_runner_ready?
42
- drb_runner
43
- end
44
-
45
- def drb_runner_ready?
46
- begin
47
- drb_runner.ready?
48
- rescue DRb::DRbConnError
49
- false
50
- end
51
- end
52
-
53
- def random_port
54
- socket = Socket.new(:INET, :STREAM, 0)
55
- socket.bind(Addrinfo.tcp("127.0.0.1", 0))
56
- port = socket.local_address.ip_port
57
- socket.close
58
- port
59
- end
60
-
61
- end
62
- end
data/test/runner_test.rb DELETED
@@ -1,21 +0,0 @@
1
- require "micro_test"
2
- require "pry"
3
- require_relative "../lib/bg"
4
-
5
- module Bg
6
- class RunnerTest < MicroTest::Test
7
-
8
- before do
9
- Bg.logfile = File.expand_path("../../log/test.log", __FILE__)
10
- end
11
-
12
- test "3 sleeps" do
13
- Bg.run 1, 2, 3 do |a, b, c|
14
- sleep a
15
- sleep b
16
- sleep c
17
- end
18
- end
19
-
20
- end
21
- end