pipeline_machines 0.1.4-x86_64-linux

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: e4b980ba128e51a91bd4effaf26aae4584a52f4cf2dacc8d3494eab5250fda84
4
+ data.tar.gz: ff87d4042f1ed86a71b2207ed678dcb8dc80119c80e3f1b63599bb48f0c719d0
5
+ SHA512:
6
+ metadata.gz: 2d8faa0c17e0bef8607b50b43bafb92803b02b2759f8658bc13d75f445d42f420ba605466d8b139ce0e69c4c9ce9c7d5b2d0e4fbd759f0b9a405534edb241845
7
+ data.tar.gz: a154716a50a82bf1c338c8f0f6d6a3350891bcef96b142ef20bff35a2ebefcf9481d18af260bdd8dfeb9b1f9614e10fc8d8c0b0b103eaf3e6478940e6aeab987
data/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ ## [0.1.4](https://github.com/seuros/pipeline_machines/compare/pipeline_machines/v0.1.3...pipeline_machines/v0.1.4) (2026-11-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * extract has_queued lane check ([8c9510f](https://github.com/seuros/pipeline_machines/commit/8c9510fbd5987c2d2f9d4c653ad7aff983e30955))
9
+
10
+ ## [0.1.3](https://github.com/seuros/pipeline_machines/compare/pipeline_machines/v0.1.2...pipeline_machines/v0.1.3) (2026-11-06)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * crates.io keywords and categories for the core ([f308575](https://github.com/seuros/pipeline_machines/commit/f30857529c4ac61c145128710bb916770699f760))
16
+
17
+ ## [0.1.2](https://github.com/seuros/pipeline_machines/compare/pipeline_machines/v0.1.1...pipeline_machines/v0.1.2) (2026-11-06)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * keep test sources out of the packaged gem and crate ([cd46e0d](https://github.com/seuros/pipeline_machines/commit/cd46e0d8adb4868270b1278fb7cd24c001c89c72))
23
+
24
+ ## [0.1.1](https://github.com/seuros/pipeline_machines/compare/pipeline_machines-v0.1.0...pipeline_machines/v0.1.1) (2026-11-06)
25
+
26
+
27
+ ### Features
28
+
29
+ * initial extraction ([5e3f8da](https://github.com/seuros/pipeline_machines/commit/5e3f8dadd4cb7159e5fcbdf64a0c8d2de110c680))
30
+
31
+
32
+ ### Bug Fixes
33
+
34
+ * name the gemspec block spec so the release workflow detects the extension ([4a86b80](https://github.com/seuros/pipeline_machines/commit/4a86b80992c296d0157d5488daea23c1295665c4))
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,250 @@
1
+ # PipelineMachines
2
+
3
+ > A job you cancelled is still on the ship until someone signs for it. 🛤️
4
+
5
+ A stage coordinator for Ruby. You submit work into one of four weighted lanes, pull it out in fair order, report what happened, and collect the receipt. Fan a job out across several lanes and join on all of them. The stage keeps count of every slot, including the ones holding work you told to stop but that hasn't stopped yet.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ gem 'pipeline_machines'
11
+ ```
12
+
13
+ ```ruby
14
+ # Four lanes: transcode, thumbnail, captions, notify. Thumbnails get double share.
15
+ stage = PipelineMachines::Stage.new([1, 2, 1, 1])
16
+
17
+ # One upload, three lanes at once.
18
+ jobs = stage.fan_out([0, 1, 2], upload.id)
19
+
20
+ # The worker loop. dispatch hands back [job, payload, lane].
21
+ while (job, payload, lane = stage.dispatch)
22
+ run_lane(lane, payload)
23
+ stage.complete(job)
24
+ end
25
+
26
+ stage.join(jobs) # => :ready
27
+ jobs.each { |job| stage.reap(job) } # collect receipts, free the slots
28
+ ```
29
+
30
+ ## A Message to the Resistance
31
+
32
+ So you built the upload pipeline. A job comes in, you push three tasks onto a queue, three workers pick them up, the video gets transcoded, thumbnailed, and captioned. Then product asked for cancel.
33
+
34
+ You added a `cancelled` flag. Workers check it before starting. Great. Except the transcode worker checked it four seconds ago and is now thirty seconds into an ffmpeg call that doesn't care about your flag. The user sees "cancelled", uploads a replacement, and now two transcodes of the same video are fighting over one CPU. Meanwhile your concurrency limit says there's a free slot, because the cancelled job was removed from the queue the moment the flag flipped.
35
+
36
+ The queue was honest about what was *waiting*. It lied about what was *running*. A slot isn't free when you decide a job should stop. It's free when the job has actually stopped and you've collected the outcome.
37
+
38
+ This gem keeps that count. Queued work can be cancelled outright. Running work stays on the books until the worker confirms it quit, or finishes anyway. Nothing frees a slot except reaping the receipt.
39
+
40
+ ## Chapter 1: Stardate 2025.186, the AMS Welsh
41
+
42
+ > **AMS Welsh**, NCC-4212, *Welsh*-class stage carrier.
43
+ > Freight is not gone merely because it left the waiting deck.
44
+
45
+ The carrier doesn't run any of the work. It holds the manifest: which berths are charged, which descriptors are on the waiting deck, which are on the work deck, and which outcomes are still unsigned. A berth stays charged until the receipt is collected. Not when the job is dispatched, not when it's cancelled. Collected.
46
+
47
+ Somewhere on the bridge, a junior officer proposes:
48
+
49
+ ```ruby
50
+ def cancel(job)
51
+ queue.delete(job)
52
+ job.update(status: :cancelled) # the worker will notice. probably.
53
+ end
54
+ ```
55
+
56
+ "That," says the deck officer, "is how a berth gets charged twice."
57
+
58
+ ## The Flight Plan
59
+
60
+ Every job is in exactly one of these states. Only reaping frees its slot.
61
+
62
+ ```mermaid
63
+ stateDiagram-v2
64
+ [*] --> queued: submit / fan_out
65
+ queued --> running: dispatch
66
+ queued --> cancelled: cancel
67
+ running --> complete: complete
68
+ running --> cancelling: cancel (worker not yet stopped)
69
+ cancelling --> cancelled: confirm_cancel
70
+ cancelling --> complete: complete (finished anyway)
71
+ complete --> [*]: reap
72
+ cancelled --> [*]: reap
73
+ ```
74
+
75
+ And the upload that got cancelled mid-transcode:
76
+
77
+ ```mermaid
78
+ sequenceDiagram
79
+ participant You
80
+ participant Stage
81
+ participant W as Worker
82
+
83
+ You->>Stage: submit(lane 0, upload 42)
84
+ W->>Stage: dispatch
85
+ Stage-->>W: job, 42, lane 0
86
+ Note over W: ffmpeg starts, 30 seconds to go
87
+
88
+ You->>Stage: cancel(job)
89
+ Stage-->>You: true
90
+ You->>Stage: join([job])
91
+ Stage-->>You: pending
92
+ Note over Stage: slot still charged
93
+
94
+ Note over W: ffmpeg killed
95
+ W->>Stage: confirm_cancel(job)
96
+ Stage-->>W: true
97
+ You->>Stage: reap(job)
98
+ Stage-->>You: [42, cancelled]
99
+ Note over Stage: slot free
100
+ ```
101
+
102
+ ## How the Deck Works
103
+
104
+ ### Submit and dispatch
105
+
106
+ Lanes are numbered 0 to 3. Each lane has a weight you choose at construction. Dispatch walks the lanes in weighted round robin, first in first out within a lane.
107
+
108
+ ```ruby
109
+ stage = PipelineMachines::Stage.new([1, 2, 1, 1])
110
+ stage.lanes # => 4
111
+ stage.capacity # => 64
112
+
113
+ stage.submit(0, 100)
114
+ stage.submit(0, 101)
115
+ stage.submit(1, 200)
116
+ stage.submit(1, 201)
117
+ stage.submit(2, 300)
118
+
119
+ job, payload, lane = stage.dispatch
120
+ [payload, lane] # => [100, 0]
121
+ ```
122
+
123
+ Lane 1 has weight 2, so it gets two turns per round. With the queue above, dispatch order is lane 0, lane 1, lane 1, lane 2, then lane 0 again. Payloads are Integers: ids, pointers into your own tables, whatever names the work. The stage never sees the work itself.
124
+
125
+ A lane that doesn't exist, or a full stage, returns `nil` from `submit`. It never raises for that.
126
+
127
+ ### Fan out and join
128
+
129
+ One payload, several lanes, admitted atomically. Either every lane gets a job or none does.
130
+
131
+ ```ruby
132
+ stage = PipelineMachines::Stage.new([1, 1, 1, 1])
133
+ jobs = stage.fan_out([3, 1], 9)
134
+ jobs.size # => 2
135
+ stage.join(jobs) # => :pending
136
+
137
+ 2.times do
138
+ job, = stage.dispatch
139
+ stage.complete(job)
140
+ end
141
+ stage.join(jobs) # => :ready
142
+ ```
143
+
144
+ `join` answers for the whole group. `:ready` when every job completed, `:cancelled` when any was cancelled, `:pending` while any is still queued, running, or waiting on a cancel confirmation, and `:unknown` if any handle has been reaped or belongs to another stage. Lanes in a fan-out must be distinct, and asking for a lane that doesn't exist returns `nil` without admitting anything.
145
+
146
+ ### Cancel, and what cancel actually means
147
+
148
+ Cancel a queued job and it's done. Terminal. Reap it whenever you like.
149
+
150
+ ```ruby
151
+ stage = PipelineMachines::Stage.new([1, 1, 1, 1])
152
+ job = stage.submit(2, 77)
153
+
154
+ stage.cancel(job) # => true
155
+ stage.join([job]) # => :cancelled
156
+ stage.reap(job) # => [77, :cancelled]
157
+ stage.dispatch # => nil, nothing left
158
+ ```
159
+
160
+ Cancel a *running* job and you've only asked. The slot stays charged until the worker says it stopped.
161
+
162
+ ```ruby
163
+ stage = PipelineMachines::Stage.new([1, 1, 1, 1])
164
+ stage.submit(0, 5)
165
+ job, = stage.dispatch
166
+
167
+ stage.cancel(job) # => true, request noted
168
+ stage.reap(job) # => nil, not terminal yet
169
+ stage.join([job]) # => :pending
170
+
171
+ stage.confirm_cancel(job) # => true, worker has stopped
172
+ stage.reap(job) # => [5, :cancelled]
173
+ ```
174
+
175
+ If the worker finishes before it sees the request, `complete` wins and the cancel is moot:
176
+
177
+ ```ruby
178
+ stage = PipelineMachines::Stage.new([1, 1, 1, 1])
179
+ stage.submit(0, 1)
180
+ job, = stage.dispatch
181
+
182
+ stage.cancel(job) # => true
183
+ stage.complete(job) # => true, it finished anyway
184
+ stage.confirm_cancel(job) # => false, nothing to confirm
185
+ stage.reap(job) # => [1, :complete]
186
+ ```
187
+
188
+ ### Reap, or the slot never comes back
189
+
190
+ The stage holds 64 jobs. Not 64 queued, not 64 running. 64 *unreaped*. A completed job you never reap is a slot you never get back.
191
+
192
+ ```ruby
193
+ stage = PipelineMachines::Stage.new([1, 1, 1, 1])
194
+ jobs = 64.times.map { |i| stage.submit(0, i) }
195
+ stage.submit(0, 65) # => nil, full
196
+
197
+ job, = stage.dispatch
198
+ stage.complete(job)
199
+ stage.submit(0, 65) # => nil, still full. complete isn't reap.
200
+
201
+ stage.reap(job) # => [0, :complete]
202
+ stage.submit(0, 65) # now admitted
203
+ ```
204
+
205
+ `reap` only returns for terminal jobs: `[payload, :complete]` or `[payload, :cancelled]`. Anything else is `nil`. After reaping, the handle is dead. Every call with it returns `false` or `nil`, and `join` reports `:unknown`.
206
+
207
+ ### Drain
208
+
209
+ Shutting down a worker pool means two things: stop taking work, and know when the last job is gone.
210
+
211
+ ```ruby
212
+ stage = PipelineMachines::Stage.new([1, 1, 1, 1])
213
+ job = stage.submit(0, 5)
214
+
215
+ stage.begin_drain
216
+ stage.submit(0, 6) # => nil, closed
217
+ stage.drained? # => false, one job still charged
218
+
219
+ work, = stage.dispatch # draining still dispatches what's queued
220
+ stage.complete(work)
221
+ stage.drained? # => false, completed but not reaped
222
+ stage.reap(job)
223
+ stage.drained? # => true
224
+ ```
225
+
226
+ ## The Rules of the Carrier
227
+
228
+ - **Four lanes, sixty-four slots.** Weights are an Array of exactly four Integers in `1..2**32-1`. Anything else raises `TypeError` or `ArgumentError`.
229
+ - **Lanes and payloads are Integers** in `0..2**64-1`. Floats and strings raise `TypeError`. Negative or too large raises `RangeError`.
230
+ - **Bad lanes and full stages return `nil`.** Only malformed input raises.
231
+ - **Handles belong to one stage.** A job from another stage, or one already reaped, mutates nothing: `cancel`, `complete`, and `confirm_cancel` return `false`, `reap` returns `nil`, `join` says `:unknown`.
232
+ - **`join` takes up to 64 handles.** An empty Array is `:ready`.
233
+ - **Frozen means frozen.** Every mutation raises `FrozenError`. `join` and `drained?` still answer.
234
+
235
+ ## What You Think You're Doing vs Reality
236
+
237
+ ### You Think: "I cancelled it, so the slot is free."
238
+ ### Reality: You cancelled it, so the slot is spoken for by a job nobody is watching.
239
+
240
+ Removing work from a queue is instant. Stopping work that has already started is a negotiation with a process that may be deep inside a syscall. Between those two moments the resources are still in use, and any capacity count that says otherwise will admit one job too many, every time, right when the system is under the most pressure.
241
+
242
+ ## What's Yours
243
+
244
+ The stage counts. It doesn't run, schedule, persist, or time anything out. You run the worker loop and call `dispatch` when you have a free worker. You decide what a payload means. You tell the stage `complete` or `confirm_cancel` when the work really stopped, and you `reap` every job, every time, or the slots drain away. Across a restart, the manifest is gone, so write down what you dispatched before you dispatch it.
245
+
246
+ ## License
247
+
248
+ MIT License. See [LICENSE.txt](LICENSE.txt) for details.
249
+
250
+ **Remember: The transcode you cancelled at 14:02 is still burning a core at 14:03. The stage knows. Your queue didn't.**
@@ -0,0 +1,29 @@
1
+ # Changelog
2
+
3
+ ## [0.2.3](https://github.com/seuros/pipeline_machines/compare/pipeline-machines-v0.2.2...pipeline-machines-v0.2.3) (2026-11-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * extract has_queued lane check ([8c9510f](https://github.com/seuros/pipeline_machines/commit/8c9510fbd5987c2d2f9d4c653ad7aff983e30955))
9
+
10
+ ## [0.2.2](https://github.com/seuros/pipeline_machines/compare/pipeline-machines-v0.2.1...pipeline-machines-v0.2.2) (2026-11-06)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * crates.io keywords and categories for the core ([f308575](https://github.com/seuros/pipeline_machines/commit/f30857529c4ac61c145128710bb916770699f760))
16
+
17
+ ## [0.2.1](https://github.com/seuros/pipeline_machines/compare/pipeline-machines-v0.2.0...pipeline-machines-v0.2.1) (2026-11-06)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * keep test sources out of the packaged gem and crate ([cd46e0d](https://github.com/seuros/pipeline_machines/commit/cd46e0d8adb4868270b1278fb7cd24c001c89c72))
23
+
24
+ ## [0.2.0](https://github.com/seuros/pipeline_machines/compare/pipeline-machines-v0.1.0...pipeline-machines-v0.2.0) (2026-11-06)
25
+
26
+
27
+ ### Features
28
+
29
+ * initial extraction ([5e3f8da](https://github.com/seuros/pipeline_machines/commit/5e3f8dadd4cb7159e5fcbdf64a0c8d2de110c680))
@@ -0,0 +1,38 @@
1
+ # pipeline-machines
2
+
3
+ > **AMS Welsh**, NCC-4212, *Welsh*-class stage carrier.
4
+
5
+ Engine-room flight manual: account for bounded cargo stages and unresolved flights.
6
+
7
+ Allocation-free `no_std` coordinator for fixed lanes and shared bounded job slots.
8
+ Copyable payloads should be command IDs or small descriptors; adapters own actual
9
+ resources and execute work. WRR selects ready lanes, FIFO within each lane.
10
+ External eligibility masks can incorporate arbitration or deadline policy.
11
+
12
+ Flow credits bound **all unreaped jobs**, including running and terminal jobs.
13
+ Fanout admission is atomic across distinct lanes. Fan-in reports readiness over
14
+ unreaped handles, not an output aggregation or distributed transaction.
15
+
16
+ Queued cancellation is immediate. Running cancellation becomes `Cancelling` and
17
+ keeps its capacity until the executor confirms cancellation or reports completion.
18
+ This never assumes an unresolved external effect has disappeared. Reap only
19
+ terminal jobs to return credit; generation checks reject stale handles.
20
+ Handle namespaces are scoped to one stage lifetime.
21
+
22
+ Draining refuses submissions while allowing dispatch and completion. No async
23
+ runtime, multi-stage graph orchestration, automatic propagation, result payloads,
24
+ durability, or Ruby binding is implemented.
25
+
26
+ ```rust
27
+ use core::num::NonZeroU32;
28
+ use pipeline_machines::{Stage, State};
29
+ # fn example() -> Option<()> {
30
+ let mut stage = Stage::<u64, 1, 4>::new([NonZeroU32::MIN])?;
31
+ let id = stage.submit(0, 42)?;
32
+ assert_eq!(stage.dispatch(&[true])?.id, id);
33
+ assert!(stage.complete(id));
34
+ assert_eq!(stage.reap(id), Some((42, State::Complete)));
35
+ # Some(())
36
+ # }
37
+ # assert_eq!(example(), Some(()));
38
+ ```
@@ -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 PipelineMachines
3
+ VERSION = '0.1.4'
4
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'pipeline_machines/version'
3
+ require 'pipeline_machines_native/pipeline_machines_native'
@@ -0,0 +1,20 @@
1
+ module PipelineMachines
2
+ VERSION: String
3
+ class Job
4
+ end
5
+ class Stage
6
+ def self.new: (Array[Integer] weights) -> Stage
7
+ def capacity: () -> Integer
8
+ def lanes: () -> Integer
9
+ def submit: (Integer lane, Integer payload) -> Job?
10
+ def fan_out: (Array[Integer] lanes, Integer payload) -> Array[Job]?
11
+ def dispatch: () -> [Job, Integer, Integer]?
12
+ def cancel: (Job) -> bool
13
+ def complete: (Job) -> bool
14
+ def confirm_cancel: (Job) -> bool
15
+ def reap: (Job) -> [Integer, Symbol]?
16
+ def join: (Array[Job]) -> Symbol
17
+ def begin_drain: () -> nil
18
+ def drained?: () -> bool
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pipeline_machines
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ platform: x86_64-linux
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: Local unstable stage policy model, not a durable pipeline runtime.
14
+ email:
15
+ - oss@seuros.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - CHANGELOG.md
21
+ - LICENSE.txt
22
+ - README.md
23
+ - ext/pipeline_machines_native/core/CHANGELOG.md
24
+ - ext/pipeline_machines_native/core/README.md
25
+ - ext/pipeline_machines_native/extconf.rb
26
+ - lib/pipeline_machines.rb
27
+ - lib/pipeline_machines/version.rb
28
+ - lib/pipeline_machines_native/pipeline_machines_native.so
29
+ - sig/pipeline_machines.rbs
30
+ homepage: https://github.com/seuros/pipeline_machines
31
+ licenses:
32
+ - MIT
33
+ metadata:
34
+ source_code_uri: https://github.com/seuros/pipeline_machines
35
+ rubygems_mfa_required: 'true'
36
+ cargo_crate_name: pipeline_machines_native
37
+ cargo_manifest_path: ext/pipeline_machines_native/ffi/Cargo.toml
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '4.0'
47
+ - - "<"
48
+ - !ruby/object:Gem::Version
49
+ version: 4.1.dev
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubygems_version: 3.5.23
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: Initial native bounded stage lifecycle and fanout decisions
60
+ test_files: []