Skip to content

Commit 82e868c

Browse files
committed
[outbox] Rename Repository into Repositories::Mysql57
1 parent 66e7c9d commit 82e868c

File tree

13 files changed

+419
-408
lines changed

13 files changed

+419
-408
lines changed

contrib/ruby_event_store-outbox/.mutant.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ matcher:
3434
- RubyEventStore::Outbox::Configuration*
3535
- RubyEventStore::Outbox::Consumer#get_remaining_count
3636
- RubyEventStore::Outbox::CleanupStrategies::None*
37-
- RubyEventStore::Outbox::Repository*
37+
- RubyEventStore::Outbox::Repositories*
3838
- RubyEventStore::Outbox::Runner#initialize
3939
- RubyEventStore::Outbox::Runner#run
4040
- RubyEventStore::Outbox::Runner#prepare_traps

contrib/ruby_event_store-outbox/lib/ruby_event_store/outbox.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ module Outbox
88
end
99

1010
require_relative "outbox/fetch_specification"
11-
require_relative "outbox/repository"
11+
require_relative "outbox/repositories/mysql57"
1212
require_relative "outbox/sidekiq_scheduler"
1313
require_relative "outbox/legacy_sidekiq_scheduler"
1414
require_relative "outbox/version"
1515
require_relative "outbox/tempo"
1616
require_relative "outbox/batch_result"
1717
require_relative "outbox/cleanup_strategies"
18+
require_relative "outbox/repositories"

contrib/ruby_event_store-outbox/lib/ruby_event_store/outbox/consumer.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
require "logger"
22
require "redis"
33
require "active_record"
4-
require_relative "repository"
4+
require_relative "repositories/mysql57"
55
require_relative "sidekiq5_format"
66
require_relative "sidekiq_processor"
77
require_relative "fetch_specification"
@@ -24,7 +24,7 @@ def initialize(consumer_uuid, configuration, clock: Time, logger:, metrics:)
2424
raise "Unknown format" if configuration.message_format != SIDEKIQ5_FORMAT
2525
@processor = SidekiqProcessor.new(Redis.new(url: configuration.redis_url))
2626

27-
@repository = Repository.build_for_consumer(configuration.database_url)
27+
@repository = Repositories::Mysql57.build_for_consumer(configuration.database_url)
2828
@cleanup_strategy = CleanupStrategies.build(configuration, repository)
2929
end
3030

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module RubyEventStore
2+
module Outbox
3+
module Repositories
4+
end
5+
end
6+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# frozen_string_literal: true
2+
3+
require "active_record"
4+
require "active_support/core_ext/numeric/time.rb"
5+
6+
module RubyEventStore
7+
module Outbox
8+
module Repositories
9+
class Mysql57
10+
RECENTLY_LOCKED_DURATION = 10.minutes
11+
12+
class Record < ::ActiveRecord::Base
13+
self.primary_key = :id
14+
self.table_name = "event_store_outbox"
15+
16+
def self.remaining_for(fetch_specification)
17+
where(format: fetch_specification.message_format, split_key: fetch_specification.split_key, enqueued_at: nil)
18+
end
19+
20+
def self.for_fetch_specification(fetch_specification)
21+
where(format: fetch_specification.message_format, split_key: fetch_specification.split_key)
22+
end
23+
24+
def hash_payload
25+
JSON.parse(payload).deep_symbolize_keys
26+
end
27+
28+
def enqueued?
29+
!enqueued_at.nil?
30+
end
31+
end
32+
33+
class Lock < ::ActiveRecord::Base
34+
self.table_name = "event_store_outbox_locks"
35+
36+
def self.obtain(fetch_specification, process_uuid, clock:)
37+
transaction do
38+
l = get_lock_record(fetch_specification)
39+
40+
if l.recently_locked?(clock: clock)
41+
:taken
42+
else
43+
l.update!(locked_by: process_uuid, locked_at: clock.now)
44+
l
45+
end
46+
end
47+
rescue ::ActiveRecord::Deadlocked
48+
:deadlocked
49+
rescue ::ActiveRecord::LockWaitTimeout
50+
:lock_timeout
51+
end
52+
53+
def refresh(clock:)
54+
transaction do
55+
current_process_uuid = locked_by
56+
lock!
57+
if locked_by == current_process_uuid
58+
update!(locked_at: clock.now)
59+
:ok
60+
else
61+
:stolen
62+
end
63+
end
64+
rescue ::ActiveRecord::Deadlocked
65+
:deadlocked
66+
rescue ::ActiveRecord::LockWaitTimeout
67+
:lock_timeout
68+
end
69+
70+
def self.release(fetch_specification, process_uuid)
71+
transaction do
72+
l = get_lock_record(fetch_specification)
73+
if !l.locked_by?(process_uuid)
74+
:not_taken_by_this_process
75+
else
76+
l.update!(locked_by: nil, locked_at: nil)
77+
:ok
78+
end
79+
end
80+
rescue ::ActiveRecord::Deadlocked
81+
:deadlocked
82+
rescue ::ActiveRecord::LockWaitTimeout
83+
:lock_timeout
84+
end
85+
86+
def locked_by?(process_uuid)
87+
locked_by.eql?(process_uuid)
88+
end
89+
90+
def recently_locked?(clock:)
91+
locked_by && locked_at > RECENTLY_LOCKED_DURATION.ago(clock.now)
92+
end
93+
94+
def fetch_specification
95+
FetchSpecification.new(format, split_key)
96+
end
97+
98+
private
99+
100+
def self.lock_for_split_key(fetch_specification)
101+
lock.find_by(format: fetch_specification.message_format, split_key: fetch_specification.split_key)
102+
end
103+
104+
def self.get_lock_record(fetch_specification)
105+
l = lock_for_split_key(fetch_specification)
106+
if l.nil?
107+
begin
108+
l = create!(format: fetch_specification.message_format, split_key: fetch_specification.split_key)
109+
rescue ::ActiveRecord::RecordNotUnique
110+
l = lock_for_split_key(fetch_specification)
111+
end
112+
end
113+
l
114+
end
115+
end
116+
117+
def self.build_for_consumer(database_url)
118+
::ActiveRecord::Base.establish_connection(database_url) unless ::ActiveRecord::Base.connected?
119+
if ::ActiveRecord::Base.connection.adapter_name == "Mysql2"
120+
::ActiveRecord::Base.connection.execute("SET SESSION innodb_lock_wait_timeout = 1;")
121+
end
122+
new
123+
end
124+
125+
def insert_record(format, split_key, payload)
126+
Record.create!(format: format, split_key: split_key, payload: payload)
127+
end
128+
129+
def retrieve_batch(fetch_specification, batch_size)
130+
Record.remaining_for(fetch_specification).order("id ASC").limit(batch_size).to_a
131+
end
132+
133+
def get_remaining_count(fetch_specification)
134+
Record.remaining_for(fetch_specification).count
135+
end
136+
137+
def obtain_lock_for_process(fetch_specification, process_uuid, clock:)
138+
Lock.obtain(fetch_specification, process_uuid, clock: clock)
139+
end
140+
141+
def release_lock_for_process(fetch_specification, process_uuid)
142+
Lock.release(fetch_specification, process_uuid)
143+
end
144+
145+
def mark_as_enqueued(record, now)
146+
record.update_column(:enqueued_at, now)
147+
end
148+
149+
def delete_enqueued_older_than(fetch_specification, duration, limit)
150+
scope = Record.for_fetch_specification(fetch_specification).where("enqueued_at < ?", duration.ago)
151+
scope = scope.limit(limit).order(:id) unless limit == :all
152+
scope.delete_all
153+
:ok
154+
rescue ::ActiveRecord::Deadlocked
155+
:deadlocked
156+
rescue ::ActiveRecord::LockWaitTimeout
157+
:lock_timeout
158+
end
159+
end
160+
end
161+
end
162+
end

contrib/ruby_event_store-outbox/lib/ruby_event_store/outbox/repository.rb

-160
This file was deleted.

contrib/ruby_event_store-outbox/lib/ruby_event_store/outbox/sidekiq_producer.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
require "sidekiq"
44
require_relative "sidekiq5_format"
5-
require_relative "repository"
5+
require_relative "repositories/mysql57"
66

77
module RubyEventStore
88
module Outbox
99
class SidekiqProducer
10-
def initialize(repository = Repository.new)
10+
def initialize(repository = Repositories::Mysql57.new)
1111
@repository = repository
1212
end
1313

contrib/ruby_event_store-outbox/lib/ruby_event_store/outbox/sidekiq_scheduler.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
module RubyEventStore
66
module Outbox
77
class SidekiqScheduler
8-
def initialize(repository: Repository.new, serializer: RubyEventStore::Serializers::YAML)
8+
def initialize(repository: Repositories::Mysql57.new, serializer: RubyEventStore::Serializers::YAML)
99
@serializer = serializer
1010
@sidekiq_producer = SidekiqProducer.new(repository)
1111
end

0 commit comments

Comments
 (0)