fibered_mysql2 0.1.0.pre.3 → 0.1.0.pre.4
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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/fibered_mysql2.rb +5 -1
- data/lib/fibered_mysql2/fibered_database_connection_pool.rb +221 -0
- data/lib/fibered_mysql2/fibered_mutex_with_waiter_priority.rb +33 -0
- data/lib/fibered_mysql2/fibered_mysql2_connection_factory.rb +81 -0
- data/lib/fibered_mysql2/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52a7c1f9db37bca0d6b9861985ad594863e1cde7847952dc83a2ed94bed72d2e
|
4
|
+
data.tar.gz: 1ac220f2391d1b7c74df9985ddfaca59855570ee01e36f8351841f8651306a98
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4df2ada93ac02bf3b34b6a223c83c26690a620a88eec6c38ffc4f1a1c8a052867fc126b4f745ad3e1aa9278e86a7808c08dc7b493dbd99cb93812895b1901584
|
7
|
+
data.tar.gz: 78fafb964db2b16da1604032ea1421b2761f81ef60c52851be4df30f96e78a9f688b9a656fec1d4a0ad4b1564aca20f57bb9722a69aba64be4891835d11cfd6e
|
data/Gemfile.lock
CHANGED
data/lib/fibered_mysql2.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'fibered_mysql2/version'
|
4
|
+
require_relative '../lib/active_record/connection_adapters/fibered_mysql2_adapter'
|
5
|
+
require 'fibered_mysql2/fibered_database_connection_pool'
|
6
|
+
require 'fibered_mysql2/fibered_mutex_with_waiter_priority'
|
7
|
+
require 'fibered_mysql2/fibered_mysql2_connection_factory'
|
4
8
|
|
5
9
|
module FiberedMysql2
|
6
10
|
class Error < StandardError; end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This class behaves the same as ActiveRecord's ConnectionPool, but synchronizes with fibers rather than threads.
|
4
|
+
|
5
|
+
# Note - trace statements have been commented out. This is useful trace but we do not want it on by default.
|
6
|
+
# When we have configurable logging we can put this back and have it off by default.
|
7
|
+
|
8
|
+
require 'em-synchrony'
|
9
|
+
require 'em-synchrony/thread'
|
10
|
+
require 'fibered_mysql2/fibered_mutex_with_waiter_priority'
|
11
|
+
|
12
|
+
EventMachine::Synchrony::Thread::Mutex.prepend(FiberedMysql2::FiberedMutexWithWaiterPriority)
|
13
|
+
|
14
|
+
module FiberedMysql2
|
15
|
+
class FiberedConditionVariable < MonitorMixin::ConditionVariable
|
16
|
+
def initialize(monitor)
|
17
|
+
@monitor = monitor
|
18
|
+
@cond = EM::Synchrony::Thread::ConditionVariable.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# From Ruby's MonitorMixin, with all occurrences of Thread changed to Fiber
|
23
|
+
module FiberedMonitorMixin
|
24
|
+
def self.extend_object(obj)
|
25
|
+
super
|
26
|
+
obj.__send__(:mon_initialize)
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Attempts to enter exclusive section. Returns +false+ if lock fails.
|
31
|
+
#
|
32
|
+
def mon_try_enter
|
33
|
+
if @mon_owner != Fiber.current
|
34
|
+
@mon_mutex.try_lock or return false
|
35
|
+
@mon_owner = Fiber.current
|
36
|
+
@mon_count = 0
|
37
|
+
end
|
38
|
+
@mon_count += 1
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Enters exclusive section.
|
44
|
+
#
|
45
|
+
def mon_enter
|
46
|
+
if @mon_owner != Fiber.current
|
47
|
+
@mon_mutex.lock
|
48
|
+
@mon_owner = Fiber.current
|
49
|
+
@mon_count = 0
|
50
|
+
end
|
51
|
+
@mon_count += 1
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Leaves exclusive section.
|
56
|
+
#
|
57
|
+
def mon_exit
|
58
|
+
mon_check_owner
|
59
|
+
@mon_count -= 1
|
60
|
+
if @mon_count == 0
|
61
|
+
@mon_owner = nil
|
62
|
+
@mon_mutex.unlock
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Enters exclusive section and executes the block. Leaves the exclusive
|
68
|
+
# section automatically when the block exits. See example under
|
69
|
+
# +MonitorMixin+.
|
70
|
+
#
|
71
|
+
def mon_synchronize
|
72
|
+
mon_enter
|
73
|
+
begin
|
74
|
+
yield
|
75
|
+
ensure
|
76
|
+
begin
|
77
|
+
mon_exit
|
78
|
+
rescue => ex
|
79
|
+
ActiveRecord::Base.logger.error("Exception occurred while executing mon_exit: #{ex}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
alias synchronize mon_synchronize
|
84
|
+
|
85
|
+
#
|
86
|
+
# Creates a new FiberedConditionVariable associated with the
|
87
|
+
# receiver.
|
88
|
+
#
|
89
|
+
def new_cond
|
90
|
+
FiberedConditionVariable.new(self)
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# Initializes the FiberedMonitorMixin after being included in a class
|
96
|
+
def mon_initialize
|
97
|
+
@mon_owner = nil
|
98
|
+
@mon_count = 0
|
99
|
+
@mon_mutex = EM::Synchrony::Thread::Mutex.new
|
100
|
+
end
|
101
|
+
|
102
|
+
def mon_check_owner
|
103
|
+
@mon_owner == Fiber.current or raise FiberError, "current fiber not owner"
|
104
|
+
end
|
105
|
+
|
106
|
+
def mon_enter_for_cond(count)
|
107
|
+
@mon_owner = Fiber.current
|
108
|
+
@mon_count = count
|
109
|
+
end
|
110
|
+
|
111
|
+
# returns the old mon_count
|
112
|
+
def mon_exit_for_cond
|
113
|
+
count = @mon_count
|
114
|
+
@mon_owner = nil
|
115
|
+
@mon_count = 0
|
116
|
+
count
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
module FiberedDatabaseConnectionPool
|
121
|
+
include FiberedMonitorMixin
|
122
|
+
|
123
|
+
module Adapter_4_2
|
124
|
+
def cached_connections
|
125
|
+
@reserved_connections
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
module Adapter_5_2
|
130
|
+
def cached_connections
|
131
|
+
@thread_cached_conns
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
case Rails::VERSION::MAJOR
|
136
|
+
when 4
|
137
|
+
include Adapter_4_2
|
138
|
+
when 5, 6
|
139
|
+
include Adapter_5_2
|
140
|
+
end
|
141
|
+
|
142
|
+
def initialize(connection_spec)
|
143
|
+
connection_spec.config[:reaping_frequency] and raise "reaping_frequency is not supported (the ActiveRecord Reaper is thread-based)"
|
144
|
+
|
145
|
+
super(connection_spec)
|
146
|
+
|
147
|
+
@reaper = nil # no need to keep a reference to this since it does nothing in this sub-class
|
148
|
+
|
149
|
+
# note that @reserved_connections is a ThreadSafe::Cache which is overkill in a fibered world, but harmless
|
150
|
+
end
|
151
|
+
|
152
|
+
def connection
|
153
|
+
# this is correctly done double-checked locking
|
154
|
+
# (ThreadSafe::Cache's lookups have volatile semantics)
|
155
|
+
if (result = cached_connections[current_connection_id])
|
156
|
+
result
|
157
|
+
else
|
158
|
+
synchronize do
|
159
|
+
if (result = cached_connections[current_connection_id])
|
160
|
+
result
|
161
|
+
else
|
162
|
+
cached_connections[current_connection_id] = checkout
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
if Rails::VERSION::MAJOR > 4
|
169
|
+
def release_connection(owner_thread = Fiber.current)
|
170
|
+
if (conn = @thread_cached_conns.delete(connection_cache_key(owner_thread)))
|
171
|
+
checkin(conn)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def current_connection_id
|
177
|
+
case Rails::VERSION::MAJOR
|
178
|
+
when 4
|
179
|
+
ActiveRecord::Base.connection_id ||= Fiber.current.object_id
|
180
|
+
else
|
181
|
+
connection_cache_key(current_thread)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def checkout(checkout_timeout = @checkout_timeout)
|
186
|
+
begin
|
187
|
+
reap_connections
|
188
|
+
rescue => ex
|
189
|
+
ActiveRecord::Base.logger.error("Exception occurred while executing reap_connections: #{ex}")
|
190
|
+
end
|
191
|
+
if Rails::VERSION::MAJOR > 4
|
192
|
+
super
|
193
|
+
else
|
194
|
+
super()
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def reap_connections
|
199
|
+
cached_connections.values.each do |connection|
|
200
|
+
unless connection.owner.alive?
|
201
|
+
checkin(connection)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
#--
|
209
|
+
# This hook-in method allows for easier monkey-patching fixes needed by
|
210
|
+
# JRuby users that use Fibers.
|
211
|
+
def connection_cache_key(fiber)
|
212
|
+
fiber
|
213
|
+
end
|
214
|
+
|
215
|
+
def current_thread
|
216
|
+
Fiber.current
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(FiberedMysql2::FiberedDatabaseConnectionPool)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FiberedMysql2
|
4
|
+
module FiberedMutexWithWaiterPriority
|
5
|
+
# Note: @waiters is a bit confusing because the first waiter is actually the current fiber that has it locked;
|
6
|
+
# the _rest_ of @waiters are the actual waiters
|
7
|
+
def sleep(timeout = nil)
|
8
|
+
unlock
|
9
|
+
beg = Time.now
|
10
|
+
current = Fiber.current
|
11
|
+
@slept[current] = true
|
12
|
+
if timeout
|
13
|
+
timer = EM.add_timer(timeout) do
|
14
|
+
_wakeup(current)
|
15
|
+
end
|
16
|
+
Fiber.yield
|
17
|
+
EM.cancel_timer(timer) # if we resumed not via timer
|
18
|
+
else
|
19
|
+
Fiber.yield
|
20
|
+
end
|
21
|
+
@slept.delete(current)
|
22
|
+
yield if block_given?
|
23
|
+
|
24
|
+
# Invoca patch: inline lock that puts us at the front of the mutex @waiters queue instead of the back
|
25
|
+
# ==========================
|
26
|
+
@waiters.unshift(current)
|
27
|
+
Fiber.yield if @waiters.size > 1
|
28
|
+
# ==========================
|
29
|
+
|
30
|
+
Time.now - beg
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../active_record/connection_adapters/fibered_mysql2_adapter'
|
4
|
+
|
5
|
+
module EM::Synchrony
|
6
|
+
module ActiveRecord
|
7
|
+
_ = Adapter_4_2
|
8
|
+
module Adapter_4_2
|
9
|
+
def configure_connection
|
10
|
+
super # undo EM::Synchrony's override here
|
11
|
+
end
|
12
|
+
|
13
|
+
def transaction(*args)
|
14
|
+
super # and here
|
15
|
+
end
|
16
|
+
|
17
|
+
_ = TransactionManager
|
18
|
+
class TransactionManager < _
|
19
|
+
if Rails::VERSION::MAJOR > 5
|
20
|
+
# Overriding the em-synchrony override to bring it up to rails 6 requirements.
|
21
|
+
# Changes from the original Rails 6 source are:
|
22
|
+
# 1. the usage of _current_stack created by em-synchrony instead of the Rails provided @stack instance variable
|
23
|
+
# 2. the usage of Fiber.current.object_id as a part of the savepoint transaction name
|
24
|
+
#
|
25
|
+
# Original EM Synchrony Source:
|
26
|
+
# https://github.com/igrigorik/em-synchrony/blob/master/lib/em-synchrony/activerecord_4_2.rb#L35-L44
|
27
|
+
#
|
28
|
+
# Original Rails Source:
|
29
|
+
# https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L205-L224
|
30
|
+
def begin_transaction(options = {})
|
31
|
+
@connection.lock.synchronize do
|
32
|
+
run_commit_callbacks = !current_transaction.joinable?
|
33
|
+
transaction =
|
34
|
+
if _current_stack.empty?
|
35
|
+
::ActiveRecord::ConnectionAdapters::RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
|
36
|
+
else
|
37
|
+
::ActiveRecord::ConnectionAdapters::SavepointTransaction.new(@connection, "active_record_#{Fiber.current.object_id}_#{open_transactions}", _current_stack.last, options,
|
38
|
+
run_commit_callbacks: run_commit_callbacks)
|
39
|
+
end
|
40
|
+
|
41
|
+
if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && options[:_lazy] != false
|
42
|
+
@has_unmaterialized_transactions = true
|
43
|
+
else
|
44
|
+
transaction.materialize!
|
45
|
+
end
|
46
|
+
_current_stack.push(transaction)
|
47
|
+
transaction
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module FiberedMysql2
|
57
|
+
module FiberedMysql2ConnectionFactory
|
58
|
+
def fibered_mysql2_connection(raw_config)
|
59
|
+
config = raw_config.symbolize_keys
|
60
|
+
|
61
|
+
config[:username] = 'root' if config[:username].nil?
|
62
|
+
config[:flags] = Mysql2::Client::FOUND_ROWS if Mysql2::Client.const_defined?(:FOUND_ROWS)
|
63
|
+
|
64
|
+
client =
|
65
|
+
begin
|
66
|
+
Mysql2::EM::Client.new(config)
|
67
|
+
rescue Mysql2::Error => error
|
68
|
+
if error.message.include?("Unknown database")
|
69
|
+
raise ActiveRecord::NoDatabaseError.new(error.message, error)
|
70
|
+
else
|
71
|
+
raise
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
|
76
|
+
FiberedMysql2Adapter.new(client, logger, options, config)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
ActiveRecord::Base.class.prepend(FiberedMysql2::FiberedMysql2ConnectionFactory)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fibered_mysql2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.pre.
|
4
|
+
version: 0.1.0.pre.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Octothorp
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-10-
|
11
|
+
date: 2020-10-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: em-synchrony
|
@@ -68,6 +68,9 @@ files:
|
|
68
68
|
- gemfiles/rails_6.gemfile
|
69
69
|
- lib/active_record/connection_adapters/fibered_mysql2_adapter.rb
|
70
70
|
- lib/fibered_mysql2.rb
|
71
|
+
- lib/fibered_mysql2/fibered_database_connection_pool.rb
|
72
|
+
- lib/fibered_mysql2/fibered_mutex_with_waiter_priority.rb
|
73
|
+
- lib/fibered_mysql2/fibered_mysql2_connection_factory.rb
|
71
74
|
- lib/fibered_mysql2/version.rb
|
72
75
|
homepage: https://github.com/Invoca/fibered_mysql2
|
73
76
|
licenses: []
|