pg_eventstore 0.10.2 → 1.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +26 -0
  4. data/db/migrations/1_create_events.sql +12 -11
  5. data/db/migrations/2_create_subscriptions.sql +6 -2
  6. data/db/migrations/3_create_subscription_commands.sql +9 -5
  7. data/db/migrations/4_create_subscriptions_set_commands.sql +1 -1
  8. data/db/migrations/5_partitions.sql +1 -0
  9. data/docs/how_it_works.md +14 -1
  10. data/lib/pg_eventstore/commands/append.rb +1 -1
  11. data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +30 -8
  12. data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +8 -10
  13. data/lib/pg_eventstore/commands/link_to.rb +14 -7
  14. data/lib/pg_eventstore/errors.rb +10 -12
  15. data/lib/pg_eventstore/event.rb +4 -0
  16. data/lib/pg_eventstore/queries/event_queries.rb +27 -6
  17. data/lib/pg_eventstore/queries/links_resolver.rb +28 -6
  18. data/lib/pg_eventstore/queries/partition_queries.rb +8 -0
  19. data/lib/pg_eventstore/queries/subscription_command_queries.rb +27 -7
  20. data/lib/pg_eventstore/queries/subscription_queries.rb +58 -28
  21. data/lib/pg_eventstore/queries/subscriptions_set_command_queries.rb +13 -1
  22. data/lib/pg_eventstore/queries/subscriptions_set_queries.rb +18 -4
  23. data/lib/pg_eventstore/query_builders/events_filtering_query.rb +4 -4
  24. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +10 -2
  25. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +9 -7
  26. data/lib/pg_eventstore/subscriptions/commands_handler.rb +3 -2
  27. data/lib/pg_eventstore/subscriptions/subscription.rb +28 -12
  28. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +19 -15
  29. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +1 -1
  30. data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +1 -1
  31. data/lib/pg_eventstore/subscriptions/subscriptions_set.rb +22 -1
  32. data/lib/pg_eventstore/version.rb +1 -1
  33. data/lib/pg_eventstore/web/application.rb +187 -0
  34. data/lib/pg_eventstore/web/paginator/base_collection.rb +56 -0
  35. data/lib/pg_eventstore/web/paginator/event_types_collection.rb +50 -0
  36. data/lib/pg_eventstore/web/paginator/events_collection.rb +105 -0
  37. data/lib/pg_eventstore/web/paginator/helpers.rb +119 -0
  38. data/lib/pg_eventstore/web/paginator/stream_contexts_collection.rb +48 -0
  39. data/lib/pg_eventstore/web/paginator/stream_ids_collection.rb +50 -0
  40. data/lib/pg_eventstore/web/paginator/stream_names_collection.rb +51 -0
  41. data/lib/pg_eventstore/web/public/fonts/vendor/FontAwesome.otf +0 -0
  42. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.eot +0 -0
  43. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.svg +685 -0
  44. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.ttf +0 -0
  45. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.woff +0 -0
  46. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.woff2 +0 -0
  47. data/lib/pg_eventstore/web/public/images/favicon.ico +0 -0
  48. data/lib/pg_eventstore/web/public/javascripts/gentelella.js +334 -0
  49. data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +197 -0
  50. data/lib/pg_eventstore/web/public/javascripts/vendor/bootstrap.bundle.min.js +7 -0
  51. data/lib/pg_eventstore/web/public/javascripts/vendor/bootstrap.bundle.min.js.map +1 -0
  52. data/lib/pg_eventstore/web/public/javascripts/vendor/jquery.autocomplete.min.js +8 -0
  53. data/lib/pg_eventstore/web/public/javascripts/vendor/jquery.min.js +4 -0
  54. data/lib/pg_eventstore/web/public/javascripts/vendor/jquery.min.js.map +1 -0
  55. data/lib/pg_eventstore/web/public/javascripts/vendor/select2.full.min.js +2 -0
  56. data/lib/pg_eventstore/web/public/stylesheets/pg_eventstore.css +9 -0
  57. data/lib/pg_eventstore/web/public/stylesheets/vendor/bootstrap.min.css +7 -0
  58. data/lib/pg_eventstore/web/public/stylesheets/vendor/bootstrap.min.css.map +1 -0
  59. data/lib/pg_eventstore/web/public/stylesheets/vendor/font-awesome.min.css +4 -0
  60. data/lib/pg_eventstore/web/public/stylesheets/vendor/font-awesome.min.css.map +7 -0
  61. data/lib/pg_eventstore/web/public/stylesheets/vendor/gentelella.min.css +13 -0
  62. data/lib/pg_eventstore/web/public/stylesheets/vendor/select2-bootstrap4.min.css +3 -0
  63. data/lib/pg_eventstore/web/public/stylesheets/vendor/select2.min.css +2 -0
  64. data/lib/pg_eventstore/web/subscriptions/helpers.rb +76 -0
  65. data/lib/pg_eventstore/web/subscriptions/set_collection.rb +34 -0
  66. data/lib/pg_eventstore/web/subscriptions/subscriptions.rb +33 -0
  67. data/lib/pg_eventstore/web/subscriptions/subscriptions_set.rb +33 -0
  68. data/lib/pg_eventstore/web/subscriptions/subscriptions_to_set_association.rb +32 -0
  69. data/lib/pg_eventstore/web/views/home/dashboard.erb +147 -0
  70. data/lib/pg_eventstore/web/views/home/partials/event_filter.erb +15 -0
  71. data/lib/pg_eventstore/web/views/home/partials/events.erb +22 -0
  72. data/lib/pg_eventstore/web/views/home/partials/pagination_links.erb +3 -0
  73. data/lib/pg_eventstore/web/views/home/partials/stream_filter.erb +31 -0
  74. data/lib/pg_eventstore/web/views/layouts/application.erb +135 -0
  75. data/lib/pg_eventstore/web/views/subscriptions/index.erb +220 -0
  76. data/lib/pg_eventstore/web.rb +22 -0
  77. data/lib/pg_eventstore.rb +5 -0
  78. data/pg_eventstore.gemspec +2 -1
  79. metadata +60 -2
@@ -22,11 +22,7 @@ module PgEventstore
22
22
  # @param attrs [Hash]
23
23
  # @return [Hash, nil]
24
24
  def find_by(attrs)
25
- builder = SQLBuilder.new.select('*').from('subscriptions')
26
- attrs.each do |attr, val|
27
- builder.where("#{attr} = ?", val)
28
- end
29
-
25
+ builder = find_by_attrs_builder(attrs).limit(1)
30
26
  pg_result = connection.with do |conn|
31
27
  conn.exec_params(*builder.to_exec_params)
32
28
  end
@@ -35,6 +31,25 @@ module PgEventstore
35
31
  deserialize(pg_result.to_a.first)
36
32
  end
37
33
 
34
+ # @param attrs [Hash]
35
+ # @return [Array<Hash>]
36
+ def find_all(attrs)
37
+ builder = find_by_attrs_builder(attrs)
38
+ pg_result = connection.with do |conn|
39
+ conn.exec_params(*builder.to_exec_params)
40
+ end
41
+ return [] if pg_result.ntuples.zero?
42
+
43
+ pg_result.map(&method(:deserialize))
44
+ end
45
+
46
+ # @return [Array<String>]
47
+ def set_collection
48
+ connection.with do |conn|
49
+ conn.exec('SELECT set FROM subscriptions GROUP BY set ORDER BY set ASC')
50
+ end.map { |attrs| attrs['set'] }
51
+ end
52
+
38
53
  # @param id [Integer]
39
54
  # @return [Hash]
40
55
  # @raise [PgEventstore::RecordNotFound]
@@ -58,19 +73,32 @@ module PgEventstore
58
73
 
59
74
  # @param id [Integer]
60
75
  # @param attrs [Hash]
61
- def update(id, attrs)
76
+ # @param locked_by [Integer]
77
+ # @return [Hash]
78
+ # @raise [PgEventstore::RecordNotFound]
79
+ # @raise [PgEventstore::WrongLockIdError]
80
+ def update(id, attrs:, locked_by:)
62
81
  attrs = { updated_at: Time.now.utc }.merge(attrs)
63
82
  attrs_sql = attrs.keys.map.with_index(1) do |attr, index|
64
83
  "#{attr} = $#{index}"
65
84
  end.join(', ')
66
85
  sql =
67
86
  "UPDATE subscriptions SET #{attrs_sql} WHERE id = $#{attrs.keys.size + 1} RETURNING *"
68
- pg_result = connection.with do |conn|
69
- conn.exec_params(sql, [*attrs.values, id])
87
+ updated_attrs = transaction_queries.transaction do
88
+ pg_result = connection.with do |conn|
89
+ conn.exec_params(sql, [*attrs.values, id])
90
+ end
91
+ raise(RecordNotFound.new("subscriptions", id)) if pg_result.ntuples.zero?
92
+
93
+ updated_attrs = pg_result.to_a.first
94
+ unless updated_attrs['locked_by'] == locked_by
95
+ # Subscription is force-locked by someone else. We have to roll back such transaction
96
+ raise(WrongLockIdError.new(updated_attrs['set'], updated_attrs['name'], updated_attrs['locked_by']))
97
+ end
98
+ updated_attrs
70
99
  end
71
- raise(RecordNotFound.new("subscriptions", id)) if pg_result.ntuples.zero?
72
100
 
73
- deserialize(pg_result.to_a.first)
101
+ deserialize(updated_attrs)
74
102
  end
75
103
 
76
104
  # @param query_options [Hash{Integer => Hash}] runner_id/query options association
@@ -90,13 +118,16 @@ module PgEventstore
90
118
  end
91
119
 
92
120
  # @param id [Integer] subscription's id
93
- # @param lock_id [String] UUIDv4 id of the subscriptions set which reserves the subscription
121
+ # @param lock_id [Integer] id of the subscriptions set which reserves the subscription
94
122
  # @param force [Boolean] whether to lock the subscription despite on #locked_by value
95
- # @return [String] UUIDv4 lock id
123
+ # @return [Integer] lock id
96
124
  # @raise [SubscriptionAlreadyLockedError] in case the Subscription is already locked
97
- def lock!(id, lock_id, force = false)
125
+ def lock!(id, lock_id, force: false)
98
126
  transaction_queries.transaction do
99
127
  attrs = find!(id)
128
+ # We don't care who locked the Subscription - whether it is the same SubscriptionsSet or not - multiple locks
129
+ # must not happen even with the same SubscriptionsSet. We later assume this to reset Subscription's stats, for
130
+ # example.
100
131
  if attrs[:locked_by] && !force
101
132
  raise SubscriptionAlreadyLockedError.new(attrs[:set], attrs[:name], attrs[:locked_by])
102
133
  end
@@ -107,22 +138,11 @@ module PgEventstore
107
138
  lock_id
108
139
  end
109
140
 
110
- # @param id [Integer] subscription's id
111
- # @param lock_id [String] UUIDv4 id of the set which reserved the subscription after itself
141
+ # @param id [Integer]
112
142
  # @return [void]
113
- # @raise [SubscriptionUnlockError] in case the Subscription is locked by some SubscriptionsSet, other than the one,
114
- # persisted in memory
115
- def unlock!(id, lock_id)
116
- transaction_queries.transaction do
117
- attrs = find!(id)
118
- # Normally this should never happen as locking/unlocking happens within the same process. This is done only for
119
- # the matter of consistency.
120
- unless attrs[:locked_by] == lock_id
121
- raise SubscriptionUnlockError.new(attrs[:set], attrs[:name], lock_id, attrs[:locked_by])
122
- end
123
- connection.with do |conn|
124
- conn.exec_params('UPDATE subscriptions SET locked_by = $1 WHERE id = $2', [nil, id])
125
- end
143
+ def delete(id)
144
+ connection.with do |conn|
145
+ conn.exec_params('DELETE FROM subscriptions WHERE id = $1', [id])
126
146
  end
127
147
  end
128
148
 
@@ -159,5 +179,15 @@ module PgEventstore
159
179
  def deserialize(hash)
160
180
  hash.transform_keys(&:to_sym)
161
181
  end
182
+
183
+ # @param attrs [Hash]
184
+ # @return [PgEventstore::SQLBuilder]
185
+ def find_by_attrs_builder(attrs)
186
+ builder = SQLBuilder.new.select('*').from('subscriptions').order('id ASC')
187
+ attrs.each do |attr, val|
188
+ builder.where("#{attr} = ?", val)
189
+ end
190
+ builder
191
+ end
162
192
  end
163
193
  end
@@ -11,6 +11,13 @@ module PgEventstore
11
11
  @connection = connection
12
12
  end
13
13
 
14
+ # @return [Hash]
15
+ def find_or_create_by(...)
16
+ transaction_queries.transaction do
17
+ find_by(...) || create(...)
18
+ end
19
+ end
20
+
14
21
  # @param subscriptions_set_id [Integer]
15
22
  # @param command_name [String]
16
23
  # @return [Hash, nil]
@@ -31,7 +38,7 @@ module PgEventstore
31
38
  # @param subscriptions_set_id [Integer]
32
39
  # @param command_name [String]
33
40
  # @return [Hash]
34
- def create_by(subscriptions_set_id:, command_name:)
41
+ def create(subscriptions_set_id:, command_name:)
35
42
  sql = <<~SQL
36
43
  INSERT INTO subscriptions_set_commands (name, subscriptions_set_id)
37
44
  VALUES ($1, $2)
@@ -72,5 +79,10 @@ module PgEventstore
72
79
  def deserialize(hash)
73
80
  hash.transform_keys(&:to_sym)
74
81
  end
82
+
83
+ # @return [PgEventstore::TransactionQueries]
84
+ def transaction_queries
85
+ TransactionQueries.new(connection)
86
+ end
75
87
  end
76
88
  end
@@ -14,7 +14,7 @@ module PgEventstore
14
14
  # @param attrs [Hash]
15
15
  # @return [Array<Hash>]
16
16
  def find_all(attrs)
17
- builder = SQLBuilder.new.select('*').from('subscriptions_set')
17
+ builder = SQLBuilder.new.select('*').from('subscriptions_set').order('name ASC, id ASC')
18
18
  attrs.each do |attr, val|
19
19
  builder.where("#{attr} = ?", val)
20
20
  end
@@ -25,13 +25,22 @@ module PgEventstore
25
25
  pg_result.to_a.map(&method(:deserialize))
26
26
  end
27
27
 
28
+ # @return [Array<String>]
29
+ def set_names
30
+ builder = SQLBuilder.new.select('name').from('subscriptions_set').group('name').order('name ASC')
31
+
32
+ connection.with do |conn|
33
+ conn.exec_params(*builder.to_exec_params)
34
+ end.map { |attrs| attrs['name'] }
35
+ end
36
+
28
37
  # The same as #find_all, but returns first result
29
38
  # @return [Hash, nil]
30
39
  def find_by(...)
31
40
  find_all(...).first
32
41
  end
33
42
 
34
- # @param id [String] UUIDv4
43
+ # @param id [Integer]
35
44
  # @return [Hash]
36
45
  # @raise [PgEventstore::RecordNotFound]
37
46
  def find!(id)
@@ -52,7 +61,7 @@ module PgEventstore
52
61
  deserialize(pg_result.to_a.first)
53
62
  end
54
63
 
55
- # @param id [String] UUIDv4
64
+ # @param id [Integer]
56
65
  # @param attrs [Hash]
57
66
  def update(id, attrs)
58
67
  attrs = { updated_at: Time.now.utc }.merge(attrs)
@@ -70,7 +79,7 @@ module PgEventstore
70
79
  deserialize(pg_result.to_a.first)
71
80
  end
72
81
 
73
- # @return id [Integer]
82
+ # @param id [Integer]
74
83
  # @return [void]
75
84
  def delete(id)
76
85
  connection.with do |conn|
@@ -85,5 +94,10 @@ module PgEventstore
85
94
  def deserialize(hash)
86
95
  hash.transform_keys(&:to_sym)
87
96
  end
97
+
98
+ # @return [PgEventstore::TransactionQueries]
99
+ def transaction_queries
100
+ TransactionQueries.new(connection)
101
+ end
88
102
  end
89
103
  end
@@ -28,8 +28,8 @@ module PgEventstore
28
28
  # @return [PgEventstore::QueryBuilders::EventsFiltering]
29
29
  def all_stream_filtering(options)
30
30
  event_filter = new
31
- options in { filter: { event_types: Array => event_type_ids } }
32
- event_filter.add_event_types(event_type_ids)
31
+ options in { filter: { event_types: Array => event_types } }
32
+ event_filter.add_event_types(event_types)
33
33
  event_filter.add_limit(options[:max_count])
34
34
  options in { filter: { streams: Array => streams } }
35
35
  streams&.each { |attrs| event_filter.add_stream_attrs(**attrs) }
@@ -43,8 +43,8 @@ module PgEventstore
43
43
  # @return [PgEventstore::QueryBuilders::EventsFiltering]
44
44
  def specific_stream_filtering(stream, options)
45
45
  event_filter = new
46
- options in { filter: { event_types: Array => event_type_ids } }
47
- event_filter.add_event_types(event_type_ids)
46
+ options in { filter: { event_types: Array => event_types } }
47
+ event_filter.add_event_types(event_types)
48
48
  event_filter.add_limit(options[:max_count])
49
49
  event_filter.add_stream_attrs(**stream.to_hash)
50
50
  event_filter.add_revision(options[:from_revision], options[:direction])
@@ -3,7 +3,7 @@
3
3
  module PgEventstore
4
4
  module CommandHandlers
5
5
  class SubscriptionFeederCommands
6
- AVAILABLE_COMMANDS = %w[StopAll StartAll].freeze
6
+ AVAILABLE_COMMANDS = %i[StopAll StartAll Restore Stop].to_h { [_1, _1.to_s] }.freeze
7
7
 
8
8
  # @param config_name [Symbol]
9
9
  # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
@@ -16,7 +16,7 @@ module PgEventstore
16
16
  # @return [void]
17
17
  def process
18
18
  queries.find_commands(@subscription_feeder.id).each do |command|
19
- unless AVAILABLE_COMMANDS.include?(command[:name])
19
+ unless AVAILABLE_COMMANDS.values.include?(command[:name])
20
20
  PgEventstore.logger&.warn(
21
21
  "#{self.class.name}: Don't know how to handle #{command[:name].inspect}. Details: #{command.inspect}."
22
22
  )
@@ -47,6 +47,14 @@ module PgEventstore
47
47
  def start_all
48
48
  @subscription_feeder.start_all
49
49
  end
50
+
51
+ def restore
52
+ @subscription_feeder.restore
53
+ end
54
+
55
+ def stop
56
+ @subscription_feeder.stop
57
+ end
50
58
  end
51
59
  end
52
60
  end
@@ -3,20 +3,22 @@
3
3
  module PgEventstore
4
4
  module CommandHandlers
5
5
  class SubscriptionRunnersCommands
6
- AVAILABLE_COMMANDS = %w[StopRunner RestoreRunner StartRunner].freeze
6
+ AVAILABLE_COMMANDS = %i[Start Stop Restore].to_h { [_1, _1.to_s] }.freeze
7
7
 
8
8
  # @param config_name [Symbol]
9
9
  # @param runners [Array<PgEventstore::SubscriptionRunner>]
10
- def initialize(config_name, runners)
10
+ # @param subscriptions_set_id [Integer]
11
+ def initialize(config_name, runners, subscriptions_set_id)
11
12
  @config_name = config_name
12
13
  @runners = runners
14
+ @subscriptions_set_id = subscriptions_set_id
13
15
  end
14
16
 
15
17
  # Look up commands for all given SubscriptionRunner-s and execute them
16
18
  # @return [void]
17
19
  def process
18
- queries.find_commands(@runners.map(&:id)).each do |command|
19
- unless AVAILABLE_COMMANDS.include?(command[:name])
20
+ queries.find_commands(@runners.map(&:id), subscriptions_set_id: @subscriptions_set_id).each do |command|
21
+ unless AVAILABLE_COMMANDS.values.include?(command[:name])
20
22
  PgEventstore.logger&.warn(
21
23
  "#{self.class.name}: Don't know how to handle #{command[:name].inspect}. Details: #{command.inspect}."
22
24
  )
@@ -48,19 +50,19 @@ module PgEventstore
48
50
 
49
51
  # @param subscription_id [Integer]
50
52
  # @return [void]
51
- def start_runner(subscription_id)
53
+ def start(subscription_id)
52
54
  find_subscription_runner(subscription_id)&.start
53
55
  end
54
56
 
55
57
  # @param subscription_id [Integer]
56
58
  # @return [void]
57
- def restore_runner(subscription_id)
59
+ def restore(subscription_id)
58
60
  find_subscription_runner(subscription_id)&.restore
59
61
  end
60
62
 
61
63
  # @param subscription_id [Integer]
62
64
  # @return [void]
63
- def stop_runner(subscription_id)
65
+ def stop(subscription_id)
64
66
  find_subscription_runner(subscription_id)&.stop_async
65
67
  end
66
68
  end
@@ -11,6 +11,7 @@ module PgEventstore
11
11
  extend Forwardable
12
12
 
13
13
  RESTART_DELAY = 5 # seconds
14
+ PULL_INTERVAL = 1
14
15
 
15
16
  def_delegators :@basic_runner, :start, :stop, :state, :stop_async, :wait_for_finish
16
17
 
@@ -21,7 +22,7 @@ module PgEventstore
21
22
  @config_name = config_name
22
23
  @subscription_feeder = subscription_feeder
23
24
  @runners = runners
24
- @basic_runner = BasicRunner.new(1, 0)
25
+ @basic_runner = BasicRunner.new(PULL_INTERVAL, 0)
25
26
  attach_runner_callbacks
26
27
  end
27
28
 
@@ -56,7 +57,7 @@ module PgEventstore
56
57
 
57
58
  # @return [PgEventstore::CommandHandlers::SubscriptionRunnersCommands]
58
59
  def subscription_runners_commands
59
- CommandHandlers::SubscriptionRunnersCommands.new(@config_name, @runners)
60
+ CommandHandlers::SubscriptionRunnersCommands.new(@config_name, @runners, @subscription_feeder.id)
60
61
  end
61
62
  end
62
63
  end
@@ -63,8 +63,8 @@ module PgEventstore
63
63
  # processor
64
64
  attribute(:last_chunk_greatest_position)
65
65
  # @!attribute locked_by
66
- # @return [String, nil] UUIDv4. The id of subscription manager which obtained the lock of the Subscription. _nil_
67
- # value means that the Subscription isn't locked yet by any subscription manager.
66
+ # @return [Integer, nil] The id of subscription manager which obtained the lock of the Subscription. _nil_ value
67
+ # means that the Subscription isn't locked yet by any subscription manager.
68
68
  attribute(:locked_by)
69
69
  # @!attribute created_at
70
70
  # @return [Time]
@@ -80,7 +80,7 @@ module PgEventstore
80
80
  # @param attrs [Hash]
81
81
  # @return [Hash]
82
82
  def update(attrs)
83
- assign_attributes(subscription_queries.update(id, attrs))
83
+ assign_attributes(subscription_queries.update(id, attrs: attrs, locked_by: locked_by))
84
84
  end
85
85
 
86
86
  # @param attrs [Hash]
@@ -93,20 +93,13 @@ module PgEventstore
93
93
 
94
94
  # Locks the Subscription by the given lock id
95
95
  # @return [PgEventstore::Subscription]
96
- def lock!(lock_id, force = false)
96
+ def lock!(lock_id, force: false)
97
97
  self.id = subscription_queries.find_or_create_by(set: set, name: name)[:id]
98
- self.locked_by = subscription_queries.lock!(id, lock_id, force)
98
+ self.locked_by = subscription_queries.lock!(id, lock_id, force: force)
99
99
  reset_runtime_attributes
100
100
  self
101
101
  end
102
102
 
103
- # Unlocks the Subscription.
104
- # @return [void]
105
- def unlock!
106
- subscription_queries.unlock!(id, locked_by)
107
- self.locked_by = nil
108
- end
109
-
110
103
  # Dup the current object without assigned connection
111
104
  # @return [PgEventstore::Subscription]
112
105
  def dup
@@ -119,6 +112,27 @@ module PgEventstore
119
112
  self
120
113
  end
121
114
 
115
+ # @return [Integer]
116
+ def hash
117
+ id.hash
118
+ end
119
+
120
+ # @param another [Object]
121
+ # @return [Boolean]
122
+ def eql?(another)
123
+ return false unless another.is_a?(Subscription)
124
+
125
+ hash == another.hash
126
+ end
127
+
128
+ # @param another [PgEventstore::SubscriptionsSet]
129
+ # @return [Boolean]
130
+ def ==(another)
131
+ return false unless another.is_a?(Subscription)
132
+
133
+ id == another.id
134
+ end
135
+
122
136
  private
123
137
 
124
138
  def reset_runtime_attributes
@@ -130,6 +144,8 @@ module PgEventstore
130
144
  chunk_query_interval: chunk_query_interval,
131
145
  last_chunk_fed_at: Time.at(0).utc,
132
146
  last_chunk_greatest_position: nil,
147
+ last_error: nil,
148
+ last_error_occurred_at: nil,
133
149
  time_between_restarts: time_between_restarts,
134
150
  state: RunnerState::STATES[:initial]
135
151
  )
@@ -3,10 +3,11 @@
3
3
  module PgEventstore
4
4
  # This class is responsible for starting/stopping all SubscriptionRunners. The background runner of it is responsible
5
5
  # for events pulling and feeding those SubscriptionRunners.
6
- # @!visibility private
7
6
  class SubscriptionFeeder
8
7
  extend Forwardable
9
8
 
9
+ HEARTBEAT_INTERVAL = 10 # seconds
10
+
10
11
  def_delegators :subscriptions_set, :id
11
12
  def_delegators :@basic_runner, :start, :stop, :restore, :state, :wait_for_finish, :stop_async
12
13
 
@@ -17,12 +18,13 @@ module PgEventstore
17
18
  def initialize(config_name:, set_name:, max_retries:, retries_interval:)
18
19
  @config_name = config_name
19
20
  @runners = []
20
- @set_name = set_name
21
- @max_retries = max_retries
22
- @retries_interval = retries_interval
21
+ @subscriptions_set_attrs = {
22
+ name: set_name, max_restarts_number: max_retries, time_between_restarts: retries_interval
23
+ }
23
24
  @commands_handler = CommandsHandler.new(@config_name, self, @runners)
24
25
  @basic_runner = BasicRunner.new(0.2, 0)
25
26
  @force_lock = false
27
+ @refreshed_at = Time.at(0)
26
28
  attach_runner_callbacks
27
29
  end
28
30
 
@@ -77,18 +79,12 @@ module PgEventstore
77
79
  # Locks all Subscriptions behind the current SubscriptionsSet
78
80
  # @return [void]
79
81
  def lock_all
80
- @runners.each { |runner| runner.lock!(subscriptions_set.id, @force_lock) }
81
- end
82
-
83
- # @return [void]
84
- def unlock_all
85
- @runners.each(&:unlock!)
82
+ @runners.each { |runner| runner.lock!(subscriptions_set.id, force: @force_lock) }
86
83
  end
87
84
 
88
85
  # @return [PgEventstore::SubscriptionsSet]
89
86
  def subscriptions_set
90
- @subscriptions_set ||= SubscriptionsSet.using_connection(@config_name).
91
- create(name: @set_name, max_restarts_number: @max_retries, time_between_restarts: @retries_interval)
87
+ @subscriptions_set ||= SubscriptionsSet.using_connection(@config_name).create(**@subscriptions_set_attrs)
92
88
  end
93
89
 
94
90
  # @return [PgEventstore::SubscriptionRunnersFeeder]
@@ -102,6 +98,7 @@ module PgEventstore
102
98
  @basic_runner.define_callback(:before_runner_started, :before, method(:before_runner_started))
103
99
  @basic_runner.define_callback(:after_runner_died, :before, method(:after_runner_died))
104
100
  @basic_runner.define_callback(:after_runner_died, :after, method(:restart_runner))
101
+ @basic_runner.define_callback(:process_async, :before, method(:ping_subscriptions_set))
105
102
  @basic_runner.define_callback(:process_async, :before, method(:process_async))
106
103
  @basic_runner.define_callback(:after_runner_stopped, :before, method(:after_runner_stopped))
107
104
  @basic_runner.define_callback(:before_runner_restored, :after, method(:update_runner_restarts))
@@ -141,13 +138,20 @@ module PgEventstore
141
138
  feeder.feed(@runners)
142
139
  end
143
140
 
141
+ # @return [void]
142
+ def ping_subscriptions_set
143
+ return unless subscriptions_set.updated_at > Time.now.utc - HEARTBEAT_INTERVAL
144
+
145
+ subscriptions_set.update(updated_at: Time.now.utc)
146
+ @refreshed_at = Time.now.utc
147
+ end
148
+
144
149
  # @return [void]
145
150
  def after_runner_stopped
146
- @commands_handler.stop
151
+ @runners.each(&:stop_async).each(&:wait_for_finish)
147
152
  @subscriptions_set&.delete
148
153
  @subscriptions_set = nil
149
- @runners.each(&:stop_async).each(&:wait_for_finish)
150
- unlock_all
154
+ @commands_handler.stop
151
155
  end
152
156
 
153
157
  # @return [void]
@@ -16,7 +16,7 @@ module PgEventstore
16
16
  attr_reader :subscription
17
17
 
18
18
  def_delegators :@events_processor, :start, :stop, :stop_async, :feed, :wait_for_finish, :restore, :state, :running?
19
- def_delegators :@subscription, :lock!, :unlock!, :id
19
+ def_delegators :@subscription, :lock!, :id
20
20
 
21
21
  # @param stats [PgEventstore::SubscriptionHandlerPerformance]
22
22
  # @param events_processor [PgEventstore::EventsProcessor]
@@ -18,7 +18,7 @@ module PgEventstore
18
18
  runners_query_options = runners.to_h { |runner| [runner.id, runner.next_chunk_query_opts] }
19
19
  grouped_events = subscription_queries.subscriptions_events(runners_query_options)
20
20
  runners.each do |runner|
21
- runner.feed(grouped_events[runner.id]) if grouped_events[runner.id]
21
+ runner.feed(grouped_events[runner.id] || [])
22
22
  end
23
23
  end
24
24
 
@@ -22,7 +22,7 @@ module PgEventstore
22
22
  end
23
23
 
24
24
  # @!attribute id
25
- # @return [String] UUIDv4. It is used to lock the Subscription by updating Subscription#locked_by attribute
25
+ # @return [Integer] It is used to lock the Subscription by updating Subscription#locked_by attribute
26
26
  attribute(:id)
27
27
  # @!attribute name
28
28
  # @return [String] name of the set
@@ -87,6 +87,27 @@ module PgEventstore
87
87
  self
88
88
  end
89
89
 
90
+ # @return [Integer]
91
+ def hash
92
+ id.hash
93
+ end
94
+
95
+ # @param another [Object]
96
+ # @return [Boolean]
97
+ def eql?(another)
98
+ return false unless another.is_a?(SubscriptionsSet)
99
+
100
+ hash == another.hash
101
+ end
102
+
103
+ # @param another [PgEventstore::SubscriptionsSet]
104
+ # @return [Boolean]
105
+ def ==(another)
106
+ return false unless another.is_a?(SubscriptionsSet)
107
+
108
+ id == another.id
109
+ end
110
+
90
111
  private
91
112
 
92
113
  # @return [PgEventstore::SubscriptionsSetQueries]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgEventstore
4
- VERSION = "0.10.2"
4
+ VERSION = "1.0.0.rc2"
5
5
  end