event_sourcery 0.19.0 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 19b0a0d9e68626a19ba4b628c0b0e835d5f400e0
4
- data.tar.gz: 92b65ee0ca76d3044530c77e6a1c56fbb66cc03c
2
+ SHA256:
3
+ metadata.gz: 61fe1c1886a98c249f210f252778de57849d7da3a2c1aafe7f503be3193399d4
4
+ data.tar.gz: 8b3da0d26b96b02f4f7230c74891f6e3cab1cf46062559d00f4b9405f00a49bd
5
5
  SHA512:
6
- metadata.gz: 73c8044348497a991169477fd1075bf94bba46c02c686c8f8723bebf4b39f7f902fa38c1e79205520ffc0ed132e2a7cb7033d969de396d435eb1d94fa94645e2
7
- data.tar.gz: c0184c613e7d89f4dee8d573913b3ddeda0df8b64a0bc490370a3c7c46be2f92db0f8d62357443af3bc32a7d550f212167d845bced0a6dd7c0d3920bd0b5850b
6
+ metadata.gz: 53ad37d18c358351946bec8901ca83359b937097b2fdd55bdf63ad630acdb5e201f786791af0ab2d4d009ac3a836da6cad128f049858eeb52f72767ccbefde52
7
+ data.tar.gz: 350b132bcaef85bd0a8f0bd4069bd48aae6a2111f2269a3cfa8b0886a38799a7c6228e88e863383bf47d49609e2e057db734193d02665ba970042fff3051cc22
@@ -5,8 +5,106 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
- ## [0.19.0] - 2018-06-06
8
+ ## [Unreleased]
9
+
10
+ ## [0.23.1] - 2020-10-02
11
+ ### Fixed
12
+ - Upgrade development dependency Rake to version 13. This resolves
13
+ [CVE-2020-8130](https://github.com/advisories/GHSA-jppv-gw3r-w3q8).
14
+
15
+ - Patch `ESPRunner` to gracefully handle terminating subprocesses it did
16
+ not start ([#223]).
17
+
18
+ - Resolve warnings raised by Ruby 2.7 ([#225]).
19
+
20
+ [#223]: https://github.com/envato/event_sourcery/pull/223
21
+ [#225]: https://github.com/envato/event_sourcery/pull/225
22
+
23
+ ## [0.23.0] - 2019-07-11
24
+ ### Added
25
+ - Add Ruby 2.6 to the CI test matrix.
26
+ - `ESPRunner` supports an `after_subprocess_termination` hook. This optional
27
+ initializer argument will will be executed when each child process
28
+ terminates. This allows for monitoring and alerts to be configured.
29
+ For example, Rollbar:
30
+
31
+ ```ruby
32
+ EventSourcery::EventProcessing::ESPRunner.new(
33
+ event_processors: processors,
34
+ event_source: source,
35
+ after_subprocess_termination: proc do |processor:, runner:, exit_status:|
36
+ if exit_status != 0
37
+ Rollbar.error("Processor #{processor.processor_name} "\
38
+ "terminated with exit status #{exit_status}")
39
+ end
40
+ end
41
+ ).start!
42
+ ```
43
+
44
+ - `ESPRunner` exposes three new public methods `start_processor`, `shutdown`,
45
+ and `shutdown_requested?`. These provide options for handling subprocess
46
+ failure/termination. For example, shutting down the `ESPRunner`:
47
+
48
+ ```ruby
49
+ EventSourcery::EventProcessing::ESPRunner.new(
50
+ event_processors: processors,
51
+ event_source: source,
52
+ after_subprocess_termination: proc do |processor:, runner:, exit_status:|
53
+ runner.shutdown
54
+ end
55
+ ).start!
56
+ ```
57
+
58
+ Or restarting the event processor:
59
+
60
+ ```ruby
61
+ EventSourcery::EventProcessing::ESPRunner.new(
62
+ event_processors: processors,
63
+ event_source: source,
64
+ after_subprocess_termination: proc do |processor:, runner:, exit_status:|
65
+ runner.start_processor(processor) unless runner.shutdown_requested?
66
+ end
67
+ ).start!
68
+ ```
9
69
 
70
+ - `ESPRunner` checks for dead child processes every second. This means we
71
+ shouldn't see `[ruby] <defunct>` in the process list (ps) when a processor
72
+ fails.
73
+ - `ESPRunner` logs when child processes die.
74
+ - `ESPRunner` logs when sending signals to child processes.
75
+
76
+ ### Removed
77
+ - Remove Ruby 2.2 from the CI test matrix.
78
+
79
+ ## [0.22.0] - 2018-10-04
80
+ ### Added
81
+ - Log critical exceptions to the application provided block via the new
82
+ configuration option ([#209](https://github.com/envato/event_sourcery/pull/209)):
83
+
84
+ ```ruby
85
+ config.on_event_processor_critical_error = proc do |exception, processor_name|
86
+ # report the death of this processor to an error reporting service like Rollbar.
87
+ end
88
+ ```
89
+
90
+ ## [0.21.0] - 2018-07-02
91
+ ### Added
92
+ - Graceful shutdown interrupts poll-wait sleep for quicker quitting
93
+ ([#207](https://github.com/envato/event_sourcery/pull/207)).
94
+ - Added `bug_tracker_uri`, `changelog_uri` and `source_code_uri` to project
95
+ metadata ([#205](https://github.com/envato/event_sourcery/pull/205)).
96
+
97
+ ### Changed
98
+ - Fixed a bug where ESPRunner would raise an error under certain circumstances
99
+ ([#203](https://github.com/envato/event_sourcery/pull/203)).
100
+
101
+ ## [0.20.0] - 2018-06-21
102
+ ### Changed
103
+ - Changed signature of `ESPProcess#initialize` to include a default value for `after_fork`. This prevents the
104
+ `after_fork` change from 0.19.0 from being a breaking change to external creators of ESPProcess.
105
+ - Added more logging when a fatal exception occurs in ESPProcess
106
+
107
+ ## [0.19.0] - 2018-06-06
10
108
  ### Added
11
109
 
12
110
  - Allow passing an `after_fork` lambda to `ESPRunner` that is called after each
@@ -136,10 +234,16 @@ moving all Postgres related code into a separate gem.
136
234
  - EventSourcery no longer depends on Virtus.
137
235
  - `Command` and `CommandHandler` have been removed.
138
236
 
139
- [Unreleased]: https://github.com/envato/event_sourcery/compare/v0.19.0...HEAD
237
+ [Unreleased]: https://github.com/envato/event_sourcery/compare/v0.23.1...HEAD
238
+ [0.23.1]: https://github.com/envato/event_sourcery/compare/v0.23.0...v0.23.1
239
+ [0.23.0]: https://github.com/envato/event_sourcery/compare/v0.22.0...v0.23.0
240
+ [0.22.0]: https://github.com/envato/event_sourcery/compare/v0.21.0...v0.22.0
241
+ [0.21.0]: https://github.com/envato/event_sourcery/compare/v0.20.0...v0.21.0
242
+ [0.20.0]: https://github.com/envato/event_sourcery/compare/v0.19.0...v0.20.0
140
243
  [0.19.0]: https://github.com/envato/event_sourcery/compare/v0.18.0...v0.19.0
141
244
  [0.18.0]: https://github.com/envato/event_sourcery/compare/v0.17.0...v0.18.0
142
245
  [0.17.0]: https://github.com/envato/event_sourcery/compare/v0.16.0...v0.17.0
246
+ [0.16.1]: https://github.com/envato/event_sourcery/compare/v0.16.0...v0.16.1
143
247
  [0.16.0]: https://github.com/envato/event_sourcery/compare/v0.15.0...v0.16.0
144
248
  [0.15.0]: https://github.com/envato/event_sourcery/compare/v0.14.0...v0.15.0
145
249
  [0.14.0]: https://github.com/envato/event_sourcery/compare/v0.13.0...v0.14.0
data/README.md CHANGED
@@ -67,6 +67,7 @@ EventSourcery.configure do |config|
67
67
  # Add custom reporting of errors occurring during event processing.
68
68
  # One might set up an error reporting service like Rollbar here.
69
69
  config.on_event_processor_error = proc { |exception, processor_name| … }
70
+ config.on_event_processor_critical_error = proc { |exception, processor_name| … }
70
71
 
71
72
  # Enable EventSourcery logging.
72
73
  config.logger = Logger.new('logs/my_event_sourcery_app.log')
@@ -103,8 +104,9 @@ bundle exec rspec
103
104
  To release a new version:
104
105
 
105
106
  1. Update the version number in `lib/event_sourcery/version.rb`
106
- 2. Get this change onto master via the normal PR process
107
- 3. Run `bundle exec rake release`, this will create a git tag for the
107
+ 2. Add the new version with release notes to CHANGELOG.md
108
+ 3. Get these changes onto master via the normal PR process
109
+ 4. Run `bundle exec rake release`, this will create a git tag for the
108
110
  version, push tags up to GitHub, and package the code in a `.gem` file.
109
111
 
110
112
  ## Core Concepts
@@ -216,7 +218,7 @@ The event store is a persistent store of events.
216
218
 
217
219
  EventSourcery currently supports a Postgres-based event store via the [event_sourcery-postgres gem](https://github.com/envato/event_sourcery-postgres).
218
220
 
219
- For more information about the `EventStore` API refer to [the postgres event store](https://github.com/envato/event_sourcery-postgres/blob/master/lib/event_sourcery/postgres/event_store.rb) or the [in memory event store in this repo](lib/event_sourcery/event_store/memory.rb)
221
+ For more information about the `EventStore` API refer to [the postgres event store](https://github.com/envato/event_sourcery-postgres/blob/master/lib/event_sourcery/postgres/event_store.rb) or the [in memory event store in this repo](lib/event_sourcery/memory/event_store.rb)
220
222
 
221
223
  #### Storing Events
222
224
 
@@ -12,6 +12,11 @@ Gem::Specification.new do |spec|
12
12
  spec.summary = 'Event Sourcing Library'
13
13
  spec.description = ''
14
14
  spec.homepage = 'https://github.com/envato/event_sourcery'
15
+ spec.metadata = {
16
+ 'bug_tracker_uri' => 'https://github.com/envato/event_sourcery/issues',
17
+ 'changelog_uri' => 'https://github.com/envato/event_sourcery/blob/master/CHANGELOG.md',
18
+ 'source_code_uri' => 'https://github.com/envato/event_sourcery',
19
+ }
15
20
 
16
21
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(\.|Gemfile|Rakefile|bin/|script/|spec/)}) }
17
22
  spec.bindir = 'exe'
@@ -20,8 +25,8 @@ Gem::Specification.new do |spec|
20
25
 
21
26
  spec.required_ruby_version = '>= 2.2.0'
22
27
 
23
- spec.add_development_dependency 'bundler', '~> 1.10'
24
- spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'bundler'
29
+ spec.add_development_dependency 'rake', '~> 13'
25
30
  spec.add_development_dependency 'rspec'
26
31
  spec.add_development_dependency 'pry'
27
32
  spec.add_development_dependency 'benchmark-ips'
@@ -19,6 +19,14 @@ module EventSourcery
19
19
  # @return Proc
20
20
  attr_accessor :on_event_processor_error
21
21
 
22
+ # A Proc to be executed on an event processor critical error.
23
+ # App defined behaviour can be provided. This will be called
24
+ # if an exception causes an a event processor to die.
25
+ # i.e. report to an error reporting service like Rollbar.
26
+ #
27
+ # @return Proc
28
+ attr_accessor :on_event_processor_critical_error
29
+
22
30
  # @return EventStore::EventTypeSerializers::Underscored
23
31
  attr_accessor :event_type_serializer
24
32
 
@@ -40,6 +48,9 @@ module EventSourcery
40
48
  @on_event_processor_error = proc { |exception, processor_name|
41
49
  # app specific custom logic ie. report to an error reporting service like Rollbar.
42
50
  }
51
+ @on_event_processor_critical_error = proc { |exception, processor_name|
52
+ # app specific custom logic ie. report to an error reporting service like Rollbar.
53
+ }
43
54
  @event_builder = nil
44
55
  @event_type_serializer = EventStore::EventTypeSerializers::Underscored.new
45
56
  @error_handler_class = EventProcessing::ErrorHandlers::ConstantRetry
@@ -47,10 +58,10 @@ module EventSourcery
47
58
  end
48
59
 
49
60
  # Logger instance used by EventSourcery.
50
- # By default EventSourcery will log to STDOUT with a log level of Logger::DEBUG
61
+ # By default EventSourcery will log to STDOUT with a log level of Logger::INFO
51
62
  def logger
52
63
  @logger ||= ::Logger.new(STDOUT).tap do |logger|
53
- logger.level = Logger::DEBUG
64
+ logger.level = Logger::INFO
54
65
  end
55
66
  end
56
67
 
@@ -6,7 +6,7 @@ module EventSourcery
6
6
  def initialize(event_processor:,
7
7
  event_source:,
8
8
  subscription_master: EventStore::SignalHandlingSubscriptionMaster.new,
9
- after_fork:)
9
+ after_fork: nil)
10
10
  @event_processor = event_processor
11
11
  @event_source = event_source
12
12
  @subscription_master = subscription_master
@@ -21,10 +21,12 @@ module EventSourcery
21
21
  error_handler.with_error_handling do
22
22
  EventSourcery.logger.info("Starting #{processor_name}")
23
23
  subscribe_to_event_stream
24
- EventSourcery.logger.info("Stopping #{@event_processor.processor_name}")
24
+ EventSourcery.logger.info("Stopping #{processor_name}")
25
25
  end
26
26
  rescue Exception => e
27
- EventSourcery.logger.error(e)
27
+ EventSourcery.logger.fatal("An unhandled exception occurred in #{processor_name}")
28
+ EventSourcery.logger.fatal(e)
29
+ EventSourcery.config.on_event_processor_critical_error.call(e, processor_name)
28
30
  raise e
29
31
  end
30
32
 
@@ -7,14 +7,18 @@ module EventSourcery
7
7
  event_source:,
8
8
  max_seconds_for_processes_to_terminate: 30,
9
9
  shutdown_requested: false,
10
- after_fork: nil)
10
+ after_fork: nil,
11
+ after_subprocess_termination: nil,
12
+ logger: EventSourcery.logger)
11
13
  @event_processors = event_processors
12
14
  @event_source = event_source
13
- @pids = []
15
+ @pids = {}
14
16
  @max_seconds_for_processes_to_terminate = max_seconds_for_processes_to_terminate
15
17
  @shutdown_requested = shutdown_requested
16
18
  @exit_status = true
17
19
  @after_fork = after_fork
20
+ @after_subprocess_termination = after_subprocess_termination
21
+ @logger = logger
18
22
  end
19
23
 
20
24
  # Start each Event Stream Processor in a new child process.
@@ -22,8 +26,9 @@ module EventSourcery
22
26
  with_logging do
23
27
  start_processes
24
28
  listen_for_shutdown_signals
25
- wait_till_shutdown_requested
26
- record_terminated_processes
29
+ while_waiting_for_shutdown do
30
+ record_terminated_processes
31
+ end
27
32
  terminate_remaining_processes
28
33
  until all_processes_terminated? || waited_long_enough?
29
34
  record_terminated_processes
@@ -34,25 +39,36 @@ module EventSourcery
34
39
  exit_indicating_status_of_processes
35
40
  end
36
41
 
42
+ def start_processor(event_processor)
43
+ process = ESPProcess.new(
44
+ event_processor: event_processor,
45
+ event_source: @event_source,
46
+ after_fork: @after_fork,
47
+ )
48
+ pid = Process.fork { process.start }
49
+ @pids[pid] = event_processor
50
+ end
51
+
52
+ def shutdown
53
+ @shutdown_requested = true
54
+ end
55
+
56
+ def shutdown_requested?
57
+ @shutdown_requested
58
+ end
59
+
37
60
  private
38
61
 
62
+ attr_reader :logger
63
+
39
64
  def with_logging
40
- EventSourcery.logger.info { 'Forking ESP processes' }
65
+ logger.info('ESPRunner: Forking processes')
41
66
  yield
42
- EventSourcery.logger.info { 'ESP processes shutdown' }
67
+ logger.info('ESPRunner: Processes shutdown')
43
68
  end
44
69
 
45
70
  def start_processes
46
- @event_processors.each(&method(:start_process))
47
- end
48
-
49
- def start_process(event_processor)
50
- process = ESPProcess.new(
51
- event_processor: event_processor,
52
- event_source: @event_source,
53
- after_fork: @after_fork,
54
- )
55
- @pids << Process.fork { process.start }
71
+ @event_processors.each(&method(:start_processor))
56
72
  end
57
73
 
58
74
  def listen_for_shutdown_signals
@@ -61,12 +77,12 @@ module EventSourcery
61
77
  end
62
78
  end
63
79
 
64
- def shutdown
65
- @shutdown_requested = true
66
- end
67
-
68
- def wait_till_shutdown_requested
69
- sleep(1) until @shutdown_requested
80
+ def while_waiting_for_shutdown
81
+ loop do
82
+ yield
83
+ break if shutdown_requested?
84
+ sleep(1)
85
+ end
70
86
  end
71
87
 
72
88
  def terminate_remaining_processes
@@ -77,19 +93,24 @@ module EventSourcery
77
93
  send_signal_to_remaining_processes(:KILL)
78
94
  end
79
95
 
80
-
81
96
  def send_signal_to_remaining_processes(signal)
82
- Process.kill(signal, *@pids) unless all_processes_terminated?
97
+ return if all_processes_terminated?
98
+
99
+ logger.info("ESPRunner: Sending #{signal} to [#{@pids.values.map(&:processor_name).join(', ')}]")
100
+ Process.kill(signal, *@pids.keys)
83
101
  rescue Errno::ESRCH
84
102
  record_terminated_processes
85
103
  retry
86
104
  end
87
105
 
88
106
  def record_terminated_processes
89
- until all_processes_terminated? ||
90
- ((pid, status) = Process.wait2(-1, Process::WNOHANG)).nil?
91
- @pids.delete(pid)
92
- @exit_status &&= status.success?
107
+ until all_processes_terminated? || (pid, status = Process.wait2(-1, Process::WNOHANG)).nil?
108
+ event_processor = @pids.delete(pid)
109
+ logger.info("ESPRunner: Process #{event_processor&.processor_name || pid} " \
110
+ "terminated with exit status: #{status.exitstatus.inspect}")
111
+ next unless event_processor
112
+ @exit_status &&= !!status.success?
113
+ @after_subprocess_termination&.call(processor: event_processor, runner: self, exit_status: status.exitstatus)
93
114
  end
94
115
  end
95
116
 
@@ -5,10 +5,10 @@ module EventSourcery
5
5
  @event_type_serializer = event_type_serializer
6
6
  end
7
7
 
8
- def build(event_data)
8
+ def build(**event_data)
9
9
  type = event_data.fetch(:type)
10
10
  klass = event_type_serializer.deserialize(type)
11
- upcast(klass.new(event_data))
11
+ upcast(klass.new(**event_data))
12
12
  end
13
13
 
14
14
  private
@@ -21,7 +21,30 @@ module EventSourcery
21
21
 
22
22
  def setup_graceful_shutdown
23
23
  %i(TERM INT).each do |signal|
24
- Signal.trap(signal) { @shutdown_requested = true }
24
+ Signal.trap(signal) do
25
+ @shutdown_requested = true
26
+ wakeup_main_thread
27
+ end
28
+ end
29
+ end
30
+
31
+ # If the main thread happens to be sleeping when we receive the
32
+ # interrupt, wake it up.
33
+ #
34
+ # Note: the main thread processes the signal trap, hence calling
35
+ # Thread.main.wakeup in the signal trap is a no-op as it's undoubtedly
36
+ # awake. Instead, we need to fork a new thread, which waits for the main
37
+ # thread to go back to sleep and then wakes it up.
38
+ def wakeup_main_thread
39
+ Thread.fork do
40
+ main_thread = Thread.main
41
+ 10.times do
42
+ if main_thread.status == 'sleep'
43
+ main_thread.wakeup
44
+ break
45
+ end
46
+ sleep 0.01
47
+ end
25
48
  end
26
49
  end
27
50
  end
@@ -212,9 +212,9 @@ RSpec.shared_examples 'an event store' do
212
212
  end
213
213
  end
214
214
 
215
- def events_by_range(*args)
215
+ def events_by_range(from_event_id, to_event_id, **args)
216
216
  [].tap do |events|
217
- event_store.each_by_range(*args) do |event|
217
+ event_store.each_by_range(from_event_id, to_event_id, **args) do |event|
218
218
  events << event
219
219
  end
220
220
  end
@@ -1,4 +1,4 @@
1
1
  module EventSourcery
2
2
  # Defines the version
3
- VERSION = '0.19.0'.freeze
3
+ VERSION = '0.23.1'.freeze
4
4
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: event_sourcery
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.0
4
+ version: 0.23.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Envato
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-06-06 00:00:00.000000000 Z
11
+ date: 2020-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.10'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.10'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '13'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '13'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -125,8 +125,11 @@ files:
125
125
  - lib/event_sourcery/version.rb
126
126
  homepage: https://github.com/envato/event_sourcery
127
127
  licenses: []
128
- metadata: {}
129
- post_install_message:
128
+ metadata:
129
+ bug_tracker_uri: https://github.com/envato/event_sourcery/issues
130
+ changelog_uri: https://github.com/envato/event_sourcery/blob/master/CHANGELOG.md
131
+ source_code_uri: https://github.com/envato/event_sourcery
132
+ post_install_message:
130
133
  rdoc_options: []
131
134
  require_paths:
132
135
  - lib
@@ -141,9 +144,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
144
  - !ruby/object:Gem::Version
142
145
  version: '0'
143
146
  requirements: []
144
- rubyforge_project:
145
- rubygems_version: 2.5.2
146
- signing_key:
147
+ rubygems_version: 3.1.4
148
+ signing_key:
147
149
  specification_version: 4
148
150
  summary: Event Sourcing Library
149
151
  test_files: []