pg_eventstore 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/README.md +4 -0
  4. data/docs/subscriptions.md +14 -56
  5. data/exe/pg-eventstore +16 -0
  6. data/lib/pg_eventstore/callbacks.rb +16 -0
  7. data/lib/pg_eventstore/cli/commands/base_command.rb +45 -0
  8. data/lib/pg_eventstore/cli/commands/callback_handlers/start_cmd_handlers.rb +38 -0
  9. data/lib/pg_eventstore/cli/commands/help_command.rb +22 -0
  10. data/lib/pg_eventstore/cli/commands/start_subscriptions_command.rb +96 -0
  11. data/lib/pg_eventstore/cli/commands/stop_subscriptions_command.rb +22 -0
  12. data/lib/pg_eventstore/cli/commands.rb +6 -0
  13. data/lib/pg_eventstore/cli/exit_codes.rb +12 -0
  14. data/lib/pg_eventstore/cli/parser_options/base_options.rb +46 -0
  15. data/lib/pg_eventstore/cli/parser_options/default_options.rb +10 -0
  16. data/lib/pg_eventstore/cli/parser_options/metadata.rb +34 -0
  17. data/lib/pg_eventstore/cli/parser_options/subscription_options.rb +31 -0
  18. data/lib/pg_eventstore/cli/parser_options.rb +6 -0
  19. data/lib/pg_eventstore/cli/parsers/base_parser.rb +33 -0
  20. data/lib/pg_eventstore/cli/parsers/default_parser.rb +24 -0
  21. data/lib/pg_eventstore/cli/parsers/subscription_parser.rb +24 -0
  22. data/lib/pg_eventstore/cli/parsers.rb +5 -0
  23. data/lib/pg_eventstore/cli/try_to_delete_subscriptions_set.rb +75 -0
  24. data/lib/pg_eventstore/cli/try_unlock_subscriptions_set.rb +16 -0
  25. data/lib/pg_eventstore/cli/wait_for_subscriptions_set_shutdown.rb +67 -0
  26. data/lib/pg_eventstore/cli.rb +42 -0
  27. data/lib/pg_eventstore/extensions/options_extension.rb +53 -8
  28. data/lib/pg_eventstore/rspec/has_option_matcher.rb +38 -13
  29. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +13 -1
  30. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +1 -1
  31. data/lib/pg_eventstore/subscriptions/queries/subscription_command_queries.rb +1 -1
  32. data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rb +3 -1
  33. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +10 -50
  34. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands/ping.rb +22 -0
  35. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands.rb +1 -0
  36. data/lib/pg_eventstore/subscriptions/subscriptions_lifecycle.rb +7 -2
  37. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +44 -10
  38. data/lib/pg_eventstore/subscriptions/subscriptions_set_lifecycle.rb +1 -1
  39. data/lib/pg_eventstore/utils.rb +32 -0
  40. data/lib/pg_eventstore/version.rb +1 -1
  41. data/lib/pg_eventstore/web/views/home/partials/event_filter.erb +1 -1
  42. data/lib/pg_eventstore/web/views/home/partials/events.erb +5 -5
  43. data/lib/pg_eventstore/web/views/home/partials/stream_filter.erb +3 -3
  44. data/lib/pg_eventstore/web/views/layouts/application.erb +3 -3
  45. data/lib/pg_eventstore/web/views/subscriptions/index.erb +6 -6
  46. data/lib/pg_eventstore.rb +5 -2
  47. data/sig/pg_eventstore/callbacks.rbs +4 -0
  48. data/sig/pg_eventstore/cli/commands/base_command.rbs +13 -0
  49. data/sig/pg_eventstore/cli/commands/callback_handlers/start_cmd_handlers.rbs +14 -0
  50. data/sig/pg_eventstore/cli/commands/help_command.rbs +13 -0
  51. data/sig/pg_eventstore/cli/commands/start_subscriptions_command.rbs +28 -0
  52. data/sig/pg_eventstore/cli/commands/stop_subscriptions_command.rbs +11 -0
  53. data/sig/pg_eventstore/cli/exit_codes.rbs +8 -0
  54. data/sig/pg_eventstore/cli/parser_options/base_options.rbs +15 -0
  55. data/sig/pg_eventstore/cli/parser_options/default_options.rbs +8 -0
  56. data/sig/pg_eventstore/cli/parser_options/metadata.rbs +11 -0
  57. data/sig/pg_eventstore/cli/parser_options/subscription_options.rbs +9 -0
  58. data/sig/pg_eventstore/cli/parsers/base_parser.rbs +18 -0
  59. data/sig/pg_eventstore/cli/parsers/default_parser.rbs +9 -0
  60. data/sig/pg_eventstore/cli/parsers/subscription_parser.rbs +9 -0
  61. data/sig/pg_eventstore/cli/try_to_delete_subscriptions_set.rbs +24 -0
  62. data/sig/pg_eventstore/cli/try_unlock_subscriptions_set.rbs +7 -0
  63. data/sig/pg_eventstore/cli/wait_for_subscriptions_set_shutdown.rbs +26 -0
  64. data/sig/pg_eventstore/cli.rbs +10 -0
  65. data/sig/pg_eventstore/extensions/options_extension.rbs +18 -1
  66. data/sig/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rbs +8 -0
  67. data/sig/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rbs +7 -1
  68. data/sig/pg_eventstore/subscriptions/queries/subscription_command_queries.rbs +1 -1
  69. data/sig/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rbs +1 -1
  70. data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +10 -11
  71. data/sig/pg_eventstore/subscriptions/subscription_feeder_commands/ping.rbs +11 -0
  72. data/sig/pg_eventstore/subscriptions/subscriptions_lifecycle.rbs +1 -1
  73. data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +13 -1
  74. data/sig/pg_eventstore/utils.rbs +2 -0
  75. data/sig/pg_eventstore.rbs +2 -8
  76. metadata +44 -3
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CLI
5
+ class TryToDeleteSubscriptionsSet
6
+ class << self
7
+ def try_to_delete(...)
8
+ new(...).try_to_delete
9
+ end
10
+ end
11
+
12
+ attr_reader :config_name, :subscriptions_set_id
13
+
14
+ # @param config_name [Symbol]
15
+ # @param subscriptions_set_id [Integer]
16
+ def initialize(config_name, subscriptions_set_id)
17
+ @config_name = config_name
18
+ @subscriptions_set_id = subscriptions_set_id
19
+ end
20
+
21
+ # @return [Boolean] whether subscription was deleted
22
+ def try_to_delete
23
+ PgEventstore.logger&.info(
24
+ "Trying to delete SubscriptionsSet##{subscriptions_set_id}."
25
+ )
26
+ cmd_name = SubscriptionFeederCommands.command_class('Ping').new.name
27
+ subscriptions_set_commands_queries.find_or_create_by(
28
+ subscriptions_set_id: subscriptions_set_id, command_name: cmd_name, data: {}
29
+ )
30
+ # Potentially CommandsHandler can be dead exactly at the same moment we expect it to process "Ping" command.
31
+ # Wait for potential recover plus run interval and plus another second to allow potential processing of
32
+ # "Ping" command. "Ping" command comes in prio, so it is guaranteed it will be processed as a first command.
33
+ sleep CommandsHandler::RESTART_DELAY + CommandsHandler::PULL_INTERVAL + 1
34
+ if subscriptions_set_commands_queries.find_by(subscriptions_set_id: subscriptions_set_id, command_name: cmd_name)
35
+ # "Ping" command wasn't consumed. Related process must be dead.
36
+ subscriptions_set_queries.delete(subscriptions_set_id)
37
+ PgEventstore.logger&.info(
38
+ "SubscriptionsSet##{subscriptions_set_id} was deleted successfully. Proceeding with startup process."
39
+ )
40
+ return true
41
+ end
42
+
43
+ PgEventstore.logger&.warn(
44
+ "Failed to delete SubscriptionsSet##{subscriptions_set_id}. It looks alive."
45
+ )
46
+ false
47
+ rescue RecordNotFound
48
+ # Failed to create "Ping" command because related SubscriptionsSet does not exist.
49
+ true
50
+ end
51
+
52
+ private
53
+
54
+ # @return [PgEventstore::SubscriptionsSetQueries]
55
+ def subscriptions_set_queries
56
+ SubscriptionsSetQueries.new(connection)
57
+ end
58
+
59
+ # @return [PgEventstore::SubscriptionsSetCommandQueries]
60
+ def subscriptions_set_commands_queries
61
+ SubscriptionsSetCommandQueries.new(connection)
62
+ end
63
+
64
+ # @return [PgEventstore::Connection]
65
+ def connection
66
+ PgEventstore.connection(config_name)
67
+ end
68
+
69
+ # @return [PgEventstore::Config]
70
+ def config
71
+ PgEventstore.config(config_name)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'try_to_delete_subscriptions_set'
4
+ require_relative 'wait_for_subscriptions_set_shutdown'
5
+
6
+ module PgEventstore
7
+ module CLI
8
+ class TryUnlockSubscriptionsSet
9
+ class << self
10
+ def try_unlock(...)
11
+ TryToDeleteSubscriptionsSet.try_to_delete(...) || WaitForSubscriptionsSetShutdown.wait_for_shutdown(...)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CLI
5
+ class WaitForSubscriptionsSetShutdown
6
+ class << self
7
+ def wait_for_shutdown(...)
8
+ new(...).wait_for_shutdown
9
+ end
10
+ end
11
+
12
+ # @return [Float] seconds
13
+ SHUTDOWN_CHECK_INTERVAL = 2.0
14
+
15
+ attr_reader :config_name, :subscriptions_set_id
16
+
17
+ # @param config_name [Symbol]
18
+ # @param subscriptions_set_id [Integer]
19
+ def initialize(config_name, subscriptions_set_id)
20
+ @config_name = config_name
21
+ @subscriptions_set_id = subscriptions_set_id
22
+ end
23
+
24
+ # @return [Boolean]
25
+ def wait_for_shutdown
26
+ PgEventstore.logger&.info(
27
+ "Trying to wait for shutdown of SubscriptionsSet##{subscriptions_set_id}."
28
+ )
29
+ deadline = Time.now.utc + config.subscription_graceful_shutdown_timeout
30
+ loop do
31
+ find_set!
32
+ return false if Time.now.utc > deadline
33
+
34
+ sleep SHUTDOWN_CHECK_INTERVAL
35
+ end
36
+ rescue RecordNotFound
37
+ PgEventstore.logger&.warn(
38
+ "SubscriptionsSet##{subscriptions_set_id} no longer exists. Proceeding with startup process."
39
+ )
40
+ true
41
+ end
42
+
43
+ private
44
+
45
+ # @return [PgEventstore::SubscriptionsSetQueries]
46
+ def subscriptions_set_queries
47
+ SubscriptionsSetQueries.new(connection)
48
+ end
49
+
50
+ # @return [PgEventstore::SubscriptionsSet]
51
+ # @raise [PgEventstore::RecordNotFound]
52
+ def find_set!
53
+ SubscriptionsSet.using_connection(config_name).new(**subscriptions_set_queries.find!(subscriptions_set_id))
54
+ end
55
+
56
+ # @return [PgEventstore::Connection]
57
+ def connection
58
+ PgEventstore.connection(config_name)
59
+ end
60
+
61
+ # @return [PgEventstore::Config]
62
+ def config
63
+ PgEventstore.config(config_name)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require_relative 'cli/parsers'
5
+ require_relative 'cli/parser_options'
6
+ require_relative 'cli/try_unlock_subscriptions_set'
7
+ require_relative 'cli/exit_codes'
8
+ require_relative 'cli/commands'
9
+
10
+ module PgEventstore
11
+ module CLI
12
+ OPTIONS_PARSER = {
13
+ "subscriptions" => [Parsers::SubscriptionParser, ParserOptions::SubscriptionOptions].freeze
14
+ }.tap do |directions|
15
+ directions.default = [Parsers::DefaultParser, ParserOptions::DefaultOptions].freeze
16
+ end.freeze
17
+
18
+ COMMANDS = {
19
+ ["subscriptions", "start"].freeze => Commands::StartSubscriptionsCommand,
20
+ ["subscriptions", "stop"].freeze => Commands::StopSubscriptionsCommand
21
+ }.freeze
22
+
23
+ class << self
24
+ # @return [PgEventstore::Callbacks]
25
+ def callbacks
26
+ @callbacks ||= Callbacks.new
27
+ end
28
+
29
+ # @param args [Array<String>]
30
+ # @return [Integer] exit code
31
+ def execute(args)
32
+ options_parser_class, options_class = OPTIONS_PARSER[args[0]]
33
+ command, parsed_options = options_parser_class.new(ARGV, options_class.new).parse
34
+ return Commands::HelpCommand.new(parsed_options).call if parsed_options.help
35
+ return COMMANDS[command].new(parsed_options).call if COMMANDS[command]
36
+
37
+ _, parsed_options = options_parser_class.new(['-h'], options_class.new).parse
38
+ Commands::HelpCommand.new(parsed_options).call
39
+ end
40
+ end
41
+ end
42
+ end
@@ -40,14 +40,59 @@ module PgEventstore
40
40
  #
41
41
  # SomeClass.new(attr1: 'hihi', attr4: 'byebye')
42
42
  module OptionsExtension
43
+ class Option
44
+ attr_reader :name, :metadata
45
+
46
+ # @param name [Symbol]
47
+ # @param metadata [Object, nil]
48
+ def initialize(name, metadata: nil)
49
+ @name = name
50
+ @metadata = metadata
51
+ end
52
+
53
+ # @param other_option [Object]
54
+ # @return [Boolean]
55
+ def ==(other_option)
56
+ return false unless other_option.is_a?(Option)
57
+
58
+ name == other_option.name
59
+ end
60
+
61
+ # @param other_option [Object]
62
+ # @return [Boolean]
63
+ def eql?(other_option)
64
+ return false unless other_option.is_a?(Option)
65
+
66
+ name.eql?(other_option.name)
67
+ end
68
+
69
+ # @return [Integer]
70
+ def hash
71
+ name.hash
72
+ end
73
+ end
74
+
75
+ class Options < Set
76
+ def add(o)
77
+ @hash[o] = o
78
+ self
79
+ end
80
+
81
+ # @param option [Symbol]
82
+ # @return [PgEventstore::Extensions::OptionsExtension::Option, nil]
83
+ def [](option)
84
+ @hash[Option.new(option)]
85
+ end
86
+ end
87
+
43
88
  # @!visibility private
44
89
  module ClassMethods
45
90
  # @param opt_name [Symbol] option name
46
91
  # @param blk [Proc] provide define value using block. It will be later evaluated in the
47
92
  # context of your object to determine the default value of the option
48
93
  # @return [Symbol]
49
- def option(opt_name, &blk)
50
- self.options = (options + Set.new([opt_name])).freeze
94
+ def option(opt_name, metadata: nil, &blk)
95
+ self.options = (options + Options.new([Option.new(opt_name, metadata: metadata)])).freeze
51
96
  warn_already_defined(opt_name)
52
97
  warn_already_defined(:"#{opt_name}=")
53
98
  define_method "#{opt_name}=" do |value|
@@ -67,7 +112,7 @@ module PgEventstore
67
112
 
68
113
  def inherited(klass)
69
114
  super
70
- klass.options = Set.new(options).freeze
115
+ klass.options = Options.new(options).freeze
71
116
  end
72
117
 
73
118
  private
@@ -86,7 +131,7 @@ module PgEventstore
86
131
 
87
132
  def self.included(klass)
88
133
  klass.singleton_class.attr_accessor(:options)
89
- klass.options = Set.new.freeze
134
+ klass.options = Options.new.freeze
90
135
  klass.extend(ClassMethods)
91
136
  end
92
137
 
@@ -100,7 +145,7 @@ module PgEventstore
100
145
  # @return [Hash]
101
146
  def options_hash
102
147
  self.class.options.each_with_object({}) do |option, res|
103
- res[option] = public_send(option)
148
+ res[option.name] = public_send(option.name)
104
149
  end
105
150
  end
106
151
  alias attributes_hash options_hash
@@ -108,7 +153,7 @@ module PgEventstore
108
153
  # @param opt_name [Symbol]
109
154
  # @return [Boolean]
110
155
  def readonly!(opt_name)
111
- return false unless self.class.options.include?(opt_name)
156
+ return false unless self.class.options.include?(Option.new(opt_name))
112
157
 
113
158
  @readonly.add(opt_name)
114
159
  true
@@ -136,8 +181,8 @@ module PgEventstore
136
181
  def init_default_values(options)
137
182
  self.class.options.each do |option|
138
183
  # init default values of options
139
- value = options.key?(option) ? options[option] : public_send(option)
140
- public_send("#{option}=", value)
184
+ value = options.key?(option.name) ? options[option.name] : public_send(option.name)
185
+ public_send("#{option.name}=", value)
141
186
  end
142
187
  end
143
188
  end
@@ -45,25 +45,46 @@
45
45
  # end
46
46
  RSpec::Matchers.define :has_option do |option_name|
47
47
  match do |obj|
48
- option_presence = obj.class.respond_to?(:options) && obj.class.options.include?(option_name)
49
- if @default_value
50
- option_presence && RSpec::Matchers::BuiltIn::Match.new(@default_value).matches?(obj.class.allocate.public_send(option_name))
51
- else
52
- option_presence
48
+ option = obj.class.options[option_name]
49
+ is_correct = obj.class.respond_to?(:options) && option
50
+ if defined?(@default_value)
51
+ is_correct &&=
52
+ RSpec::Matchers::BuiltIn::Match.new(@default_value).matches?(obj.class.allocate.public_send(option_name))
53
+ end
54
+ if defined?(@metadata)
55
+ is_correct &&= RSpec::Matchers::BuiltIn::Match.new(@metadata).matches?(option.metadata)
53
56
  end
57
+ is_correct
54
58
  end
55
59
 
56
60
  failure_message do |obj|
57
- option_presence = obj.class.respond_to?(:options) && obj.class.options.include?(option_name)
58
- if option_presence && @default_value
59
- msg = "Expected #{obj.class} to have `#{option_name.inspect}' option with #{@default_value.inspect}"
60
- msg += ' default value, but default value is'
61
- msg += " #{obj.class.allocate.public_send(option_name).inspect}"
61
+ option = obj.class.options[option_name]
62
+ option_presence = obj.class.respond_to?(:options) && option
63
+
64
+ default_value_message = "with default value #{@default_value.inspect}"
65
+ metadata_message = "with metadata #{@metadata.inspect}"
66
+ message = "Expected #{obj.class} to have `#{option_name.inspect}' option"
67
+ message += " #{default_value_message}," if defined?(@default_value)
68
+ message += " #{metadata_message}," if defined?(@metadata)
69
+ message += "," unless defined?(@metadata) || defined?(@default_value)
70
+ if option_presence
71
+ actual_default_value = obj.class.allocate.public_send(option_name)
72
+ actual_metadata = option.metadata
73
+ default_value_matches = RSpec::Matchers::BuiltIn::Match.new(@default_value).matches?(actual_default_value)
74
+ metadata_matches = RSpec::Matchers::BuiltIn::Match.new(@metadata).matches?(actual_metadata)
75
+
76
+ case [default_value_matches, metadata_matches]
77
+ when [false, true]
78
+ message += " but default value is #{actual_default_value.inspect}"
79
+ when [true, false]
80
+ message += " but metadata is #{actual_metadata.inspect}"
81
+ else
82
+ message += " but default value is #{actual_default_value.inspect} and metadata is #{actual_metadata.inspect}"
83
+ end
62
84
  else
63
- msg = "Expected #{obj} to have `#{option_name.inspect}' option."
85
+ message += " but there is no option found with the given name"
64
86
  end
65
-
66
- msg
87
+ message
67
88
  end
68
89
 
69
90
  description do
@@ -83,6 +104,10 @@ RSpec::Matchers.define :has_option do |option_name|
83
104
  chain :with_default_value do |val|
84
105
  @default_value = val
85
106
  end
107
+
108
+ chain :with_metadata do |val|
109
+ @metadata = val
110
+ end
86
111
  end
87
112
 
88
113
  RSpec::Matchers.alias_matcher :have_option, :has_option
@@ -13,7 +13,7 @@ module PgEventstore
13
13
  # Look up commands for the given SubscriptionFeeder and execute them
14
14
  # @return [void]
15
15
  def process
16
- queries.find_commands(@subscription_feeder.id).each do |command|
16
+ commands.each do |command|
17
17
  command.exec_cmd(@subscription_feeder)
18
18
  queries.delete(command.id)
19
19
  end
@@ -21,6 +21,18 @@ module PgEventstore
21
21
 
22
22
  private
23
23
 
24
+ # @return [Array<PgEventstore::SubscriptionFeederCommands::Base>]
25
+ def commands
26
+ commands = queries.find_commands(@subscription_feeder.id)
27
+ ping_cmd = commands.find do |cmd|
28
+ cmd.name == 'Ping'
29
+ end
30
+ return commands unless ping_cmd
31
+
32
+ # "Ping" command should go in prio
33
+ [ping_cmd, *(commands - [ping_cmd])]
34
+ end
35
+
24
36
  # @return [PgEventstore::SubscriptionsSetCommandQueries]
25
37
  def queries
26
38
  SubscriptionsSetCommandQueries.new(connection)
@@ -5,7 +5,7 @@ module PgEventstore
5
5
  class SubscriptionRunnersCommands
6
6
  # @param config_name [Symbol]
7
7
  # @param runners [Array<PgEventstore::SubscriptionRunner>]
8
- # @param subscriptions_set_id [Integer]
8
+ # @param subscriptions_set_id [Integer, nil]
9
9
  def initialize(config_name, runners, subscriptions_set_id)
10
10
  @config_name = config_name
11
11
  @runners = runners.to_h { |runner| [runner.id, runner] }
@@ -64,7 +64,7 @@ module PgEventstore
64
64
  end
65
65
 
66
66
  # @param subscription_ids [Array<Integer>]
67
- # @param subscriptions_set_id [Integer]
67
+ # @param subscriptions_set_id [Integer, nil]
68
68
  # @return [Array<PgEventstore::SubscriptionRunnerCommands::Base>]
69
69
  def find_commands(subscription_ids, subscriptions_set_id:)
70
70
  return [] if subscription_ids.empty?
@@ -55,9 +55,11 @@ module PgEventstore
55
55
  conn.exec_params(sql, [command_name, subscriptions_set_id, data])
56
56
  end
57
57
  deserialize(pg_result.to_a.first)
58
+ rescue PG::ForeignKeyViolation
59
+ raise RecordNotFound.new("subscriptions_set", subscriptions_set_id)
58
60
  end
59
61
 
60
- # @param subscriptions_set_id [Integer]
62
+ # @param subscriptions_set_id [Integer, nil]
61
63
  # @return [Array<PgEventstore::SubscriptionFeederCommands::Base>]
62
64
  def find_commands(subscriptions_set_id)
63
65
  sql_builder =
@@ -6,37 +6,25 @@ module PgEventstore
6
6
  class SubscriptionFeeder
7
7
  extend Forwardable
8
8
 
9
- def_delegators :@basic_runner, :start, :stop, :restore, :state, :wait_for_finish, :stop_async
10
- def_delegators :@subscriptions_lifecycle, :force_lock!
9
+ attr_reader :config_name
10
+
11
+ def_delegators :@basic_runner, :start, :stop, :restore, :state, :wait_for_finish, :stop_async, :running?
11
12
 
12
13
  # @param config_name [Symbol]
13
- # @param set_name [String]
14
- # @param max_retries [Integer] max number of retries of failed SubscriptionsSet
15
- # @param retries_interval [Integer] a delay between retries of failed SubscriptionsSet
16
- def initialize(config_name:, set_name:, max_retries:, retries_interval:)
14
+ # @param subscriptions_set_lifecycle [PgEventstore::SubscriptionsSetLifecycle]
15
+ # @param subscriptions_lifecycle [PgEventstore::SubscriptionsLifecycle]
16
+ def initialize(config_name:, subscriptions_set_lifecycle:, subscriptions_lifecycle:)
17
17
  @config_name = config_name
18
18
  @basic_runner = BasicRunner.new(0.2, 0)
19
- @subscriptions_set_lifecycle = SubscriptionsSetLifecycle.new(
20
- @config_name,
21
- { name: set_name, max_restarts_number: max_retries, time_between_restarts: retries_interval }
22
- )
23
- @subscriptions_lifecycle = SubscriptionsLifecycle.new(@config_name, @subscriptions_set_lifecycle)
19
+ @subscriptions_set_lifecycle = subscriptions_set_lifecycle
20
+ @subscriptions_lifecycle = subscriptions_lifecycle
24
21
  @commands_handler = CommandsHandler.new(@config_name, self, @subscriptions_lifecycle.runners)
25
22
  attach_runner_callbacks
26
23
  end
27
24
 
28
- # @return [Integer]
25
+ # @return [Integer, nil]
29
26
  def id
30
- @subscriptions_set_lifecycle.persisted_subscriptions_set.id
31
- end
32
-
33
- # Adds SubscriptionRunner to the set
34
- # @param runner [PgEventstore::SubscriptionRunner]
35
- # @return [PgEventstore::SubscriptionRunner]
36
- def add(runner)
37
- assert_proper_state!
38
- @subscriptions_lifecycle.runners.push(runner)
39
- runner
27
+ @subscriptions_set_lifecycle.subscriptions_set&.id
40
28
  end
41
29
 
42
30
  # Starts all SubscriptionRunners. This is only available if SubscriptionFeeder's runner is alive.
@@ -57,20 +45,6 @@ module PgEventstore
57
45
  self
58
46
  end
59
47
 
60
- # Produces a copy of currently running Subscriptions. This is needed, because original Subscriptions objects are
61
- # dangerous to use - users may incidentally break their state.
62
- # @return [Array<PgEventstore::Subscription>]
63
- def read_only_subscriptions
64
- @subscriptions_lifecycle.subscriptions.map(&:dup)
65
- end
66
-
67
- # Produces a copy of current SubscriptionsSet. This is needed, because original SubscriptionsSet object is
68
- # dangerous to use - users may incidentally break its state.
69
- # @return [PgEventstore::SubscriptionsSet, nil]
70
- def read_only_subscriptions_set
71
- @subscriptions_set_lifecycle.subscriptions_set&.dup
72
- end
73
-
74
48
  private
75
49
 
76
50
  # @return [void]
@@ -133,19 +107,5 @@ module PgEventstore
133
107
  SubscriptionFeederHandlers.setup_handler(:update_subscriptions_set_restarts, @subscriptions_set_lifecycle)
134
108
  )
135
109
  end
136
-
137
- # This method helps to ensure that no Subscription is added after SubscriptionFeeder's runner is working
138
- # @return [void]
139
- # @raise [RuntimeError]
140
- def assert_proper_state!
141
- return if @basic_runner.initial? || @basic_runner.stopped?
142
- subscriptions_set = @subscriptions_set_lifecycle.persisted_subscriptions_set
143
-
144
- error_message = <<~TEXT
145
- Could not add subscription - #{subscriptions_set.name}##{subscriptions_set.id} must be \
146
- either in the initial or in the stopped state, but it is in the #{@basic_runner.state} state now.
147
- TEXT
148
- raise error_message
149
- end
150
110
  end
151
111
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionFeederCommands
5
+ # @!visibility private
6
+ class Ping < Base
7
+ # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
8
+ # @return [void]
9
+ def exec_cmd(subscription_feeder)
10
+ queries(subscription_feeder.config_name).update(subscriptions_set_id, {})
11
+ end
12
+
13
+ private
14
+
15
+ # @param config_name [Symbol]
16
+ # @return [PgEventstore::SubscriptionsSetQueries]
17
+ def queries(config_name)
18
+ SubscriptionsSetQueries.new(PgEventstore.connection(config_name))
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'subscription_feeder_commands/base'
4
+ require_relative 'subscription_feeder_commands/ping'
4
5
  require_relative 'subscription_feeder_commands/restore'
5
6
  require_relative 'subscription_feeder_commands/start_all'
6
7
  require_relative 'subscription_feeder_commands/stop'
@@ -13,12 +13,12 @@ module PgEventstore
13
13
 
14
14
  # @param config_name [Symbol]
15
15
  # @param subscriptions_set_lifecycle [PgEventstore::SubscriptionsSetLifecycle]
16
- def initialize(config_name, subscriptions_set_lifecycle)
16
+ def initialize(config_name, subscriptions_set_lifecycle, force_lock: false)
17
17
  @config_name = config_name
18
18
  @subscriptions_set_lifecycle = subscriptions_set_lifecycle
19
19
  @runners = []
20
20
  @subscriptions_pinged_at = Time.at(0)
21
- @force_lock = false
21
+ @force_lock = force_lock
22
22
  end
23
23
 
24
24
  # Locks all Subscriptions behind the current SubscriptionsSet
@@ -59,6 +59,11 @@ module PgEventstore
59
59
  # locked by the new SubscriptionsSet.
60
60
  # @return [void]
61
61
  def force_lock!
62
+ message = <<~TEXT
63
+ Usage of #force_lock! is deprecated and will be removed in v2. Please pass :force_lock keyword argument when \
64
+ initializing SubscriptionsManager. Example: PgEventstore.subscriptions_manager(force_lock: true)
65
+ TEXT
66
+ Utils.deprecation_warning(message)
62
67
  @force_lock = true
63
68
  end
64
69