pool_machines 0.1.3-x86_64-linux-musl

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c6b296aef106fc0885f643bf602d08fa77556c3c9bbb14eb7e38dc8a91598ad2
4
+ data.tar.gz: 1da98319728442f71015117a4388d79a3a7d7f4fbe60330165459a393db88e27
5
+ SHA512:
6
+ metadata.gz: c72dde4e1bf389cdbc1cab774ff4ff3f8b26024c1a67345b9a3b892cbde3e0ac6e59b339dfa8572fb985098b6eeafe28d42091c1b9126eb435088fd458702023
7
+ data.tar.gz: a003c86e00f9537b228aa47996506c6fe9b91c72658c918472fb6f65e0f93926c1c1472e7e6a2857eb27b917d58448d86711d618ee7b0d0cfdede80ff03028fd
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## [0.1.3](https://github.com/seuros/pool_machines/compare/pool_machines/v0.1.2...pool_machines/v0.1.3) (2026-11-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * crates.io keywords and categories for the core ([e65b159](https://github.com/seuros/pool_machines/commit/e65b1592ae2868c0acea23df562fab5b43dab71f))
9
+
10
+ ## [0.1.2](https://github.com/seuros/pool_machines/compare/pool_machines/v0.1.1...pool_machines/v0.1.2) (2026-11-06)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * keep test sources out of the packaged gem and crate ([d256e29](https://github.com/seuros/pool_machines/commit/d256e29c8cb52218216e7c796057a7fef3205b00))
16
+
17
+ ## [0.1.1](https://github.com/seuros/pool_machines/compare/pool_machines-v0.1.0...pool_machines/v0.1.1) (2026-11-06)
18
+
19
+
20
+ ### Features
21
+
22
+ * initial extraction ([030ecb6](https://github.com/seuros/pool_machines/commit/030ecb6977a3559cd397225ab2d38137a46d39b7))
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Abdelkader Boudih
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # PoolMachines
2
+
3
+ > A connection returned twice is a connection somebody else is now using. 🛠️
4
+
5
+ A resource pool ledger for Ruby. You register the slots you've built, check them out, check them in, and ask which idle ones are old enough to tear down. The `Pool` never opens a socket or closes a file. It keeps the roster: which slot is busy, which is idle and since when, and whether the handle you just returned is the one it issued or a stale copy from an earlier trip.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ gem 'pool_machines'
11
+ ```
12
+
13
+ ```ruby
14
+ pool = PoolMachines::Pool.new
15
+ now = -> { Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) }
16
+
17
+ # You build the connection. The pool just learns that slot 0 is serviceable.
18
+ connections[0] = PG.connect(db_url)
19
+ pool.register(0, now.call) # => true
20
+
21
+ # Each request borrows a slot and gives it back.
22
+ handle = pool.checkout(now.call) # => PoolMachines::Handle, or nil when none is free
23
+ connections[handle.slot].exec(sql)
24
+ pool.checkin(handle, now.call) # => "idle"
25
+
26
+ # On a quiet night, tear down whatever has sat idle for five minutes.
27
+ while (slot = pool.retire_idle(now.call, 5 * 60 * 1_000_000_000))
28
+ connections.delete(slot).close
29
+ end
30
+ ```
31
+
32
+ Times are nanoseconds from a monotonic clock. Slots are the integers 0 through 31.
33
+
34
+ ## A Message to the Resistance
35
+
36
+ So you wrote a connection pool. Everyone has. An array of connections, a mutex, `pop` on the way out, `push` on the way back. It worked for a year.
37
+
38
+ Then a request timed out mid-query, the rescue block pushed the connection back, the ensure block pushed it back again, and for the next six hours two threads shared one socket. The errors made no sense because the errors belonged to someone else's query.
39
+
40
+ The bug isn't the array. The bug is that a bare connection object can't tell you whether it's already been returned. The thing you push back has to carry a receipt, and the pool has to refuse a receipt it has already stamped. This gem is the receipt book. Your connections stay yours.
41
+
42
+ ## Chapter 1: Stardate 2025.186, the AMS Little
43
+
44
+ > **AMS Little**, NCC-4214, *Little*-class service tender.
45
+ > Do not fabricate a new warp core for every departure.
46
+
47
+ The tender doesn't build engines. It keeps the dock roster: thirty-two bays, which are occupied, which hold a serviced core waiting for a ship, and how long each has waited. A crew that brings a core back gets a fresh bay ticket. A crew that turns up with last week's ticket is turned away, because that bay has been issued to someone else since.
48
+
49
+ Somewhere on the dock, a cadet proposes:
50
+
51
+ ```ruby
52
+ def with_connection
53
+ conn = @free.pop || PG.connect(db_url)
54
+ yield conn
55
+ rescue => e
56
+ @free.push(conn) # give it back on error
57
+ raise
58
+ ensure
59
+ @free.push(conn) # and also give it back always
60
+ end
61
+ ```
62
+
63
+ "That," says the dockmaster, "is how two ships leave on one core."
64
+
65
+ ## The Flight Plan
66
+
67
+ Every slot is in exactly one state. Draining is a property of the whole pool.
68
+
69
+ ```mermaid
70
+ stateDiagram-v2
71
+ [*] --> vacant
72
+ vacant --> idle: register(slot, now)
73
+ idle --> busy: checkout(now) issues a handle
74
+ busy --> idle: checkin(handle, now) returns "idle"
75
+ busy --> vacant: checkin while draining returns "retire"
76
+ idle --> vacant: retire_idle(now, idle_for) picks it
77
+ vacant --> idle: register again, same slot, fresh generation
78
+ ```
79
+
80
+ And the deploy where the old pool was drained:
81
+
82
+ ```mermaid
83
+ sequenceDiagram
84
+ participant App
85
+ participant Pool
86
+ participant DB as Connections
87
+
88
+ App->>Pool: register(0, t0)
89
+ App->>Pool: register(1, t0)
90
+ App->>Pool: checkout(t1)
91
+ Pool-->>App: handle for slot 0
92
+ Note over App: query in flight on slot 0
93
+
94
+ Note over App: SIGTERM, deploy starting
95
+ App->>Pool: begin_drain
96
+ App->>Pool: checkout(t2)
97
+ Pool-->>App: nil, no new departures
98
+ App->>Pool: retire_idle(t2, any)
99
+ Pool-->>App: 1
100
+ App->>DB: close connection 1
101
+
102
+ App->>Pool: checkin(handle, t3)
103
+ Pool-->>App: "retire"
104
+ App->>DB: close connection 0
105
+ Note over Pool: checked_out is 0. Dock is empty.
106
+ ```
107
+
108
+ ## How the Dock Works
109
+
110
+ ### Register and check out
111
+
112
+ ```ruby
113
+ pool = PoolMachines::Pool.new
114
+ pool.checkout(0) # => nil, nothing registered yet
115
+
116
+ pool.register(0, 1) # => true
117
+ pool.register(0, 1) # => false, already on the roster
118
+
119
+ handle = pool.checkout(2)
120
+ handle.slot # => 0
121
+ pool.checked_out # => 1
122
+ ```
123
+
124
+ `checkout` returns `nil` when every registered slot is busy. It never blocks and never builds anything. If you want to grow the pool, build a resource and `register` a free slot number.
125
+
126
+ ### Check in, and the receipt that only works once
127
+
128
+ A handle is good for exactly one `checkin`. Return it and the slot goes idle. Return it again and the pool raises, because the slot may already belong to a newer handle.
129
+
130
+ ```ruby
131
+ pool = PoolMachines::Pool.new
132
+ pool.register(0, 1)
133
+
134
+ first = pool.checkout(2)
135
+ pool.checkin(first, 3) # => "idle"
136
+
137
+ second = pool.checkout(4) # same slot 0, new receipt
138
+ pool.checkin(first, 5) # raises ArgumentError, that receipt is spent
139
+ pool.checkin(second, 5) # => "idle"
140
+ ```
141
+
142
+ That's the double-push bug from the cadet's code, caught at the second push instead of six hours later.
143
+
144
+ ### Retire what's been sitting
145
+
146
+ `retire_idle` hands you one slot per call: an idle slot that has been idle for at least `idle_for` nanoseconds. It returns `nil` when nothing qualifies. Loop until `nil`, closing as you go.
147
+
148
+ ```ruby
149
+ pool = PoolMachines::Pool.new
150
+ pool.register(0, 0)
151
+ pool.register(1, 0)
152
+
153
+ pool.retire_idle(2, 3) # => nil, idle for 2, needs 3
154
+ pool.retire_idle(10, 5) # => 0
155
+ pool.retire_idle(10, 5) # => 1
156
+ pool.retire_idle(10, 5) # => nil
157
+
158
+ pool.register(0, 11) # => true, the slot is vacant again
159
+ ```
160
+
161
+ A busy slot is never retired. The pool will tell you to close it when it comes back.
162
+
163
+ ### Drain
164
+
165
+ `begin_drain` flips the pool into wind-down. No more registrations, no more checkouts. Idle slots retire regardless of how long they've been idle. Busy slots keep working, and when each one is checked in the answer is `"retire"` instead of `"idle"`, which is your cue to close it.
166
+
167
+ ```ruby
168
+ pool = PoolMachines::Pool.new
169
+ pool.register(0, 0)
170
+ pool.register(1, 0)
171
+ handle = pool.checkout(1)
172
+
173
+ pool.begin_drain
174
+ pool.checkout(3) # => nil
175
+ pool.register(2, 3) # => false
176
+ pool.retire_idle(3, 100) # => 1, idle_for is ignored while draining
177
+ pool.checkin(handle, 4) # => "retire"
178
+ pool.checked_out # => 0
179
+ ```
180
+
181
+ Drain never yanks a resource out from under a running query. It only refuses new work and tells you what to close as work finishes.
182
+
183
+ ### The clock only moves forward
184
+
185
+ You pass `now` on every call. The pool remembers the latest time it has seen and treats anything earlier as a non-event: `register` returns `false`, `checkout` returns `nil`, `checkin` raises.
186
+
187
+ ```ruby
188
+ pool = PoolMachines::Pool.new
189
+ pool.register(0, 5)
190
+ pool.register(1, 3) # => false, time went backwards
191
+ ```
192
+
193
+ Use a monotonic clock. Wall clocks jump.
194
+
195
+ ## The Rules of the Tender
196
+
197
+ - **32 slots, numbered 0 to 31.** Slot 32 or a negative slot raises `RangeError`.
198
+ - **Every number is an Integer.** Slots, times, and durations. Floats, strings, and NaN raise `TypeError`. Values at or above `2**64` raise `RangeError`.
199
+ - **Handles are opaque and single-use.** You can't construct one, `dup` one, or hand one to a different pool. A spent or foreign handle raises `ArgumentError` on `checkin`.
200
+ - **Rejections are quiet, misuse is loud.** A duplicate `register`, a backwards clock, or an empty pool returns `false` or `nil`. A bad handle or a bad type raises.
201
+ - **Frozen means frozen.** `freeze` the pool and every mutating call raises `FrozenError`. `checked_out` still answers.
202
+
203
+ ## What You Think You're Doing vs Reality
204
+
205
+ ### You Think: "The ensure block guarantees the connection goes back."
206
+ ### Reality: The ensure block guarantees it goes back at least once.
207
+
208
+ `ensure` runs after `rescue`. If both return the connection, it's returned twice, and a plain array can't tell. A pool that issues receipts can, and refuses the second one on the spot.
209
+
210
+ ## What's Yours
211
+
212
+ The pool holds slot numbers and timestamps, nothing else. You create the real resource before `register`, keep it somewhere indexed by slot, and close it when `retire_idle` or a `"retire"` check-in says so. Health checks, waiting for a free slot, and persistence across restarts are all outside the roster. Serialize calls to one pool; it doesn't lock for you.
213
+
214
+ ## License
215
+
216
+ MIT License. See [LICENSE.txt](LICENSE.txt) for details.
217
+
218
+ **Remember: The second `push` always looks harmless. The receipt book is what makes it fail where you can see it.**
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## [0.2.2](https://github.com/seuros/pool_machines/compare/pool-machines-v0.2.1...pool-machines-v0.2.2) (2026-11-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * crates.io keywords and categories for the core ([e65b159](https://github.com/seuros/pool_machines/commit/e65b1592ae2868c0acea23df562fab5b43dab71f))
9
+
10
+ ## [0.2.1](https://github.com/seuros/pool_machines/compare/pool-machines-v0.2.0...pool-machines-v0.2.1) (2026-11-06)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * keep test sources out of the packaged gem and crate ([d256e29](https://github.com/seuros/pool_machines/commit/d256e29c8cb52218216e7c796057a7fef3205b00))
16
+
17
+ ## [0.2.0](https://github.com/seuros/pool_machines/compare/pool-machines-v0.1.0...pool-machines-v0.2.0) (2026-11-06)
18
+
19
+
20
+ ### Features
21
+
22
+ * initial extraction ([030ecb6](https://github.com/seuros/pool_machines/commit/030ecb6977a3559cd397225ab2d38137a46d39b7))
@@ -0,0 +1,18 @@
1
+ # pool-machines
2
+
3
+ > **AMS Little**, NCC-4214, *Little*-class service tender.
4
+
5
+ Engine-room flight manual: track resource checkouts, returns, and retirement.
6
+
7
+ Allocation-free `no_std` lifecycle decisions for fixed resource slots.
8
+ Register externally created resources, check out the oldest idle slot, and return
9
+ it with a generation-checked handle. Duplicate/stale returns are rejected.
10
+ Begin draining to refuse new checkouts; returned resources then retire rather
11
+ than becoming idle. Idle retirement frees a slot for external destruction.
12
+
13
+ Clock observations must not regress. Generations fail closed at exhaustion.
14
+ Handles are scoped to one pool instance/lifetime; do not exchange handles between
15
+ pools. The adapter must serialize these decisions with resource storage and
16
+ actually destroy retired resources. There is no automatic construction, health
17
+ check, async waiting, timeout driver, forced reclamation of busy resources,
18
+ or runtime ownership guarantee.
@@ -0,0 +1,2 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'ffi/extconf'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module PoolMachines
3
+ VERSION = '0.1.3'
4
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'pool_machines/version'
3
+ require 'pool_machines_native/pool_machines_native'
@@ -0,0 +1,16 @@
1
+ module PoolMachines
2
+ VERSION: String
3
+ # Opaque checkout result; direct construction is unsupported.
4
+ class Handle
5
+ def slot: () -> Integer
6
+ end
7
+ class Pool
8
+ def self.new: () -> instance
9
+ def register: (Integer slot, Integer now_ns) -> bool
10
+ def checkout: (Integer now_ns) -> Handle?
11
+ def checkin: (Handle handle, Integer now_ns) -> String
12
+ def begin_drain: () -> nil
13
+ def retire_idle: (Integer now_ns, Integer idle_for_ns) -> Integer?
14
+ def checked_out: () -> Integer
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pool_machines
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: x86_64-linux-musl
6
+ authors:
7
+ - Abdelkader Boudih
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-11-06 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Initial native roster model with draining and retirement. Caller owns
14
+ resources and execution. Local sibling Rust checkout required.
15
+ email:
16
+ - oss@seuros.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - CHANGELOG.md
22
+ - LICENSE.txt
23
+ - README.md
24
+ - ext/pool_machines_native/core/CHANGELOG.md
25
+ - ext/pool_machines_native/core/README.md
26
+ - ext/pool_machines_native/extconf.rb
27
+ - lib/pool_machines.rb
28
+ - lib/pool_machines/version.rb
29
+ - lib/pool_machines_native/pool_machines_native.so
30
+ - sig/pool_machines.rbs
31
+ homepage: https://github.com/seuros/pool_machines
32
+ licenses:
33
+ - MIT
34
+ metadata:
35
+ source_code_uri: https://github.com/seuros/pool_machines
36
+ rubygems_mfa_required: 'true'
37
+ cargo_crate_name: pool_machines_native
38
+ cargo_manifest_path: ext/pool_machines_native/ffi/Cargo.toml
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '4.0'
48
+ - - "<"
49
+ - !ruby/object:Gem::Version
50
+ version: 4.1.dev
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 3.3.22
56
+ requirements: []
57
+ rubygems_version: 3.5.23
58
+ signing_key:
59
+ specification_version: 4
60
+ summary: Native generation-checked resource-slot policy for Ruby
61
+ test_files: []