pipeline_machines 0.1.4-aarch64-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 +7 -0
- data/CHANGELOG.md +34 -0
- data/LICENSE.txt +21 -0
- data/README.md +250 -0
- data/ext/pipeline_machines_native/core/CHANGELOG.md +29 -0
- data/ext/pipeline_machines_native/core/README.md +38 -0
- data/ext/pipeline_machines_native/extconf.rb +2 -0
- data/lib/pipeline_machines/version.rb +4 -0
- data/lib/pipeline_machines.rb +3 -0
- data/lib/pipeline_machines_native/pipeline_machines_native.so +0 -0
- data/sig/pipeline_machines.rbs +20 -0
- metadata +60 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 44302ad5b29d0316341c492432b73fcd248522e2622a9079c02a427355ce5bb1
|
|
4
|
+
data.tar.gz: 21c744cec48a552b17027d518e137f4644edb4d24a0e5afa235873feae1fa2ba
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: '08b0ceebbf75defcca9f642cbf2a39efc0b07e49c6913424ab3702e6e512a7a4424342cb7713e9469997e6c0aa7c6fc33484eef5dd25226d93ba2e4794b6a22e'
|
|
7
|
+
data.tar.gz: ece0cce4adf5ddc60b73dd481673df5bb713a5e5f7f65077d02053125299fb555ab20669faae854a743792af2aef3d9eea4d7ff093c17b89440897eaded4579b
|
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
|
+
```
|
|
Binary file
|
|
@@ -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: aarch64-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: []
|