pg_eventstore 0.10.2 → 1.0.0.rc1

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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -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 +180 -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 +162 -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 +5 -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 +116 -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.rc1"
5
5
  end