event_sourcery 0.19.0 → 0.23.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- 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: []