standard-procedure-plumbing 0.4.3 → 0.4.4

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
  SHA256:
3
- metadata.gz: 95f2fc92882bb116a50fb4406b8d905b6783bfd0b2f74a4229162fd26a920668
4
- data.tar.gz: a0db08496ca520d9ae6baf50495395b3f943869cc07105b8d5ef7cefc054f8bf
3
+ metadata.gz: b9f1970e25364d95a6ec7ad423514cb7b58060eeb756b1a0522f54f8b2cb2b4a
4
+ data.tar.gz: 0e6eaa9583b24265285528f37e23d97109839fadbd7c48486fe621e22b5321a3
5
5
  SHA512:
6
- metadata.gz: 976815454b96a52880605f34fc7d7ae801259bedaa7807c148f9edc951e9e8766d35e9cc0988d2f572aa7076bf78e9b90a666468b1438b0b80abe6cb3e3d9947
7
- data.tar.gz: 6c9852305cf8ac858d853cdeac89c57c3424838d5ee51a130a989210ffdd8337d393dd710f61d8cf0a3212f4027383840c7919e44d61c6c86e8bfb9af53eac8f
6
+ metadata.gz: 4672d2ce39d68ac74325c2c23e87e66e66eb97735d69681c115b0190203d178966283a432566164d4a5624202ec0fb2935af83ff338876b550ff542fc7ff5bdc
7
+ data.tar.gz: 52dfe371b1f12fab3040e3270c496403b06ee6d9331380e630dee481ef90653e939de544f77e177ed9f45378aa08a090e3f48275b66e1dcdd3442c4c9a824f15
@@ -26,7 +26,7 @@ module Plumbing
26
26
  nil
27
27
  end
28
28
 
29
- def within_actor? = true
29
+ def in_context? = true
30
30
 
31
31
  def stop = nil
32
32
 
@@ -18,7 +18,7 @@ module Plumbing
18
18
  nil
19
19
  end
20
20
 
21
- def within_actor? = true
21
+ def in_context? = true
22
22
 
23
23
  def stop = nil
24
24
 
@@ -1,3 +1,5 @@
1
+ require "timeout"
2
+
1
3
  module Plumbing
2
4
  module Actor
3
5
  ::Kernel.class_eval do
@@ -5,6 +7,15 @@ module Plumbing
5
7
  result = block.call
6
8
  result.respond_to?(:value) ? result.send(:value) : result
7
9
  end
10
+
11
+ def wait_for timeout = nil, &block
12
+ Timeout.timeout(timeout || Plumbing.config.timeout) do
13
+ loop do
14
+ break if block.call
15
+ sleep 0.1
16
+ end
17
+ end
18
+ end
8
19
  end
9
20
  end
10
21
  end
@@ -1,5 +1,6 @@
1
1
  require "concurrent/array"
2
2
  require "concurrent/mvar"
3
+ require "concurrent/scheduled_task"
3
4
  require "concurrent/immutable_struct"
4
5
  require "concurrent/promises"
5
6
  require_relative "transporter"
@@ -17,7 +18,8 @@ module Plumbing
17
18
 
18
19
  # Send the message to the target and wrap the result
19
20
  def send_message(message_name, *args, **params, &block)
20
- Message.new(@target, message_name, Plumbing::Actor.transporter.marshal(*args, **params), block, Concurrent::MVar.new).tap do |message|
21
+ puts "->#{@target.class}##{message_name}(#{args.inspect}, #{params.inspect})\n#{Thread.current.name}" if Plumbing.config.debug
22
+ Message.new(@target, message_name, Plumbing::Actor.transporter.marshal(*args), Plumbing::Actor.transporter.marshal(params).first, block, Concurrent::MVar.new).tap do |message|
21
23
  @queue << message
22
24
  send_messages
23
25
  end
@@ -28,37 +30,41 @@ module Plumbing
28
30
  nil
29
31
  end
30
32
 
31
- def within_actor? = @mutex.owned?
33
+ def in_context? = @mutex.owned?
32
34
 
33
- def stop
34
- within_actor? ? @queue.clear : @mutex.synchronize { @queue.clear }
35
- end
35
+ def stop = @queue.clear
36
36
 
37
37
  protected
38
38
 
39
- def in_context(&)
40
- Concurrent::Promises.future do
41
- @mutex.synchronize(&)
39
+ def in_actor_thread &block
40
+ Concurrent::ScheduledTask.execute(0.1) do
41
+ @mutex.synchronize(&block)
42
42
  end
43
43
  end
44
44
 
45
45
  private
46
46
 
47
47
  def send_messages
48
- in_context do
49
- while (message = @queue.shift)
50
- message.call
51
- end
48
+ in_context? ? dispatch_messages : in_actor_thread { dispatch_messages }
49
+ end
50
+
51
+ def dispatch_messages
52
+ while (message = @queue.shift)
53
+ message.call
52
54
  end
53
55
  end
54
56
 
55
- class Message < Concurrent::ImmutableStruct.new(:target, :message_name, :packed_args, :unsafe_block, :result)
57
+ class Message < Concurrent::ImmutableStruct.new(:target, :message_name, :packed_args, :packed_params, :unsafe_block, :result)
56
58
  def call
57
59
  args = Plumbing::Actor.transporter.unmarshal(*packed_args)
58
- value = target.send message_name, *args, &unsafe_block
60
+ params = Plumbing::Actor.transporter.unmarshal(packed_params)
61
+ puts "=> #{target.class}##{message_name}(#{args.first.inspect}, #{params.first.inspect}, &#{!unsafe_block.nil?})\n#{Thread.current.name}" if Plumbing.config.debug
62
+ value = target.send message_name, *args, **params.first, &unsafe_block
59
63
 
60
64
  result.put Plumbing::Actor.transporter.marshal(value)
61
65
  rescue => ex
66
+ puts ex
67
+ puts ex.backtrace
62
68
  result.put ex
63
69
  end
64
70
 
@@ -8,7 +8,7 @@ module Plumbing
8
8
  nil
9
9
  end
10
10
 
11
- def within_actor? = proxy.within_actor?
11
+ def in_context? = proxy.in_context?
12
12
 
13
13
  def stop = proxy.stop
14
14
 
@@ -1,6 +1,6 @@
1
1
  # Pipes, pipelines, actors and rubber ducks
2
2
  module Plumbing
3
- Config = Data.define :mode, :actor_proxy_classes, :timeout do
3
+ Config = Data.define :mode, :actor_proxy_classes, :timeout, :debug do
4
4
  def actor_proxy_class_for target_class
5
5
  actor_proxy_classes[target_class]
6
6
  end
@@ -45,7 +45,7 @@ module Plumbing
45
45
  private_class_method :set_configuration_and_yield
46
46
 
47
47
  def self.configs
48
- @configs ||= [Config.new(mode: :inline, timeout: 30, actor_proxy_classes: {})]
48
+ @configs ||= [Config.new(mode: :inline, timeout: 30, actor_proxy_classes: {}, debug: false)]
49
49
  end
50
50
  private_class_method :configs
51
51
  end
@@ -9,15 +9,12 @@ require "rspec/expectations"
9
9
  #
10
10
  RSpec::Matchers.define :become_equal_to do
11
11
  match do |expected|
12
- max = Plumbing.config.timeout * 10
13
- counter = 0
14
- matched = false
15
- while (counter < max) && (matched == false)
16
- sleep 0.1
17
- counter += 1
18
- matched = true if (@result = block_arg.call) == expected
12
+ wait_for do
13
+ block_arg.call == expected
19
14
  end
20
- matched
15
+ true
16
+ rescue Timeout::Error
17
+ false
21
18
  end
22
19
 
23
20
  failure_message do |expected|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Plumbing
4
- VERSION = "0.4.3"
4
+ VERSION = "0.4.4"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard-procedure-plumbing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rahoul Baruah
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-17 00:00:00.000000000 Z
11
+ date: 2024-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: globalid
@@ -55,25 +55,9 @@ files:
55
55
  - lib/plumbing/rubber_duck/module.rb
56
56
  - lib/plumbing/rubber_duck/object.rb
57
57
  - lib/plumbing/rubber_duck/proxy.rb
58
+ - lib/plumbing/spec/become_equal_to_matcher.rb
58
59
  - lib/plumbing/types.rb
59
60
  - lib/plumbing/version.rb
60
- - spec/become_equal_to_matcher.rb
61
- - spec/examples/actor_spec.rb
62
- - spec/examples/await_spec.rb
63
- - spec/examples/pipe_spec.rb
64
- - spec/examples/pipeline_spec.rb
65
- - spec/examples/rubber_duck_spec.rb
66
- - spec/plumbing/a_pipe.rb
67
- - spec/plumbing/actor/transporter_spec.rb
68
- - spec/plumbing/actor_spec.rb
69
- - spec/plumbing/custom_filter_spec.rb
70
- - spec/plumbing/filter_spec.rb
71
- - spec/plumbing/junction_spec.rb
72
- - spec/plumbing/pipe_spec.rb
73
- - spec/plumbing/pipeline_spec.rb
74
- - spec/plumbing/rubber_duck_spec.rb
75
- - spec/plumbing_spec.rb
76
- - spec/spec_helper.rb
77
61
  homepage: https://github.com/standard-procedure/plumbing
78
62
  licenses: []
79
63
  metadata:
@@ -96,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
80
  - !ruby/object:Gem::Version
97
81
  version: '0'
98
82
  requirements: []
99
- rubygems_version: 3.5.12
83
+ rubygems_version: 3.5.17
100
84
  signing_key:
101
85
  specification_version: 4
102
86
  summary: Plumbing - various pipelines for your ruby application
@@ -1,86 +0,0 @@
1
- require "spec_helper"
2
- require "plumbing/actor/async"
3
- require "plumbing/actor/threaded"
4
-
5
- RSpec.shared_examples "an example actor" do |runs_in_background|
6
- # standard:disable Lint/ConstantDefinitionInBlock
7
- class Employee
8
- include Plumbing::Actor
9
- async :name, :job_title, :greet_slowly, :promote
10
-
11
- def initialize(name)
12
- @name = name
13
- @job_title = "Sales assistant"
14
- end
15
-
16
- attr_reader :name, :job_title
17
-
18
- def promote
19
- sleep 0.5
20
- @job_title = "Sales manager"
21
- end
22
-
23
- def greet_slowly
24
- sleep 0.2
25
- "H E L L O"
26
- end
27
- end
28
- # standard:enable Lint/ConstantDefinitionInBlock
29
-
30
- it "queries an object" do
31
- @person = Employee.start "Alice"
32
-
33
- expect(await { @person.name }).to eq "Alice"
34
- expect(await { @person.job_title }).to eq "Sales assistant"
35
-
36
- @time = Time.now
37
- # `greet_slowly` is a query so will block until a response is received
38
- expect(await { @person.greet_slowly }).to eq "H E L L O"
39
- expect(Time.now - @time).to be > 0.1
40
-
41
- @time = Time.now
42
- # we're not awaiting the result, so this should run in the background (unless we're using inline mode)
43
- @person.greet_slowly
44
-
45
- expect(Time.now - @time).to be < 0.1 if runs_in_background
46
- expect(Time.now - @time).to be > 0.1 if !runs_in_background
47
- ensure
48
- @person.stop
49
- end
50
-
51
- it "commands an object" do
52
- @person = Employee.start "Alice"
53
- @person.promote
54
- expect(@person.job_title.value).to eq "Sales manager"
55
- ensure
56
- @person.stop
57
- end
58
- end
59
-
60
- RSpec.describe "Actor example: " do
61
- context "inline mode" do
62
- around :example do |example|
63
- Plumbing.configure mode: :inline, &example
64
- end
65
-
66
- it_behaves_like "an example actor", false
67
- end
68
-
69
- context "async mode" do
70
- around :example do |example|
71
- Plumbing.configure mode: :async do
72
- Kernel::Async(&example)
73
- end
74
- end
75
-
76
- it_behaves_like "an example actor", true
77
- end
78
-
79
- context "threaded mode" do
80
- around :example do |example|
81
- Plumbing.configure mode: :threaded, &example
82
- end
83
-
84
- it_behaves_like "an example actor", true
85
- end
86
- end
@@ -1,43 +0,0 @@
1
- require "spec_helper"
2
- require "plumbing/actor/async"
3
- require "plumbing/actor/threaded"
4
-
5
- RSpec.describe "await" do
6
- # standard:disable Lint/ConstantDefinitionInBlock
7
- class Person
8
- include Plumbing::Actor
9
- async :name
10
- def initialize name
11
- @name = name
12
- end
13
- attr_reader :name
14
- end
15
- # standard:enable Lint/ConstantDefinitionInBlock
16
-
17
- [:inline, :async, :threaded].each do |mode|
18
- context "#{mode} mode" do
19
- around :example do |example|
20
- Sync do
21
- Plumbing.configure mode: mode, &example
22
- end
23
- end
24
-
25
- it "awaits a result from the actor directly" do
26
- @person = Person.start "Alice"
27
-
28
- expect(@person.name.value).to eq "Alice"
29
- end
30
-
31
- it "uses a block to await the result from the actor" do
32
- @person = Person.start "Alice"
33
-
34
- expect(await { @person.name }).to eq "Alice"
35
- end
36
-
37
- it "uses a block to immediately access non-actor objects" do
38
- @person = "Bob"
39
- expect(await { @person }).to eq "Bob"
40
- end
41
- end
42
- end
43
- end
@@ -1,145 +0,0 @@
1
- require "spec_helper"
2
- require "async"
3
- require "plumbing/actor/async"
4
- require "plumbing/actor/threaded"
5
-
6
- RSpec.describe "Pipe examples" do
7
- it "observes events" do
8
- @source = Plumbing::Pipe.start
9
-
10
- @result = []
11
- @source.add_observer do |event|
12
- @result << event.type
13
- end
14
-
15
- @source.notify "something_happened", message: "But what was it?"
16
- expect(@result).to eq ["something_happened"]
17
- end
18
-
19
- it "filters events" do
20
- @source = Plumbing::Pipe.start
21
-
22
- @filter = Plumbing::Filter.start source: @source do |event|
23
- %w[important urgent].include? event.type
24
- end
25
-
26
- @result = []
27
- @filter.add_observer do |event|
28
- @result << event.type
29
- end
30
-
31
- @source.notify "important", message: "ALERT! ALERT!"
32
- expect(@result).to eq ["important"]
33
-
34
- @source.notify "unimportant", message: "Nothing to see here"
35
- expect(@result).to eq ["important"]
36
- end
37
-
38
- it "allows for custom filters" do
39
- # standard:disable Lint/ConstantDefinitionInBlock
40
- class EveryThirdEvent < Plumbing::CustomFilter
41
- def initialize source:
42
- super
43
- @events = []
44
- end
45
-
46
- def received event
47
- safely do
48
- @events << event
49
- if @events.count >= 3
50
- @events.clear
51
- self << event
52
- end
53
- end
54
- end
55
- end
56
- # standard:enable Lint/ConstantDefinitionInBlock
57
-
58
- @source = Plumbing::Pipe.start
59
- @filter = EveryThirdEvent.start(source: @source)
60
-
61
- @result = []
62
- @filter.add_observer do |event|
63
- @result << event.type
64
- end
65
-
66
- 1.upto 10 do |i|
67
- @source.notify i.to_s
68
- end
69
-
70
- expect(@result).to eq ["3", "6", "9"]
71
- end
72
-
73
- it "joins multiple source pipes" do
74
- @first_source = Plumbing::Pipe.start
75
- @second_source = Plumbing::Pipe.start
76
-
77
- @junction = Plumbing::Junction.start @first_source, @second_source
78
-
79
- @result = []
80
- @junction.add_observer do |event|
81
- @result << event.type
82
- end
83
-
84
- @first_source.notify "one"
85
- expect(@result).to eq ["one"]
86
- @second_source.notify "two"
87
- expect(@result).to eq ["one", "two"]
88
- end
89
-
90
- it "dispatches events asynchronously using async" do
91
- Plumbing.configure mode: :async do
92
- Sync do
93
- @first_source = Plumbing::Pipe.start
94
- @second_source = Plumbing::Pipe.start
95
- @junction = Plumbing::Junction.start @first_source, @second_source
96
- @filter = Plumbing::Filter.start source: @junction do |event|
97
- %w[one-one two-two].include? event.type
98
- end
99
- @result = []
100
- @filter.add_observer do |event|
101
- @result << event.type
102
- end
103
-
104
- @first_source.notify "one-one"
105
- @first_source.notify "one-two"
106
- @second_source.notify "two-one"
107
- @second_source.notify "two-two"
108
-
109
- expect(["one-one", "two-two"]).to become_equal_to { @result }
110
- end
111
- end
112
- end
113
-
114
- it "dispatches events asynchronously using threads" do
115
- Plumbing.configure mode: :threaded do
116
- @result = []
117
-
118
- @first_source = Plumbing::Pipe.start
119
- @second_source = Plumbing::Pipe.start
120
- @junction = Plumbing::Junction.start @first_source, @second_source
121
-
122
- @filter = Plumbing::Filter.start source: @junction do |event|
123
- %w[one-one two-two].include? event.type
124
- end
125
- await do
126
- @filter.add_observer do |event|
127
- puts "observing #{event.type}"
128
- @result << event.type
129
- end
130
- end
131
-
132
- @first_source.notify "one-one"
133
- @first_source.notify "one-two"
134
- @second_source.notify "two-one"
135
- @second_source.notify "two-two"
136
-
137
- expect(["one-one", "two-two"]).to become_equal_to { @result.sort }
138
- ensure
139
- @first_source.shutdown
140
- @second_source.shutdown
141
- @junction.shutdown
142
- @filter.shutdown
143
- end
144
- end
145
- end
@@ -1,89 +0,0 @@
1
- require "spec_helper"
2
- require "dry/validation"
3
-
4
- RSpec.describe "Pipeline examples" do
5
- it "builds a simple pipeline of operations adding to an array with pre-conditions and post-conditions" do
6
- # standard:disable Lint/ConstantDefinitionInBlock
7
- class BuildArray < Plumbing::Pipeline
8
- perform :add_first
9
- perform :add_second
10
- perform :add_third
11
-
12
- pre_condition :must_be_an_array do |input|
13
- input.is_a? Array
14
- end
15
-
16
- post_condition :must_have_three_elements do |output|
17
- output.length == 3
18
- end
19
-
20
- private
21
-
22
- def add_first(input) = input << "first"
23
-
24
- def add_second(input) = input << "second"
25
-
26
- def add_third(input) = input << "third"
27
- end
28
- # standard:enable Lint/ConstantDefinitionInBlock
29
-
30
- expect(BuildArray.new.call([])).to eq ["first", "second", "third"]
31
- expect { BuildArray.new.call(1) }.to raise_error(Plumbing::PreConditionError)
32
- expect { BuildArray.new.call(["extra element"]) }.to raise_error(Plumbing::PostConditionError)
33
- end
34
-
35
- it "builds a simple pipeline of operations using an external class to implement one of the steps" do
36
- # standard:disable Lint/ConstantDefinitionInBlock
37
- class ExternalStep < Plumbing::Pipeline
38
- perform :add_item_to_array
39
-
40
- private
41
-
42
- def add_item_to_array(input) = input << "external"
43
- end
44
-
45
- class BuildSequenceWithExternalStep < Plumbing::Pipeline
46
- perform :add_first
47
- perform :add_second, using: "ExternalStep"
48
- perform :add_third
49
-
50
- private
51
-
52
- def add_first(input) = input << "first"
53
-
54
- def add_third(input) = input << "third"
55
- end
56
- # standard:enable Lint/ConstantDefinitionInBlock
57
-
58
- expect(BuildSequenceWithExternalStep.new.call([])).to eq ["first", "external", "third"]
59
- end
60
-
61
- it "uses a dry-validation contract to test the input parameters" do
62
- # standard:disable Lint/ConstantDefinitionInBlock
63
- class SayHello < Plumbing::Pipeline
64
- validate_with "SayHello::Input"
65
- perform :say_hello
66
-
67
- private
68
-
69
- def say_hello input
70
- "Hello #{input[:name]} - I will now send a load of annoying marketing messages to #{input[:email]}"
71
- end
72
-
73
- class Input < Dry::Validation::Contract
74
- params do
75
- required(:name).filled(:string)
76
- required(:email).filled(:string)
77
- end
78
- rule :email do
79
- key.failure("must be a valid email") unless /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.match? value
80
- end
81
- end
82
- end
83
- # standard:enable Lint/ConstantDefinitionInBlock
84
-
85
- SayHello.new.call(name: "Alice", email: "alice@example.com")
86
-
87
- expect { SayHello.new.call(some: "other data") }.to raise_error(Plumbing::PreConditionError)
88
- end
89
- end