fibered_mysql2 0.1.0.pre.3 → 0.1.0.pre.4
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|