standard-procedure-plumbing 0.4.3 → 0.4.4

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