bg 0.0.1 → 0.0.2

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
  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