pg_pipeline 0.2.5 → 0.3.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 +4 -4
- data/CHANGELOG.md +101 -0
- data/README.md +61 -1
- data/lib/pg_pipeline/bounded_queue.rb +10 -12
- data/lib/pg_pipeline/client.rb +12 -17
- data/lib/pg_pipeline/connection_driver.rb +156 -82
- data/lib/pg_pipeline/errors.rb +1 -0
- data/lib/pg_pipeline/pool.rb +82 -48
- data/lib/pg_pipeline/request.rb +5 -7
- data/lib/pg_pipeline/runtime/notification.rb +32 -0
- data/lib/pg_pipeline/runtime/queue.rb +31 -0
- data/lib/pg_pipeline/runtime/semaphore.rb +138 -0
- data/lib/pg_pipeline/runtime/task.rb +124 -0
- data/lib/pg_pipeline/runtime.rb +98 -0
- data/lib/pg_pipeline/session_guard.rb +3 -8
- data/lib/pg_pipeline/transaction.rb +23 -2
- data/lib/pg_pipeline/version.rb +1 -1
- data/lib/pg_pipeline.rb +1 -0
- metadata +26 -19
data/lib/pg_pipeline/request.rb
CHANGED
|
@@ -255,13 +255,11 @@ module PgPipeline
|
|
|
255
255
|
req.waiter = waiter
|
|
256
256
|
req.waiter_scheduler = scheduler
|
|
257
257
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
req.waiter_scheduler = nil
|
|
264
|
-
end
|
|
258
|
+
scheduler.block(req, nil) until req.settled
|
|
259
|
+
ensure
|
|
260
|
+
if waiter && req.waiter.equal?(waiter)
|
|
261
|
+
req.waiter = nil
|
|
262
|
+
req.waiter_scheduler = nil
|
|
265
263
|
end
|
|
266
264
|
end
|
|
267
265
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PgPipeline
|
|
4
|
+
module Runtime
|
|
5
|
+
class Notification
|
|
6
|
+
def initialize
|
|
7
|
+
@waiters = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def wait(timeout = nil)
|
|
11
|
+
Runtime.with_waiter(self, @waiters) do |waiter|
|
|
12
|
+
Runtime.with_timeout(timeout) { Runtime.park(self, waiter) { false } }
|
|
13
|
+
true
|
|
14
|
+
rescue TimeoutError
|
|
15
|
+
false
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def signal
|
|
20
|
+
waiter = @waiters.shift or return false
|
|
21
|
+
Runtime.wake_dequeued(waiter, self)
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def signal_all
|
|
26
|
+
pending, @waiters = @waiters, []
|
|
27
|
+
pending.each { |waiter| Runtime.wake_dequeued(waiter, self) }
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PgPipeline
|
|
4
|
+
module Runtime
|
|
5
|
+
class Queue
|
|
6
|
+
def initialize
|
|
7
|
+
@queue = ::Thread::Queue.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def enqueue(item)
|
|
11
|
+
@queue.push(item)
|
|
12
|
+
item
|
|
13
|
+
rescue ClosedQueueError
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def dequeue
|
|
18
|
+
@queue.pop
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def close
|
|
22
|
+
@queue.close
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def closed? = @queue.closed?
|
|
27
|
+
def empty? = @queue.empty?
|
|
28
|
+
def size = @queue.size
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PgPipeline
|
|
4
|
+
module Runtime
|
|
5
|
+
class Semaphore
|
|
6
|
+
def initialize(limit)
|
|
7
|
+
@limit = Integer(limit)
|
|
8
|
+
raise ArgumentError, "limit must be >= 0" if @limit.negative?
|
|
9
|
+
|
|
10
|
+
@available = @limit
|
|
11
|
+
@waiting = WaitList.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def acquire
|
|
15
|
+
wait
|
|
16
|
+
return @available unless block_given?
|
|
17
|
+
|
|
18
|
+
begin
|
|
19
|
+
yield
|
|
20
|
+
ensure
|
|
21
|
+
release
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def release
|
|
26
|
+
if (node = @waiting.shift)
|
|
27
|
+
node.grant!
|
|
28
|
+
node.resume
|
|
29
|
+
else
|
|
30
|
+
@available += 1
|
|
31
|
+
end
|
|
32
|
+
@available
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def wait
|
|
38
|
+
return (@available -= 1) if @available.positive?
|
|
39
|
+
|
|
40
|
+
task = Runtime.current_task
|
|
41
|
+
node = FiberNode.new(Fiber.current, self)
|
|
42
|
+
@waiting.push(node)
|
|
43
|
+
task&.enter_block(node.waiter)
|
|
44
|
+
|
|
45
|
+
begin
|
|
46
|
+
until node.granted?
|
|
47
|
+
node.suspend
|
|
48
|
+
abandon_wait!(node, task) if task&.cancelled?
|
|
49
|
+
end
|
|
50
|
+
abandon_wait!(node, task) if task&.cancelled?
|
|
51
|
+
ensure
|
|
52
|
+
task&.exit_block
|
|
53
|
+
@waiting.remove(node) unless node.granted? || node.list.nil?
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def abandon_wait!(node, task)
|
|
58
|
+
node.granted? ? release : @waiting.remove(node)
|
|
59
|
+
task.raise_if_cancelled!
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class WaitList
|
|
63
|
+
attr_reader :size
|
|
64
|
+
|
|
65
|
+
def initialize
|
|
66
|
+
@head = @tail = nil
|
|
67
|
+
@size = 0
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def first = @head
|
|
71
|
+
def empty? = @head.nil?
|
|
72
|
+
|
|
73
|
+
def push(node)
|
|
74
|
+
raise ArgumentError, "node already queued" if node.list
|
|
75
|
+
|
|
76
|
+
node.list = self
|
|
77
|
+
node.prev = @tail
|
|
78
|
+
node.next = nil
|
|
79
|
+
@tail ? (@tail.next = node) : (@head = node)
|
|
80
|
+
@tail = node
|
|
81
|
+
@size += 1
|
|
82
|
+
node
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def shift
|
|
86
|
+
node = @head or return nil
|
|
87
|
+
remove(node)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def remove(node)
|
|
91
|
+
return nil unless node.list.equal?(self)
|
|
92
|
+
|
|
93
|
+
node.prev ? (node.prev.next = node.next) : (@head = node.next)
|
|
94
|
+
node.next ? (node.next.prev = node.prev) : (@tail = node.prev)
|
|
95
|
+
node.list = node.prev = node.next = nil
|
|
96
|
+
@size -= 1
|
|
97
|
+
node
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class FiberNode
|
|
102
|
+
attr_accessor :list, :prev, :next
|
|
103
|
+
attr_reader :fiber, :waiter, :blocker
|
|
104
|
+
|
|
105
|
+
def initialize(fiber, blocker)
|
|
106
|
+
@fiber = fiber
|
|
107
|
+
@blocker = blocker
|
|
108
|
+
@list = @prev = @next = nil
|
|
109
|
+
@granted = false
|
|
110
|
+
@scheduler = Fiber.scheduler
|
|
111
|
+
@waiter = {fiber: fiber, scheduler: @scheduler, ready: false, blocker: blocker, queued: true}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def granted? = @granted
|
|
115
|
+
|
|
116
|
+
def grant!
|
|
117
|
+
@granted = true
|
|
118
|
+
@waiter[:ready] = true
|
|
119
|
+
@waiter[:queued] = false
|
|
120
|
+
self
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def suspend
|
|
124
|
+
@scheduler.block(@blocker, nil)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def resume
|
|
128
|
+
fiber = @fiber
|
|
129
|
+
return unless fiber.alive?
|
|
130
|
+
|
|
131
|
+
@scheduler.unblock(@blocker, fiber)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private_constant :WaitList, :FiberNode
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PgPipeline
|
|
4
|
+
module Runtime
|
|
5
|
+
class Task
|
|
6
|
+
attr_reader :fiber, :name, :error
|
|
7
|
+
|
|
8
|
+
def self.spawn(name: nil, &block)
|
|
9
|
+
raise ArgumentError, "block required" unless block
|
|
10
|
+
|
|
11
|
+
task = new(name: name)
|
|
12
|
+
task.__send__(:start, &block)
|
|
13
|
+
task
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(name: nil)
|
|
17
|
+
@fiber, @scheduler, @blocker, @result, @error = nil, nil, nil, nil, nil
|
|
18
|
+
@done, @cancelled = false, false
|
|
19
|
+
|
|
20
|
+
@name = name
|
|
21
|
+
@waiters = []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def wait(timeout = nil)
|
|
25
|
+
Runtime.with_timeout(timeout) { join }
|
|
26
|
+
raise @error if @error
|
|
27
|
+
|
|
28
|
+
@result
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def stop
|
|
32
|
+
return false if @done
|
|
33
|
+
|
|
34
|
+
@cancelled = true
|
|
35
|
+
|
|
36
|
+
return true if interrupt_fiber
|
|
37
|
+
return true if release_blocker
|
|
38
|
+
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def cancelled? = @cancelled
|
|
43
|
+
|
|
44
|
+
def raise_if_cancelled!
|
|
45
|
+
raise Cancel, "task stopped" if @cancelled
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def enter_block(waiter)
|
|
49
|
+
@blocker = waiter
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def exit_block
|
|
53
|
+
@blocker = nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def finished? = @done
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def start(&block)
|
|
61
|
+
@scheduler = Runtime.scheduler!
|
|
62
|
+
this = self
|
|
63
|
+
|
|
64
|
+
scheduled = Fiber.schedule do
|
|
65
|
+
this.__send__(:adopt_fiber, Fiber.current)
|
|
66
|
+
Thread.current[Runtime::CURRENT_TASK_KEY] = this
|
|
67
|
+
begin
|
|
68
|
+
this.__send__(:complete, block.call, nil)
|
|
69
|
+
rescue Cancel
|
|
70
|
+
this.__send__(:complete, nil, nil)
|
|
71
|
+
rescue Exception => e
|
|
72
|
+
this.__send__(:complete, nil, e)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
@fiber ||= scheduled
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def adopt_fiber(fiber)
|
|
80
|
+
@fiber ||= fiber
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def interrupt_fiber
|
|
84
|
+
fiber = @fiber
|
|
85
|
+
scheduler = @scheduler
|
|
86
|
+
return false unless fiber&.alive?
|
|
87
|
+
return false unless scheduler&.respond_to?(:fiber_interrupt)
|
|
88
|
+
|
|
89
|
+
result = scheduler.fiber_interrupt(fiber, Cancel.new("task stopped"))
|
|
90
|
+
result != false
|
|
91
|
+
rescue FiberError
|
|
92
|
+
false
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def release_blocker
|
|
96
|
+
waiter = @blocker or return false
|
|
97
|
+
Runtime.wake(waiter, waiter[:blocker])
|
|
98
|
+
waiter[:fiber].alive?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def complete(result, error)
|
|
102
|
+
return if @done
|
|
103
|
+
|
|
104
|
+
@result = result
|
|
105
|
+
@error = error
|
|
106
|
+
@done = true
|
|
107
|
+
wake_waiters
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def join
|
|
111
|
+
return if @done
|
|
112
|
+
|
|
113
|
+
Runtime.with_waiter(self, @waiters) do |waiter|
|
|
114
|
+
Runtime.park(self, waiter) { @done }
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def wake_waiters
|
|
119
|
+
pending, @waiters = @waiters, []
|
|
120
|
+
pending.each { |waiter| Runtime.wake_dequeued(waiter, self) }
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "timeout"
|
|
4
|
+
|
|
5
|
+
require_relative "errors"
|
|
6
|
+
|
|
7
|
+
module PgPipeline
|
|
8
|
+
module Runtime
|
|
9
|
+
class Cancel < Exception; end
|
|
10
|
+
class TimeoutError < Error; end
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
CURRENT_TASK_KEY = :pg_pipeline_current_task
|
|
15
|
+
WAITER_KEY = :pg_pipeline_waiter
|
|
16
|
+
|
|
17
|
+
def spawn(name: nil, &block)
|
|
18
|
+
Task.spawn(name: name, &block)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def scheduler!
|
|
22
|
+
Fiber.scheduler or raise Error, "operation requires an active Fiber scheduler"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def current_task
|
|
26
|
+
Thread.current[CURRENT_TASK_KEY]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build_waiter(blocker)
|
|
30
|
+
waiter = Thread.current[WAITER_KEY] ||= {
|
|
31
|
+
fiber: nil, scheduler: nil, ready: false, blocker: nil
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if waiter[:blocker]
|
|
35
|
+
raise Error, "waiter already parked on #{waiter[:blocker].class}; " \
|
|
36
|
+
"a fiber may only park in one place at a time"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
waiter[:fiber] = Fiber.current
|
|
40
|
+
waiter[:scheduler] = scheduler!
|
|
41
|
+
waiter[:ready] = false
|
|
42
|
+
waiter[:queued] = false
|
|
43
|
+
waiter[:blocker] = blocker
|
|
44
|
+
waiter
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def park(blocker, waiter)
|
|
48
|
+
scheduler = waiter[:scheduler]
|
|
49
|
+
task = current_task
|
|
50
|
+
task&.enter_block(waiter)
|
|
51
|
+
|
|
52
|
+
until waiter[:ready] || yield
|
|
53
|
+
scheduler.block(blocker, nil)
|
|
54
|
+
task&.raise_if_cancelled!
|
|
55
|
+
end
|
|
56
|
+
nil
|
|
57
|
+
ensure
|
|
58
|
+
waiter[:blocker] = nil
|
|
59
|
+
task&.exit_block
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def with_waiter(blocker, waiters)
|
|
63
|
+
waiter = build_waiter(blocker)
|
|
64
|
+
waiter[:queued] = true
|
|
65
|
+
waiters << waiter
|
|
66
|
+
yield waiter
|
|
67
|
+
ensure
|
|
68
|
+
waiters.delete(waiter) if waiter[:queued]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def wake_dequeued(waiter, blocker)
|
|
72
|
+
waiter[:queued] = false
|
|
73
|
+
wake(waiter, blocker)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def with_timeout(duration)
|
|
77
|
+
return yield if duration.nil?
|
|
78
|
+
|
|
79
|
+
timeout = Float(duration)
|
|
80
|
+
raise ArgumentError, "timeout must be non-negative and finite" unless timeout.finite? && timeout >= 0
|
|
81
|
+
|
|
82
|
+
::Timeout.timeout(timeout, TimeoutError) { yield }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def wake(waiter, blocker)
|
|
86
|
+
waiter[:ready] = true
|
|
87
|
+
fiber = waiter[:fiber]
|
|
88
|
+
return unless fiber.alive?
|
|
89
|
+
|
|
90
|
+
waiter[:scheduler].unblock(blocker, fiber)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
require_relative "runtime/notification"
|
|
96
|
+
require_relative "runtime/queue"
|
|
97
|
+
require_relative "runtime/semaphore"
|
|
98
|
+
require_relative "runtime/task"
|
|
@@ -18,7 +18,6 @@ module PgPipeline
|
|
|
18
18
|
"select-into-temp" => /\binto\s+(?:(?:global|local)\s+)?temp(?:orary)?\b/i,
|
|
19
19
|
"select-into-pg-temp" => /\binto\s+(?:table\s+)?pg_temp(?:_\d+)?\s*\./i
|
|
20
20
|
}.freeze
|
|
21
|
-
|
|
22
21
|
STRICT_FORBIDDEN = {
|
|
23
22
|
"nextval" => /\bnextval\s*\(/i,
|
|
24
23
|
"setval" => /\bsetval\s*\(/i,
|
|
@@ -28,9 +27,9 @@ module PgPipeline
|
|
|
28
27
|
"session-advisory-unlock" => /\bpg_advisory_unlock(?:_shared|_all)?\s*\(/i,
|
|
29
28
|
"pg_export_snapshot" => /\bpg_export_snapshot\s*\(/i
|
|
30
29
|
}.freeze
|
|
31
|
-
|
|
32
30
|
PATTERN_PREFILTER = /\binto\b/i
|
|
33
|
-
|
|
31
|
+
DOLLAR_QUOTE_TAG = /\A\$(?:(?:[A-Za-z_]|[^\x00-\x7F])(?:[A-Za-z0-9_]|[^\x00-\x7F])*)?\$/
|
|
32
|
+
NEEDS_MASK = /['"]|--|\/\*|\$(?:(?:[A-Za-z_]|[^\x00-\x7F])(?:[A-Za-z0-9_]|[^\x00-\x7F])*)?\$/
|
|
34
33
|
LEADING_KEYWORD = /\A\s*([a-zA-Z_]+)/
|
|
35
34
|
WHITESPACE_BYTES = [9, 10, 11, 12, 13, 32].freeze
|
|
36
35
|
|
|
@@ -84,7 +83,6 @@ module PgPipeline
|
|
|
84
83
|
FORBIDDEN_PATTERNS.each do |name, pattern|
|
|
85
84
|
return name if code.match?(pattern)
|
|
86
85
|
end
|
|
87
|
-
|
|
88
86
|
if mode == :strict
|
|
89
87
|
STRICT_FORBIDDEN.each do |name, pattern|
|
|
90
88
|
return "strict:#{name}" if code.match?(pattern)
|
|
@@ -111,7 +109,6 @@ module PgPipeline
|
|
|
111
109
|
while index < size
|
|
112
110
|
byte = source.getbyte(index)
|
|
113
111
|
nxt = index + 1 < size ? source.getbyte(index + 1) : nil
|
|
114
|
-
|
|
115
112
|
if block_depth.positive?
|
|
116
113
|
if byte == 47 && nxt == 42
|
|
117
114
|
block_depth += 1
|
|
@@ -159,7 +156,7 @@ module PgPipeline
|
|
|
159
156
|
|
|
160
157
|
if byte == 36
|
|
161
158
|
remainder = source.byteslice(index, size - index)
|
|
162
|
-
tag =
|
|
159
|
+
tag = DOLLAR_QUOTE_TAG.match(remainder)&.[](0)
|
|
163
160
|
if tag
|
|
164
161
|
closing = source.index(tag, index + tag.bytesize)
|
|
165
162
|
finish = closing ? closing + tag.bytesize : size
|
|
@@ -182,7 +179,6 @@ module PgPipeline
|
|
|
182
179
|
|
|
183
180
|
while index < source.bytesize
|
|
184
181
|
byte = source.getbyte(index)
|
|
185
|
-
|
|
186
182
|
if escape_backslash && byte == 92
|
|
187
183
|
output << " "
|
|
188
184
|
index += 1
|
|
@@ -203,7 +199,6 @@ module PgPipeline
|
|
|
203
199
|
index += 1
|
|
204
200
|
end
|
|
205
201
|
end
|
|
206
|
-
|
|
207
202
|
index
|
|
208
203
|
end
|
|
209
204
|
private_class_method :mask_quoted
|
|
@@ -39,7 +39,7 @@ module PgPipeline
|
|
|
39
39
|
tx.__send__(:open=, true)
|
|
40
40
|
begin
|
|
41
41
|
result = yield tx
|
|
42
|
-
conn
|
|
42
|
+
commit(conn)
|
|
43
43
|
tx.__send__(:open=, false)
|
|
44
44
|
result
|
|
45
45
|
rescue Exception
|
|
@@ -48,6 +48,28 @@ module PgPipeline
|
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
def commit(conn)
|
|
52
|
+
conn.exec("COMMIT")
|
|
53
|
+
rescue PG::Error => error
|
|
54
|
+
raise unless indeterminate_commit_failure?(conn, error)
|
|
55
|
+
|
|
56
|
+
raise IndeterminateCommitError.new(
|
|
57
|
+
"COMMIT acknowledgement was not received; the transaction may have committed " \
|
|
58
|
+
"and must not be retried blindly (#{error.class}: #{error.message})"
|
|
59
|
+
), cause: error
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def indeterminate_commit_failure?(conn, error)
|
|
63
|
+
return true if defined?(PG::ConnectionBad) && error.is_a?(PG::ConnectionBad)
|
|
64
|
+
return true if conn.finished?
|
|
65
|
+
|
|
66
|
+
conn.status != PG::CONNECTION_OK
|
|
67
|
+
rescue PG::Error
|
|
68
|
+
true
|
|
69
|
+
rescue NoMethodError
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
|
|
51
73
|
def savepoint(tx, name)
|
|
52
74
|
SessionOps.ensure_active!(tx)
|
|
53
75
|
raise Error, "savepoint requires an open transaction" unless tx.open?
|
|
@@ -57,7 +79,6 @@ module PgPipeline
|
|
|
57
79
|
tx.__send__(:savepoint_seq=, seq)
|
|
58
80
|
point = name || "pgp_sp_#{seq}"
|
|
59
81
|
ident = conn.quote_ident(point)
|
|
60
|
-
|
|
61
82
|
conn.exec("SAVEPOINT #{ident}")
|
|
62
83
|
begin
|
|
63
84
|
result = yield tx
|
data/lib/pg_pipeline/version.rb
CHANGED
data/lib/pg_pipeline.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pg_pipeline
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Roman Hajdarov
|
|
@@ -9,20 +9,6 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: async
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - "~>"
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '2.42'
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - "~>"
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '2.42'
|
|
26
12
|
- !ruby/object:Gem::Dependency
|
|
27
13
|
name: pg
|
|
28
14
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -43,6 +29,20 @@ dependencies:
|
|
|
43
29
|
- - "<"
|
|
44
30
|
- !ruby/object:Gem::Version
|
|
45
31
|
version: '2'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: async
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - "~>"
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '2.42'
|
|
39
|
+
type: :development
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - "~>"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '2.42'
|
|
46
46
|
- !ruby/object:Gem::Dependency
|
|
47
47
|
name: rake
|
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -101,10 +101,12 @@ dependencies:
|
|
|
101
101
|
version: '0.2'
|
|
102
102
|
description: |
|
|
103
103
|
A driver-adjacent Ruby control-plane over ruby-pg/libpq. It multiplexes
|
|
104
|
-
independent, session-neutral extended-protocol operations from many
|
|
105
|
-
|
|
104
|
+
independent, session-neutral extended-protocol operations from many fibers
|
|
105
|
+
onto a small number of PostgreSQL connections while keeping explicit
|
|
106
106
|
transactions and session-changing work on exclusive pinned connections.
|
|
107
|
-
|
|
107
|
+
Requires any Fiber::Scheduler host (Async::Scheduler, Itsi::Scheduler, …);
|
|
108
|
+
the gem does not depend on a particular reactor. Control-plane only: all
|
|
109
|
+
wire work stays in libpq.
|
|
108
110
|
email:
|
|
109
111
|
- romanhajdarov@gmail.com
|
|
110
112
|
executables: []
|
|
@@ -123,6 +125,11 @@ files:
|
|
|
123
125
|
- lib/pg_pipeline/pool.rb
|
|
124
126
|
- lib/pg_pipeline/prepared_statement.rb
|
|
125
127
|
- lib/pg_pipeline/request.rb
|
|
128
|
+
- lib/pg_pipeline/runtime.rb
|
|
129
|
+
- lib/pg_pipeline/runtime/notification.rb
|
|
130
|
+
- lib/pg_pipeline/runtime/queue.rb
|
|
131
|
+
- lib/pg_pipeline/runtime/semaphore.rb
|
|
132
|
+
- lib/pg_pipeline/runtime/task.rb
|
|
126
133
|
- lib/pg_pipeline/server_caps.rb
|
|
127
134
|
- lib/pg_pipeline/session.rb
|
|
128
135
|
- lib/pg_pipeline/session_guard.rb
|
|
@@ -152,5 +159,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
152
159
|
requirements: []
|
|
153
160
|
rubygems_version: 3.6.7
|
|
154
161
|
specification_version: 4
|
|
155
|
-
summary:
|
|
162
|
+
summary: Fiber-scheduler PostgreSQL pipeline multiplexing on top of ruby-pg
|
|
156
163
|
test_files: []
|