pg_pipeline 0.2.3 → 0.2.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/CHANGELOG.md +34 -0
- data/DESIGN.md +16 -7
- data/README.md +2 -0
- data/lib/pg_pipeline/request.rb +37 -8
- data/lib/pg_pipeline/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e6537ec11ece29a487aa35449c264d2bae69089022a28c473f71523e52fd2acd
|
|
4
|
+
data.tar.gz: 231b7285d0ddf23bb686836217ff35d1c89cc3872a5ac4ef20df28cea7e781bb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 31ef9eeacef9208296c2df0d29f5a83e0a2ee8a2278715bd4b67d261fb1561a5b0e11771856b5dfb27d34a28cb21034e7b6cda5408757f6e13672a3db65c8d3d
|
|
7
|
+
data.tar.gz: b686d8bd24fb976e8d9e6842b92db04988b1ae6f5981b297de57fc7d32b0add212d200f5b2906d5a7ea450b47bf46b9373a4404d5449ab24b702b189fb2a9131
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.4] - 2026-07-31
|
|
4
|
+
|
|
5
|
+
Request-completion allocation patch. It replaces the general-purpose
|
|
6
|
+
`Async::Notification` attached to every multiplexed request with a direct
|
|
7
|
+
single-waiter fiber handoff while preserving deferred reactor wakeups and the
|
|
8
|
+
existing SQL, Sync, result and failure semantics.
|
|
9
|
+
|
|
10
|
+
### Performance
|
|
11
|
+
|
|
12
|
+
- A request now stores only the waiting fiber and its originating scheduler.
|
|
13
|
+
`Scheduler#block` parks that fiber and `Scheduler#unblock` schedules only that
|
|
14
|
+
waiter on a later reactor turn.
|
|
15
|
+
- Removed the per-request `Async::Notification` and its initial
|
|
16
|
+
`Thread::Queue`; when a waiter is present, completion also avoids the
|
|
17
|
+
replacement queue and `Async::Notification::Signal` allocation.
|
|
18
|
+
- The optimization is limited to one-shot request completion. Bounded-queue and
|
|
19
|
+
pinned-pool notifications retain `Async::Notification` because they require
|
|
20
|
+
multi-waiter coordination.
|
|
21
|
+
|
|
22
|
+
### Reliability
|
|
23
|
+
|
|
24
|
+
- Completion-before-wait still returns immediately through the settled guard, so
|
|
25
|
+
no wakeup can be lost.
|
|
26
|
+
- Timeout or task cancellation clears the stored waiter in an `ensure`, preventing
|
|
27
|
+
a late query completion from retaining or waking an abandoned fiber.
|
|
28
|
+
- A second concurrent waiter is rejected explicitly instead of silently replacing
|
|
29
|
+
the first waiter.
|
|
30
|
+
- The scheduler associated with the waiting fiber is stored and used for the
|
|
31
|
+
matching unblock rather than looking up an implicit current scheduler at
|
|
32
|
+
completion time.
|
|
33
|
+
- Added focused coverage for wait-before-completion, completion after an
|
|
34
|
+
interrupted wait, the single-waiter invariant and already-settled waits outside
|
|
35
|
+
an active scheduler.
|
|
36
|
+
|
|
3
37
|
## [0.2.3] - 2026-07-30
|
|
4
38
|
|
|
5
39
|
Performance-focused release based on CPU profiles from the live pipeline
|
data/DESIGN.md
CHANGED
|
@@ -126,7 +126,7 @@ driver that lacks a successfully registered handle.
|
|
|
126
126
|
Registration is explicit: there is no unbounded automatic SQL cache. A failed
|
|
127
127
|
registration is removed from the reconnect catalog; statements that had already
|
|
128
128
|
been prepared on another connection may remain there under an unreachable
|
|
129
|
-
generated name until that connection is replaced or closed. Version 0.2.3
|
|
129
|
+
generated name until that connection is replaced or closed. Version 0.2.3 and later do
|
|
130
130
|
not expose `DEALLOCATE` for multiplexed handles. Server-side plan invalidation is
|
|
131
131
|
reported as the ordinary request-local `QueryError`.
|
|
132
132
|
|
|
@@ -221,15 +221,24 @@ without reset would leak that state to the next caller. After each pinned block:
|
|
|
221
221
|
|
|
222
222
|
Immediately resuming a waiter from inside the connection owner is unsafe: a
|
|
223
223
|
resumed caller could re-enter the driver (submit, or close the client and wait on
|
|
224
|
-
the owner) while the owner is
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
224
|
+
the owner) while the owner is still completing the current pipeline unit.
|
|
225
|
+
|
|
226
|
+
A `Request` has exactly one consumer, so request completion stores that waiter
|
|
227
|
+
fiber and its scheduler directly. `Scheduler#block` parks it, and
|
|
228
|
+
`Scheduler#unblock` pushes it onto the selector for a later reactor turn. This
|
|
229
|
+
preserves deferred wakeup without allocating a general-purpose
|
|
230
|
+
`Async::Notification` and its queue objects for every query. The settled flag
|
|
231
|
+
handles completion-before-wait, and an `ensure` removes a waiter interrupted by
|
|
232
|
+
timeout or task cancellation before a late result can try to wake it.
|
|
233
|
+
|
|
234
|
+
Multi-waiter coordination points still use `Async::Notification`: bounded-queue
|
|
235
|
+
backpressure and pinned-pool idle notifications need queue/broadcast semantics
|
|
236
|
+
that the request-specific one-shot waiter deliberately does not provide.
|
|
228
237
|
|
|
229
238
|
> Note: the supported Async range starts at 2.42 and stays below 3. The lockfile
|
|
230
239
|
> exercises the current compatible 2.x release, while CI separately runs the unit
|
|
231
|
-
> suite against the exact 2.42.0 floor so
|
|
232
|
-
> assumed from a broad pessimistic dependency range.
|
|
240
|
+
> suite against the exact 2.42.0 floor so `Scheduler#block`/`#unblock` behavior is
|
|
241
|
+
> not merely assumed from a broad pessimistic dependency range.
|
|
233
242
|
|
|
234
243
|
## 11. Version policy
|
|
235
244
|
|
data/README.md
CHANGED
|
@@ -187,6 +187,8 @@ See `examples/falcon_config.ru` for a complete setup.
|
|
|
187
187
|
|
|
188
188
|
## Benchmark
|
|
189
189
|
|
|
190
|
+
[Performance test results and methodology](docs/PERFORMANCE.md).
|
|
191
|
+
|
|
190
192
|
```bash
|
|
191
193
|
docker compose up -d pg17
|
|
192
194
|
export PG_PIPELINE_URL=postgres://postgres:postgres@127.0.0.1:5417/postgres
|
data/lib/pg_pipeline/request.rb
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "async/notification"
|
|
4
|
-
|
|
5
3
|
require_relative "errors"
|
|
6
4
|
|
|
7
5
|
module PgPipeline
|
|
8
6
|
class Request
|
|
9
|
-
attr_reader :sql, :params
|
|
7
|
+
attr_reader :sql, :params
|
|
10
8
|
attr_accessor :state, :cancelled, :settled, :result_seen, :query_boundary_seen,
|
|
11
|
-
:result, :error
|
|
9
|
+
:result, :error, :waiter, :waiter_scheduler
|
|
12
10
|
|
|
13
11
|
def initialize(sql:, params: nil)
|
|
14
12
|
@sql = RequestOps.snapshot_sql(sql)
|
|
15
13
|
@params = RequestOps.snapshot_params(params)
|
|
16
14
|
@state = :new
|
|
17
|
-
@condition = Async::Notification.new
|
|
18
15
|
@cancelled = false
|
|
19
16
|
@settled = false
|
|
20
17
|
@result_seen = false
|
|
21
18
|
@query_boundary_seen = false
|
|
22
19
|
@result = nil
|
|
23
20
|
@error = nil
|
|
21
|
+
@waiter = nil
|
|
22
|
+
@waiter_scheduler = nil
|
|
24
23
|
end
|
|
25
24
|
|
|
26
25
|
def self.prepare(statement) = PrepareRequest.new(statement)
|
|
@@ -156,7 +155,7 @@ module PgPipeline
|
|
|
156
155
|
|
|
157
156
|
req.settled = true
|
|
158
157
|
req.state = :done
|
|
159
|
-
req
|
|
158
|
+
wake_waiter(req) unless req.cancelled
|
|
160
159
|
end
|
|
161
160
|
|
|
162
161
|
def reject!(req, error)
|
|
@@ -167,7 +166,7 @@ module PgPipeline
|
|
|
167
166
|
req.error ||= error
|
|
168
167
|
req.settled = true
|
|
169
168
|
req.state = :done
|
|
170
|
-
req
|
|
169
|
+
wake_waiter(req) unless req.cancelled
|
|
171
170
|
end
|
|
172
171
|
|
|
173
172
|
def cancel!(req)
|
|
@@ -180,12 +179,42 @@ module PgPipeline
|
|
|
180
179
|
end
|
|
181
180
|
|
|
182
181
|
def wait(req)
|
|
183
|
-
req
|
|
182
|
+
park_waiter(req) unless req.settled
|
|
184
183
|
raise req.error if req.error
|
|
185
184
|
|
|
186
185
|
req.result
|
|
187
186
|
end
|
|
188
187
|
|
|
188
|
+
def park_waiter(req)
|
|
189
|
+
raise ProtocolError, "request already has a waiter" if req.waiter
|
|
190
|
+
|
|
191
|
+
scheduler = Fiber.scheduler
|
|
192
|
+
raise Error, "request wait requires an active Fiber scheduler" unless scheduler
|
|
193
|
+
|
|
194
|
+
waiter = Fiber.current
|
|
195
|
+
req.waiter = waiter
|
|
196
|
+
req.waiter_scheduler = scheduler
|
|
197
|
+
|
|
198
|
+
begin
|
|
199
|
+
scheduler.block(req, nil) until req.settled
|
|
200
|
+
ensure
|
|
201
|
+
if req.waiter.equal?(waiter)
|
|
202
|
+
req.waiter = nil
|
|
203
|
+
req.waiter_scheduler = nil
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def wake_waiter(req)
|
|
209
|
+
waiter = req.waiter
|
|
210
|
+
scheduler = req.waiter_scheduler
|
|
211
|
+
|
|
212
|
+
req.waiter = nil
|
|
213
|
+
req.waiter_scheduler = nil
|
|
214
|
+
|
|
215
|
+
scheduler.unblock(req, waiter) if scheduler && waiter
|
|
216
|
+
end
|
|
217
|
+
|
|
189
218
|
def assert_result_slot!(req)
|
|
190
219
|
raise ProtocolError, "result arrived after query boundary" if req.query_boundary_seen
|
|
191
220
|
raise ProtocolError, "multiple results for one pipeline unit" if req.result_seen
|
data/lib/pg_pipeline/version.rb
CHANGED